Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 072e4198 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Change Ringer#startRinging to work asynchronously."

parents b0781107 e82a42ff
Loading
Loading
Loading
Loading
+115 −57
Original line number Diff line number Diff line
@@ -20,26 +20,27 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.Person;
import android.content.Context;
import android.os.VibrationEffect;
import android.telecom.Log;
import android.telecom.TelecomManager;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.VolumeShaper;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.telecom.Log;
import android.telecom.TelecomManager;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.telecom.LogUtils.EventTimer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Controls the ringtone player.
@@ -113,6 +114,8 @@ public class Ringer {

    private static final int DEFAULT_RAMPING_RINGER_DURATION = 10000;  // 10 seconds

    private static final long RINGER_ATTRIBUTES_TIMEOUT = 5000; // 5 seconds

    private int mRampingRingerDuration = -1;  // ramping ringer duration in millisecond

    // vibration duration before ramping ringer in second
@@ -151,6 +154,7 @@ public class Ringer {

    private InCallTonePlayer mCallWaitingPlayer;
    private RingtoneFactory mRingtoneFactory;
    private AudioManager mAudioManager;

    /**
     * Call objects that are ringing, vibrating or call-waiting. These are used only for logging
@@ -165,6 +169,8 @@ public class Ringer {
     */
    private boolean mIsVibrating = false;

    private Handler mHandler = null;

    /** Initializes the Ringer. */
    @VisibleForTesting
    public Ringer(
@@ -187,6 +193,7 @@ public class Ringer {
        mRingtoneFactory = ringtoneFactory;
        mInCallController = inCallController;
        mVibrationEffectProxy = vibrationEffectProxy;
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

        if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
            mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
@@ -220,58 +227,39 @@ public class Ringer {
            return false;
        }

        AudioManager audioManager =
                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        LogUtils.EventTimer timer = new EventTimer();
        boolean isVolumeOverZero = audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
        timer.record("isVolumeOverZero");
        boolean shouldRingForContact = shouldRingForContact(foregroundCall.getContactUri());
        timer.record("shouldRingForContact");
        boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(foregroundCall) == null);
        timer.record("getRingtone");
        boolean isSelfManaged = foregroundCall.isSelfManaged();
        timer.record("isSelfManaged");
        boolean isSilentRingingRequested = foregroundCall.isSilentRingingRequested();
        timer.record("isSilentRingRequested");

        boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent;
        timer.record("isRingerAudible");
        boolean hasExternalRinger = hasExternalRinger(foregroundCall);
        timer.record("hasExternalRinger");
        // Don't do call waiting operations or vibration unless these are false.
        boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
        timer.record("isTheaterModeOn");
        boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging();
        timer.record("letDialerHandleRinging");
        // Use completable future to establish a timeout, not intent to make these work outside the
        // main thread asynchronously
        // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking.
        CompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture
                .supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached),
                        new LoggedHandlerExecutor(getHandler(), "R.sR", null));

        Log.i(this, "startRinging timings: " + timer);
        boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
                hasExternalRinger || isSilentRingingRequested;
        RingerAttributes attributes = null;
        try {
            attributes = ringerAttributesFuture.get(
                    RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
        } catch (ExecutionException | InterruptedException | TimeoutException e) {
            // Keep attributs as null
            Log.i(this, "getAttributes error: " + e);
        }

        // Acquire audio focus under any of the following conditions:
        // 1. Should ring for contact and there's an HFP device attached
        // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
        //    present.
        // 3. The call is self-managed.
        boolean shouldAcquireAudioFocus =
                isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
        if (attributes == null) {
            Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error");
            return false;
        }

        if (endEarly) {
            if (letDialerHandleRinging) {
        if (attributes.isEndEarly()) {
            if (attributes.letDialerHandleRinging()) {
                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
            }
            if (isSilentRingingRequested) {
            if (attributes.isSilentRingingRequested()) {
                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
                        + "requested");
            }
            Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s",
                    isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,
                    isSilentRingingRequested);
            if (mBlockOnRingingFuture != null) {
                mBlockOnRingingFuture.complete(null);
            }
            return shouldAcquireAudioFocus;
            return attributes.shouldAcquireAudioFocus();
        }

        stopCallWaiting();
@@ -280,7 +268,7 @@ public class Ringer {
        CompletableFuture<Boolean> hapticsFuture = null;
        // Determine if the settings and DND mode indicate that the vibrator can be used right now.
        boolean isVibratorEnabled = isVibratorEnabled(mContext, foregroundCall);
        if (isRingerAudible) {
        if (attributes.isRingerAudible()) {
            mRingingCall = foregroundCall;
            Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
            // Because we wait until a contact info query to complete before processing a
@@ -331,15 +319,14 @@ public class Ringer {
                effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
            }
        } else {
            String reason = String.format(
                    "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s",
                    isVolumeOverZero, shouldRingForContact, isRingtonePresent);
            Log.i(this, "startRinging: skipping because ringer would not be audible. " + reason);
            Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Inaudible: " + reason);
            Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Inaudible: "
                    + attributes.getInaudibleReason());
            effect = mDefaultVibrationEffect;
        }

        if (hapticsFuture != null) {
            final boolean shouldRingForContact = attributes.shouldRingForContact();
            final boolean isRingerAudible = attributes.isRingerAudible();
            mVibrateFuture = hapticsFuture.thenAccept(isUsingAudioCoupledHaptics -> {
                if (!isUsingAudioCoupledHaptics || !mIsHapticPlaybackSupportedByDevice) {
                    Log.i(this, "startRinging: fileHasHaptics=%b, hapticsSupported=%b",
@@ -365,11 +352,11 @@ public class Ringer {
                mBlockOnRingingFuture.complete(null);
            }
            Log.w(this, "startRinging: No haptics future; fallback to default behavior");
            maybeStartVibration(foregroundCall, shouldRingForContact, effect, isVibratorEnabled,
                    isRingerAudible);
            maybeStartVibration(foregroundCall, attributes.shouldRingForContact(), effect,
                    isVibratorEnabled, attributes.isRingerAudible());
        }

        return shouldAcquireAudioFocus;
        return attributes.shouldAcquireAudioFocus();
    }

    private void maybeStartVibration(Call foregroundCall, boolean shouldRingForContact,
@@ -538,4 +525,75 @@ public class Ringer {
            || (mSystemSettingsUtil.applyRampingRinger(context)
                && mSystemSettingsUtil.enableRampingRingerFromDeviceConfig());
    }

    private RingerAttributes getRingerAttributes(Call call, boolean isHfpDeviceAttached) {
        RingerAttributes.Builder builder = new RingerAttributes.Builder();

        LogUtils.EventTimer timer = new EventTimer();

        boolean isVolumeOverZero = mAudioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
        timer.record("isVolumeOverZero");
        boolean shouldRingForContact = shouldRingForContact(call.getContactUri());
        timer.record("shouldRingForContact");
        boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(call) == null);
        timer.record("getRingtone");
        boolean isSelfManaged = call.isSelfManaged();
        timer.record("isSelfManaged");
        boolean isSilentRingingRequested = call.isSilentRingingRequested();
        timer.record("isSilentRingRequested");

        boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent;
        timer.record("isRingerAudible");
        String inaudibleReason = "";
        if (!isRingerAudible) {
            inaudibleReason = String.format(
                    "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s",
                    isVolumeOverZero, shouldRingForContact, isRingtonePresent);
        }

        boolean hasExternalRinger = hasExternalRinger(call);
        timer.record("hasExternalRinger");
        // Don't do call waiting operations or vibration unless these are false.
        boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
        timer.record("isTheaterModeOn");
        boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging();
        timer.record("letDialerHandleRinging");

        Log.i(this, "startRinging timings: " + timer);
        boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
                hasExternalRinger || isSilentRingingRequested;

        if (endEarly) {
            Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s",
                    isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,
                    isSilentRingingRequested);
        }

        // Acquire audio focus under any of the following conditions:
        // 1. Should ring for contact and there's an HFP device attached
        // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
        //    present.
        // 3. The call is self-managed.
        boolean shouldAcquireAudioFocus =
                isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;

        return builder.setEndEarly(endEarly)
                .setLetDialerHandleRinging(letDialerHandleRinging)
                .setAcquireAudioFocus(shouldAcquireAudioFocus)
                .setRingerAudible(isRingerAudible)
                .setInaudibleReason(inaudibleReason)
                .setShouldRingForContact(shouldRingForContact)
                .setSilentRingingRequested(isSilentRingingRequested)
                .build();
    }

    private Handler getHandler() {
        if (mHandler == null) {
            HandlerThread handlerThread = new HandlerThread("Ringer");
            handlerThread.start();
            mHandler = handlerThread.getThreadHandler();
        }
        return mHandler;
    }
}
+118 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.telecom;

public class RingerAttributes {
    public static class Builder {
        private boolean mEndEarly;
        private boolean mLetDialerHandleRinging;
        private boolean mAcquireAudioFocus;
        private boolean mRingerAudible;
        private String mInaudibleReason;
        private boolean mShouldRingForContact;
        private boolean mSilentRingingRequested;

        public RingerAttributes.Builder setEndEarly(boolean endEarly) {
            mEndEarly = endEarly;
            return this;
        }

        public RingerAttributes.Builder setLetDialerHandleRinging(boolean letDialerHandleRinging) {
            mLetDialerHandleRinging = letDialerHandleRinging;
            return this;
        }

        public RingerAttributes.Builder setAcquireAudioFocus(boolean acquireAudioFocus) {
            mAcquireAudioFocus = acquireAudioFocus;
            return this;
        }

        public RingerAttributes.Builder setRingerAudible(boolean ringerAudible) {
            mRingerAudible = ringerAudible;
            return this;
        }

        public RingerAttributes.Builder setInaudibleReason(String inaudibleReason) {
            mInaudibleReason = inaudibleReason;
            return this;
        }

        public RingerAttributes.Builder setShouldRingForContact(boolean shouldRingForContact) {
            mShouldRingForContact = shouldRingForContact;
            return this;
        }

        public RingerAttributes.Builder setSilentRingingRequested(boolean silentRingingRequested) {
            mSilentRingingRequested = silentRingingRequested;
            return this;
        }

        public RingerAttributes build() {
            return new RingerAttributes(mEndEarly, mLetDialerHandleRinging, mAcquireAudioFocus,
                    mRingerAudible, mInaudibleReason, mShouldRingForContact,
                    mSilentRingingRequested);
        }
    }

    private boolean mEndEarly;
    private boolean mLetDialerHandleRinging;
    private boolean mAcquireAudioFocus;
    private boolean mRingerAudible;
    private String mInaudibleReason;
    private boolean mShouldRingForContact;
    private boolean mSilentRingingRequested;

    private RingerAttributes(boolean endEarly, boolean letDialerHandleRinging,
            boolean acquireAudioFocus, boolean ringerAudible, String inaudibleReason,
            boolean shouldRingForContact, boolean silentRingingRequested) {
        mEndEarly = endEarly;
        mLetDialerHandleRinging = letDialerHandleRinging;
        mAcquireAudioFocus = acquireAudioFocus;
        mRingerAudible = ringerAudible;
        mInaudibleReason = inaudibleReason;
        mShouldRingForContact = shouldRingForContact;
        mSilentRingingRequested = silentRingingRequested;
    }

    public boolean isEndEarly() {
        return mEndEarly;
    }

    public boolean letDialerHandleRinging() {
        return mLetDialerHandleRinging;
    }

    public boolean shouldAcquireAudioFocus() {
        return mAcquireAudioFocus;
    }

    public boolean isRingerAudible() {
        return mRingerAudible;
    }

    public String getInaudibleReason() {
        return mInaudibleReason;
    }

    public boolean shouldRingForContact() {
        return mShouldRingForContact;
    }

    public boolean isSilentRingingRequested() {
        return mSilentRingingRequested;
    }
}