• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.server.telecom;
18 
19 import android.app.Notification;
20 import android.app.NotificationManager;
21 import android.app.Person;
22 import android.content.Context;
23 import android.media.AudioAttributes;
24 import android.media.AudioManager;
25 import android.media.Ringtone;
26 import android.media.VolumeShaper;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.HandlerThread;
31 import android.os.VibrationEffect;
32 import android.os.Vibrator;
33 import android.telecom.Log;
34 import android.telecom.TelecomManager;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.server.telecom.LogUtils.EventTimer;
38 
39 import java.util.ArrayList;
40 import java.util.concurrent.CompletableFuture;
41 import java.util.concurrent.ExecutionException;
42 import java.util.concurrent.TimeUnit;
43 import java.util.concurrent.TimeoutException;
44 
45 /**
46  * Controls the ringtone player.
47  */
48 @VisibleForTesting
49 public class Ringer {
50     public static class VibrationEffectProxy {
createWaveform(long[] timings, int[] amplitudes, int repeat)51         public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
52             return VibrationEffect.createWaveform(timings, amplitudes, repeat);
53         }
54 
get(Uri ringtoneUri, Context context)55         public VibrationEffect get(Uri ringtoneUri, Context context) {
56             return VibrationEffect.get(ringtoneUri, context);
57         }
58     }
59     @VisibleForTesting
60     public VibrationEffect mDefaultVibrationEffect;
61 
62     private static final long[] PULSE_PRIMING_PATTERN = {0,12,250,12,500}; // priming  + interval
63 
64     private static final int[] PULSE_PRIMING_AMPLITUDE = {0,255,0,255,0};  // priming  + interval
65 
66     // ease-in + peak + pause
67     private static final long[] PULSE_RAMPING_PATTERN = {
68         50,50,50,50,50,50,50,50,50,50,50,50,50,50,300,1000};
69 
70     // ease-in (min amplitude = 30%) + peak + pause
71     private static final int[] PULSE_RAMPING_AMPLITUDE = {
72         77,77,78,79,81,84,87,93,101,114,133,162,205,255,255,0};
73 
74     private static final long[] PULSE_PATTERN;
75 
76     private static final int[] PULSE_AMPLITUDE;
77 
78     private static final int RAMPING_RINGER_VIBRATION_DURATION = 5000;
79     private static final int RAMPING_RINGER_DURATION = 10000;
80 
81     static {
82         // construct complete pulse pattern
83         PULSE_PATTERN = new long[PULSE_PRIMING_PATTERN.length + PULSE_RAMPING_PATTERN.length];
System.arraycopy( PULSE_PRIMING_PATTERN, 0, PULSE_PATTERN, 0, PULSE_PRIMING_PATTERN.length)84         System.arraycopy(
85             PULSE_PRIMING_PATTERN, 0, PULSE_PATTERN, 0, PULSE_PRIMING_PATTERN.length);
System.arraycopy(PULSE_RAMPING_PATTERN, 0, PULSE_PATTERN, PULSE_PRIMING_PATTERN.length, PULSE_RAMPING_PATTERN.length)86         System.arraycopy(PULSE_RAMPING_PATTERN, 0, PULSE_PATTERN,
87             PULSE_PRIMING_PATTERN.length, PULSE_RAMPING_PATTERN.length);
88 
89         // construct complete pulse amplitude
90         PULSE_AMPLITUDE = new int[PULSE_PRIMING_AMPLITUDE.length + PULSE_RAMPING_AMPLITUDE.length];
System.arraycopy( PULSE_PRIMING_AMPLITUDE, 0, PULSE_AMPLITUDE, 0, PULSE_PRIMING_AMPLITUDE.length)91         System.arraycopy(
92             PULSE_PRIMING_AMPLITUDE, 0, PULSE_AMPLITUDE, 0, PULSE_PRIMING_AMPLITUDE.length);
System.arraycopy(PULSE_RAMPING_AMPLITUDE, 0, PULSE_AMPLITUDE, PULSE_PRIMING_AMPLITUDE.length, PULSE_RAMPING_AMPLITUDE.length)93         System.arraycopy(PULSE_RAMPING_AMPLITUDE, 0, PULSE_AMPLITUDE,
94             PULSE_PRIMING_AMPLITUDE.length, PULSE_RAMPING_AMPLITUDE.length);
95     }
96 
97     private static final long[] SIMPLE_VIBRATION_PATTERN = {
98             0, // No delay before starting
99             1000, // How long to vibrate
100             1000, // How long to wait before vibrating again
101     };
102 
103     private static final int[] SIMPLE_VIBRATION_AMPLITUDE = {
104             0, // No delay before starting
105             255, // Vibrate full amplitude
106             0, // No amplitude while waiting
107     };
108 
109     /**
110      * Indicates that vibration should be repeated at element 5 in the {@link #PULSE_AMPLITUDE} and
111      * {@link #PULSE_PATTERN} arrays.  This means repetition will happen for the main ease-in/peak
112      * pattern, but the priming + interval part will not be repeated.
113      */
114     private static final int REPEAT_VIBRATION_AT = 5;
115 
116     private static final int REPEAT_SIMPLE_VIBRATION_AT = 1;
117 
118     private static final long RINGER_ATTRIBUTES_TIMEOUT = 5000; // 5 seconds
119 
120     private static final float EPSILON = 1e-6f;
121 
122     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
123             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
124             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
125             .build();
126 
127     private static VibrationEffect mRampingRingerVibrationEffect;
128     private static VolumeShaper.Configuration mVolumeShaperConfig;
129 
130     /**
131      * Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
132      * calls and explicit ordering is useful for maintaining the proper state of the ringer.
133      */
134 
135     private final SystemSettingsUtil mSystemSettingsUtil;
136     private final InCallTonePlayer.Factory mPlayerFactory;
137     private final AsyncRingtonePlayer mRingtonePlayer;
138     private final Context mContext;
139     private final Vibrator mVibrator;
140     private final InCallController mInCallController;
141     private final VibrationEffectProxy mVibrationEffectProxy;
142     private final boolean mIsHapticPlaybackSupportedByDevice;
143     /**
144      * For unit testing purposes only; when set, {@link #startRinging(Call, boolean)} will complete
145      * the future provided by the test using {@link #setBlockOnRingingFuture(CompletableFuture)}.
146      */
147     private CompletableFuture<Void> mBlockOnRingingFuture = null;
148 
149     private CompletableFuture<Void> mVibrateFuture = CompletableFuture.completedFuture(null);
150 
151     private InCallTonePlayer mCallWaitingPlayer;
152     private RingtoneFactory mRingtoneFactory;
153     private AudioManager mAudioManager;
154 
155     /**
156      * Call objects that are ringing, vibrating or call-waiting. These are used only for logging
157      * purposes.
158      */
159     private Call mRingingCall;
160     private Call mVibratingCall;
161     private Call mCallWaitingCall;
162 
163     /**
164      * Used to track the status of {@link #mVibrator} in the case of simultaneous incoming calls.
165      */
166     private boolean mIsVibrating = false;
167 
168     private Handler mHandler = null;
169 
170     /** Initializes the Ringer. */
171     @VisibleForTesting
Ringer( InCallTonePlayer.Factory playerFactory, Context context, SystemSettingsUtil systemSettingsUtil, AsyncRingtonePlayer asyncRingtonePlayer, RingtoneFactory ringtoneFactory, Vibrator vibrator, VibrationEffectProxy vibrationEffectProxy, InCallController inCallController)172     public Ringer(
173             InCallTonePlayer.Factory playerFactory,
174             Context context,
175             SystemSettingsUtil systemSettingsUtil,
176             AsyncRingtonePlayer asyncRingtonePlayer,
177             RingtoneFactory ringtoneFactory,
178             Vibrator vibrator,
179             VibrationEffectProxy vibrationEffectProxy,
180             InCallController inCallController) {
181 
182         mSystemSettingsUtil = systemSettingsUtil;
183         mPlayerFactory = playerFactory;
184         mContext = context;
185         // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
186         // vibrator object will be isolated from others.
187         mVibrator = vibrator;
188         mRingtonePlayer = asyncRingtonePlayer;
189         mRingtoneFactory = ringtoneFactory;
190         mInCallController = inCallController;
191         mVibrationEffectProxy = vibrationEffectProxy;
192         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
193 
194         if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
195             mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
196                     SIMPLE_VIBRATION_AMPLITUDE, REPEAT_SIMPLE_VIBRATION_AT);
197         } else {
198             mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(PULSE_PATTERN,
199                     PULSE_AMPLITUDE, REPEAT_VIBRATION_AT);
200         }
201 
202         mIsHapticPlaybackSupportedByDevice =
203                 mSystemSettingsUtil.isHapticPlaybackSupported(mContext);
204     }
205 
206     @VisibleForTesting
setBlockOnRingingFuture(CompletableFuture<Void> future)207     public void setBlockOnRingingFuture(CompletableFuture<Void> future) {
208         mBlockOnRingingFuture = future;
209     }
210 
startRinging(Call foregroundCall, boolean isHfpDeviceAttached)211     public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) {
212         if (foregroundCall == null) {
213             Log.wtf(this, "startRinging called with null foreground call.");
214             return false;
215         }
216 
217         if (foregroundCall.getState() != CallState.RINGING
218                 && foregroundCall.getState() != CallState.SIMULATED_RINGING) {
219             // Its possible for bluetooth to connect JUST as a call goes active, which would mean
220             // the call would start ringing again.
221             Log.i(this, "startRinging called for non-ringing foreground callid=%s",
222                     foregroundCall.getId());
223             return false;
224         }
225 
226         // Use completable future to establish a timeout, not intent to make these work outside the
227         // main thread asynchronously
228         // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking.
229         CompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture
230                 .supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached),
231                         new LoggedHandlerExecutor(getHandler(), "R.sR", null));
232 
233         RingerAttributes attributes = null;
234         try {
235             attributes = ringerAttributesFuture.get(
236                     RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
237         } catch (ExecutionException | InterruptedException | TimeoutException e) {
238             // Keep attributs as null
239             Log.i(this, "getAttributes error: " + e);
240         }
241 
242         if (attributes == null) {
243             Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error");
244             return false;
245         }
246 
247         if (attributes.isEndEarly()) {
248             if (attributes.letDialerHandleRinging()) {
249                 Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
250             }
251             if (attributes.isSilentRingingRequested()) {
252                 Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
253                         + "requested");
254             }
255             if (mBlockOnRingingFuture != null) {
256                 mBlockOnRingingFuture.complete(null);
257             }
258             return attributes.shouldAcquireAudioFocus();
259         }
260 
261         stopCallWaiting();
262 
263         VibrationEffect effect;
264         CompletableFuture<Boolean> hapticsFuture = null;
265         // Determine if the settings and DND mode indicate that the vibrator can be used right now.
266         boolean isVibratorEnabled = isVibratorEnabled(mContext, foregroundCall);
267         if (attributes.isRingerAudible()) {
268             mRingingCall = foregroundCall;
269             Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
270             // Because we wait until a contact info query to complete before processing a
271             // call (for the purposes of direct-to-voicemail), the information about custom
272             // ringtones should be available by the time this code executes. We can safely
273             // request the custom ringtone from the call and expect it to be current.
274             if (mSystemSettingsUtil.applyRampingRinger(mContext)) {
275                 Log.i(this, "start ramping ringer.");
276                 if (mSystemSettingsUtil.enableAudioCoupledVibrationForRampingRinger()) {
277                     effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
278                 } else {
279                     effect = mDefaultVibrationEffect;
280                 }
281                 if (mVolumeShaperConfig == null) {
282                     float silencePoint = (float) (RAMPING_RINGER_VIBRATION_DURATION)
283                         / (float) (RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION);
284                     mVolumeShaperConfig = new VolumeShaper.Configuration.Builder()
285                         .setDuration(RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION)
286                         .setCurve(new float[] {0.f, silencePoint + EPSILON /*keep monotonicity*/,
287                             1.f}, new float[] {0.f, 0.f, 1.f})
288                         .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
289                         .build();
290                 }
291                 hapticsFuture = mRingtonePlayer.play(mRingtoneFactory, foregroundCall,
292                         mVolumeShaperConfig, isVibratorEnabled);
293             } else {
294                 // Ramping ringtone is not enabled.
295                 hapticsFuture = mRingtonePlayer.play(mRingtoneFactory, foregroundCall, null,
296                         isVibratorEnabled);
297                 effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
298             }
299         } else {
300             Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Inaudible: "
301                     + attributes.getInaudibleReason());
302             effect = mDefaultVibrationEffect;
303         }
304 
305         if (hapticsFuture != null) {
306             final boolean shouldRingForContact = attributes.shouldRingForContact();
307             final boolean isRingerAudible = attributes.isRingerAudible();
308             mVibrateFuture = hapticsFuture.thenAccept(isUsingAudioCoupledHaptics -> {
309                 if (!isUsingAudioCoupledHaptics || !mIsHapticPlaybackSupportedByDevice) {
310                     Log.i(this, "startRinging: fileHasHaptics=%b, hapticsSupported=%b",
311                             isUsingAudioCoupledHaptics, mIsHapticPlaybackSupportedByDevice);
312                     maybeStartVibration(foregroundCall, shouldRingForContact, effect,
313                             isVibratorEnabled, isRingerAudible);
314                 } else if (mSystemSettingsUtil.applyRampingRinger(mContext)
315                            && !mSystemSettingsUtil.enableAudioCoupledVibrationForRampingRinger()) {
316                     Log.i(this, "startRinging: apply ramping ringer vibration");
317                     maybeStartVibration(foregroundCall, shouldRingForContact, effect,
318                             isVibratorEnabled, isRingerAudible);
319                 } else {
320                     Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
321                             "using audio-coupled haptics");
322                 }
323             });
324             if (mBlockOnRingingFuture != null) {
325                 mVibrateFuture.whenComplete((v, e) -> mBlockOnRingingFuture.complete(null));
326             }
327         } else {
328             if (mBlockOnRingingFuture != null) {
329                 mBlockOnRingingFuture.complete(null);
330             }
331             Log.w(this, "startRinging: No haptics future; fallback to default behavior");
332             maybeStartVibration(foregroundCall, attributes.shouldRingForContact(), effect,
333                     isVibratorEnabled, attributes.isRingerAudible());
334         }
335 
336         return attributes.shouldAcquireAudioFocus();
337     }
338 
maybeStartVibration(Call foregroundCall, boolean shouldRingForContact, VibrationEffect effect, boolean isVibrationEnabled, boolean isRingerAudible)339     private void maybeStartVibration(Call foregroundCall, boolean shouldRingForContact,
340         VibrationEffect effect, boolean isVibrationEnabled, boolean isRingerAudible) {
341         if (isVibrationEnabled
342                 && !mIsVibrating && shouldRingForContact) {
343             if (mSystemSettingsUtil.applyRampingRinger(mContext)
344                     && isRingerAudible) {
345                 Log.i(this, "start vibration for ramping ringer.");
346                 mIsVibrating = true;
347                 mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
348             } else {
349                 Log.i(this, "start normal vibration.");
350                 mIsVibrating = true;
351                 mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
352             }
353         } else if (mIsVibrating) {
354             Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION, "already vibrating");
355         }
356     }
357 
getVibrationEffectForCall(RingtoneFactory factory, Call call)358     private VibrationEffect getVibrationEffectForCall(RingtoneFactory factory, Call call) {
359         VibrationEffect effect = null;
360         Ringtone ringtone = factory.getRingtone(call);
361         Uri ringtoneUri = ringtone != null ? ringtone.getUri() : null;
362         if (ringtoneUri != null) {
363             try {
364                 effect = mVibrationEffectProxy.get(ringtoneUri, mContext);
365             } catch (IllegalArgumentException iae) {
366                 // Deep in the bowels of the VibrationEffect class it is possible for an
367                 // IllegalArgumentException to be thrown if there is an invalid URI specified in the
368                 // device config, or a content provider failure.  Rather than crashing the Telecom
369                 // process we will just use the default vibration effect.
370                 Log.e(this, iae, "getVibrationEffectForCall: failed to get vibration effect");
371                 effect = null;
372             }
373         }
374 
375         if (effect == null) {
376             effect = mDefaultVibrationEffect;
377         }
378         return effect;
379     }
380 
startCallWaiting(Call call)381     public void startCallWaiting(Call call) {
382         startCallWaiting(call, null);
383     }
384 
startCallWaiting(Call call, String reason)385     public void startCallWaiting(Call call, String reason) {
386         if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
387             return;
388         }
389 
390         if (mInCallController.doesConnectedDialerSupportRinging()) {
391             Log.addEvent(call, LogUtils.Events.SKIP_RINGING, "Dialer handles");
392             return;
393         }
394 
395         if (call.isSelfManaged()) {
396             Log.addEvent(call, LogUtils.Events.SKIP_RINGING, "Self-managed");
397             return;
398         }
399 
400         Log.v(this, "Playing call-waiting tone.");
401 
402         stopRinging();
403 
404         if (mCallWaitingPlayer == null) {
405             Log.addEvent(call, LogUtils.Events.START_CALL_WAITING_TONE, reason);
406             mCallWaitingCall = call;
407             mCallWaitingPlayer =
408                     mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
409             mCallWaitingPlayer.startTone();
410         }
411     }
412 
stopRinging()413     public void stopRinging() {
414         if (mRingingCall != null) {
415             Log.addEvent(mRingingCall, LogUtils.Events.STOP_RINGER);
416             mRingingCall = null;
417         }
418 
419         mRingtonePlayer.stop();
420 
421         // If we haven't started vibrating because we were waiting for the haptics info, cancel
422         // it and don't vibrate at all.
423         if (mVibrateFuture != null) {
424             mVibrateFuture.cancel(true);
425         }
426 
427         if (mIsVibrating) {
428             Log.addEvent(mVibratingCall, LogUtils.Events.STOP_VIBRATOR);
429             mVibrator.cancel();
430             mIsVibrating = false;
431             mVibratingCall = null;
432         }
433     }
434 
stopCallWaiting()435     public void stopCallWaiting() {
436         Log.v(this, "stop call waiting.");
437         if (mCallWaitingPlayer != null) {
438             if (mCallWaitingCall != null) {
439                 Log.addEvent(mCallWaitingCall, LogUtils.Events.STOP_CALL_WAITING_TONE);
440                 mCallWaitingCall = null;
441             }
442 
443             mCallWaitingPlayer.stopTone();
444             mCallWaitingPlayer = null;
445         }
446     }
447 
isRinging()448     public boolean isRinging() {
449         return mRingtonePlayer.isPlaying();
450     }
451 
shouldRingForContact(Uri contactUri)452     private boolean shouldRingForContact(Uri contactUri) {
453         final NotificationManager manager =
454                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
455         final Bundle peopleExtras = new Bundle();
456         if (contactUri != null) {
457             ArrayList<Person> personList = new ArrayList<>();
458             personList.add(new Person.Builder().setUri(contactUri.toString()).build());
459             peopleExtras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, personList);
460         }
461         return manager.matchesCallFilter(peopleExtras);
462     }
463 
hasExternalRinger(Call foregroundCall)464     private boolean hasExternalRinger(Call foregroundCall) {
465         Bundle intentExtras = foregroundCall.getIntentExtras();
466         if (intentExtras != null) {
467             return intentExtras.getBoolean(TelecomManager.EXTRA_CALL_EXTERNAL_RINGER, false);
468         } else {
469             return false;
470         }
471     }
472 
isVibratorEnabled(Context context, Call call)473     private boolean isVibratorEnabled(Context context, Call call) {
474         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
475         int ringerMode = audioManager.getRingerModeInternal();
476         boolean shouldVibrate;
477         if (getVibrateWhenRinging(context)) {
478             shouldVibrate = ringerMode != AudioManager.RINGER_MODE_SILENT;
479         } else {
480             shouldVibrate = ringerMode == AudioManager.RINGER_MODE_VIBRATE;
481         }
482 
483         // Technically this should be in the calling method, but it seemed a little odd to pass
484         // around a whole bunch of state just for logging purposes.
485         if (shouldVibrate) {
486             Log.addEvent(call, LogUtils.Events.START_VIBRATOR,
487                     "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
488                     mVibrator.hasVibrator(), mSystemSettingsUtil.canVibrateWhenRinging(context),
489                     ringerMode, mIsVibrating);
490         } else {
491             Log.addEvent(call, LogUtils.Events.SKIP_VIBRATION,
492                     "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
493                     mVibrator.hasVibrator(), mSystemSettingsUtil.canVibrateWhenRinging(context),
494                     ringerMode, mIsVibrating);
495         }
496 
497         return shouldVibrate;
498     }
499 
getVibrateWhenRinging(Context context)500     private boolean getVibrateWhenRinging(Context context) {
501         if (!mVibrator.hasVibrator()) {
502             return false;
503         }
504         return mSystemSettingsUtil.canVibrateWhenRinging(context)
505             || mSystemSettingsUtil.applyRampingRinger(context);
506     }
507 
getRingerAttributes(Call call, boolean isHfpDeviceAttached)508     private RingerAttributes getRingerAttributes(Call call, boolean isHfpDeviceAttached) {
509         RingerAttributes.Builder builder = new RingerAttributes.Builder();
510 
511         LogUtils.EventTimer timer = new EventTimer();
512 
513         boolean isVolumeOverZero = mAudioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
514         timer.record("isVolumeOverZero");
515         boolean shouldRingForContact = shouldRingForContact(call.getHandle());
516         timer.record("shouldRingForContact");
517         boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(call) == null);
518         timer.record("getRingtone");
519         boolean isSelfManaged = call.isSelfManaged();
520         timer.record("isSelfManaged");
521         boolean isSilentRingingRequested = call.isSilentRingingRequested();
522         timer.record("isSilentRingRequested");
523 
524         boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent;
525         timer.record("isRingerAudible");
526         String inaudibleReason = "";
527         if (!isRingerAudible) {
528             inaudibleReason = String.format(
529                     "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s",
530                     isVolumeOverZero, shouldRingForContact, isRingtonePresent);
531         }
532 
533         boolean hasExternalRinger = hasExternalRinger(call);
534         timer.record("hasExternalRinger");
535         // Don't do call waiting operations or vibration unless these are false.
536         boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
537         timer.record("isTheaterModeOn");
538         boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging();
539         timer.record("letDialerHandleRinging");
540 
541         Log.i(this, "startRinging timings: " + timer);
542         boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
543                 hasExternalRinger || isSilentRingingRequested;
544 
545         if (endEarly) {
546             Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
547                             "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s",
548                     isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,
549                     isSilentRingingRequested);
550         }
551 
552         // Acquire audio focus under any of the following conditions:
553         // 1. Should ring for contact and there's an HFP device attached
554         // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
555         //    present.
556         // 3. The call is self-managed.
557         boolean shouldAcquireAudioFocus =
558                 isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
559 
560         return builder.setEndEarly(endEarly)
561                 .setLetDialerHandleRinging(letDialerHandleRinging)
562                 .setAcquireAudioFocus(shouldAcquireAudioFocus)
563                 .setRingerAudible(isRingerAudible)
564                 .setInaudibleReason(inaudibleReason)
565                 .setShouldRingForContact(shouldRingForContact)
566                 .setSilentRingingRequested(isSilentRingingRequested)
567                 .build();
568     }
569 
getHandler()570     private Handler getHandler() {
571         if (mHandler == null) {
572             HandlerThread handlerThread = new HandlerThread("Ringer");
573             handlerThread.start();
574             mHandler = handlerThread.getThreadHandler();
575         }
576         return mHandler;
577     }
578 }
579