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.systemui.volume; 18 19 import static android.media.AudioManager.RINGER_MODE_NORMAL; 20 21 import android.app.NotificationManager; 22 import android.content.BroadcastReceiver; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.database.ContentObserver; 31 import android.media.AudioAttributes; 32 import android.media.AudioManager; 33 import android.media.AudioSystem; 34 import android.media.IAudioService; 35 import android.media.IVolumeController; 36 import android.media.VolumePolicy; 37 import android.media.session.MediaController.PlaybackInfo; 38 import android.media.session.MediaSession.Token; 39 import android.net.Uri; 40 import android.os.Handler; 41 import android.os.HandlerThread; 42 import android.os.Looper; 43 import android.os.Message; 44 import android.os.RemoteException; 45 import android.os.ServiceManager; 46 import android.os.VibrationEffect; 47 import android.os.Vibrator; 48 import android.provider.Settings; 49 import android.service.notification.Condition; 50 import android.service.notification.ZenModeConfig; 51 import android.text.TextUtils; 52 import android.util.ArrayMap; 53 import android.util.Log; 54 import android.view.accessibility.AccessibilityManager; 55 56 import com.android.internal.annotations.GuardedBy; 57 import com.android.settingslib.volume.MediaSessions; 58 import com.android.systemui.Dumpable; 59 import com.android.systemui.R; 60 import com.android.systemui.SysUiServiceProvider; 61 import com.android.systemui.keyguard.WakefulnessLifecycle; 62 import com.android.systemui.plugins.VolumeDialogController; 63 import com.android.systemui.qs.tiles.DndTile; 64 import com.android.systemui.statusbar.phone.StatusBar; 65 66 import java.io.FileDescriptor; 67 import java.io.PrintWriter; 68 import java.util.HashMap; 69 import java.util.Map; 70 import java.util.Objects; 71 72 import javax.inject.Inject; 73 import javax.inject.Singleton; 74 75 /** 76 * Source of truth for all state / events related to the volume dialog. No presentation. 77 * 78 * All work done on a dedicated background worker thread & associated worker. 79 * 80 * Methods ending in "W" must be called on the worker thread. 81 */ 82 @Singleton 83 public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable { 84 private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class); 85 86 87 private static final int TOUCH_FEEDBACK_TIMEOUT_MS = 1000; 88 private static final int DYNAMIC_STREAM_START_INDEX = 100; 89 private static final int VIBRATE_HINT_DURATION = 50; 90 private static final AudioAttributes SONIFICIATION_VIBRATION_ATTRIBUTES = 91 new AudioAttributes.Builder() 92 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 93 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) 94 .build(); 95 96 static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>(); 97 static { 98 STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm); 99 STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco); 100 STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf); 101 STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music); 102 STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility); 103 STREAMS.put(AudioSystem.STREAM_NOTIFICATION, R.string.stream_notification); 104 STREAMS.put(AudioSystem.STREAM_RING, R.string.stream_ring); 105 STREAMS.put(AudioSystem.STREAM_SYSTEM, R.string.stream_system); 106 STREAMS.put(AudioSystem.STREAM_SYSTEM_ENFORCED, R.string.stream_system_enforced); 107 STREAMS.put(AudioSystem.STREAM_TTS, R.string.stream_tts); 108 STREAMS.put(AudioSystem.STREAM_VOICE_CALL, R.string.stream_voice_call); 109 } 110 111 private final HandlerThread mWorkerThread; 112 private final W mWorker; 113 private final Context mContext; 114 private AudioManager mAudio; 115 private IAudioService mAudioService; 116 protected StatusBar mStatusBar; 117 private final NotificationManager mNoMan; 118 private final SettingObserver mObserver; 119 private final Receiver mReceiver = new Receiver(); 120 private final MediaSessions mMediaSessions; 121 protected C mCallbacks = new C(); 122 private final State mState = new State(); 123 protected final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks(); 124 private final Vibrator mVibrator; 125 private final boolean mHasVibrator; 126 private boolean mShowA11yStream; 127 private boolean mShowVolumeDialog; 128 private boolean mShowSafetyWarning; 129 private long mLastToggledRingerOn; 130 private final NotificationManager mNotificationManager; 131 132 private boolean mDestroyed; 133 private VolumePolicy mVolumePolicy; 134 private boolean mShowDndTile = true; 135 @GuardedBy("this") 136 private UserActivityListener mUserActivityListener; 137 138 protected final VC mVolumeController = new VC(); 139 140 @Inject 141 public VolumeDialogControllerImpl(Context context) { 142 mContext = context.getApplicationContext(); 143 mNotificationManager = (NotificationManager) mContext.getSystemService( 144 Context.NOTIFICATION_SERVICE); 145 Events.writeEvent(mContext, Events.EVENT_COLLECTION_STARTED); 146 mWorkerThread = new HandlerThread(VolumeDialogControllerImpl.class.getSimpleName()); 147 mWorkerThread.start(); 148 mWorker = new W(mWorkerThread.getLooper()); 149 mMediaSessions = createMediaSessions(mContext, mWorkerThread.getLooper(), 150 mMediaSessionsCallbacksW); 151 mAudio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 152 mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 153 mObserver = new SettingObserver(mWorker); 154 mObserver.init(); 155 mReceiver.init(); 156 mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); 157 mHasVibrator = mVibrator != null && mVibrator.hasVibrator(); 158 mAudioService = IAudioService.Stub.asInterface( 159 ServiceManager.getService(Context.AUDIO_SERVICE)); 160 updateStatusBar(); 161 162 boolean accessibilityVolumeStreamActive = context.getSystemService( 163 AccessibilityManager.class).isAccessibilityVolumeStreamActive(); 164 mVolumeController.setA11yMode(accessibilityVolumeStreamActive ? 165 VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME : 166 VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME); 167 } 168 169 public AudioManager getAudioManager() { 170 return mAudio; 171 } 172 173 public void dismiss() { 174 mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER); 175 } 176 177 protected void setVolumeController() { 178 try { 179 mAudio.setVolumeController(mVolumeController); 180 } catch (SecurityException e) { 181 Log.w(TAG, "Unable to set the volume controller", e); 182 return; 183 } 184 } 185 186 protected void setAudioManagerStreamVolume(int stream, int level, int flag) { 187 mAudio.setStreamVolume(stream, level, flag); 188 } 189 190 protected int getAudioManagerStreamVolume(int stream) { 191 return mAudio.getLastAudibleStreamVolume(stream); 192 } 193 194 protected int getAudioManagerStreamMaxVolume(int stream) { 195 return mAudio.getStreamMaxVolume(stream); 196 } 197 198 protected int getAudioManagerStreamMinVolume(int stream) { 199 return mAudio.getStreamMinVolumeInt(stream); 200 } 201 202 public void register() { 203 setVolumeController(); 204 setVolumePolicy(mVolumePolicy); 205 showDndTile(mShowDndTile); 206 try { 207 mMediaSessions.init(); 208 } catch (SecurityException e) { 209 Log.w(TAG, "No access to media sessions", e); 210 } 211 } 212 213 public void setVolumePolicy(VolumePolicy policy) { 214 mVolumePolicy = policy; 215 if (mVolumePolicy == null) return; 216 try { 217 mAudio.setVolumePolicy(mVolumePolicy); 218 } catch (NoSuchMethodError e) { 219 Log.w(TAG, "No volume policy api"); 220 } 221 } 222 223 protected MediaSessions createMediaSessions(Context context, Looper looper, 224 MediaSessions.Callbacks callbacks) { 225 return new MediaSessions(context, looper, callbacks); 226 } 227 228 public void destroy() { 229 if (D.BUG) Log.d(TAG, "destroy"); 230 if (mDestroyed) return; 231 mDestroyed = true; 232 Events.writeEvent(mContext, Events.EVENT_COLLECTION_STOPPED); 233 mMediaSessions.destroy(); 234 mObserver.destroy(); 235 mReceiver.destroy(); 236 mWorkerThread.quitSafely(); 237 } 238 239 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 240 pw.println(VolumeDialogControllerImpl.class.getSimpleName() + " state:"); 241 pw.print(" mDestroyed: "); pw.println(mDestroyed); 242 pw.print(" mVolumePolicy: "); pw.println(mVolumePolicy); 243 pw.print(" mState: "); pw.println(mState.toString(4)); 244 pw.print(" mShowDndTile: "); pw.println(mShowDndTile); 245 pw.print(" mHasVibrator: "); pw.println(mHasVibrator); 246 pw.print(" mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams 247 .values()); 248 pw.print(" mShowA11yStream: "); pw.println(mShowA11yStream); 249 pw.println(); 250 mMediaSessions.dump(pw); 251 } 252 253 public void addCallback(Callbacks callback, Handler handler) { 254 mCallbacks.add(callback, handler); 255 callback.onAccessibilityModeChanged(mShowA11yStream); 256 } 257 258 public void setUserActivityListener(UserActivityListener listener) { 259 if (mDestroyed) return; 260 synchronized (this) { 261 mUserActivityListener = listener; 262 } 263 } 264 265 public void removeCallback(Callbacks callback) { 266 mCallbacks.remove(callback); 267 } 268 269 public void getState() { 270 if (mDestroyed) return; 271 mWorker.sendEmptyMessage(W.GET_STATE); 272 } 273 274 public boolean areCaptionsEnabled() { 275 int currentValue = Settings.Secure.getInt(mContext.getContentResolver(), 276 Settings.Secure.ODI_CAPTIONS_ENABLED, 0); 277 return currentValue == 1; 278 } 279 280 public void setCaptionsEnabled(boolean isEnabled) { 281 Settings.Secure.putInt(mContext.getContentResolver(), 282 Settings.Secure.ODI_CAPTIONS_ENABLED, isEnabled ? 1 : 0); 283 } 284 285 @Override 286 public boolean isCaptionStreamOptedOut() { 287 // TODO(b/129768185): Removing secure setting, to be replaced by sound event listener 288 return false; 289 } 290 291 public void getCaptionsComponentState(boolean fromTooltip) { 292 if (mDestroyed) return; 293 mWorker.obtainMessage(W.GET_CAPTIONS_COMPONENT_STATE, fromTooltip).sendToTarget(); 294 } 295 296 public void notifyVisible(boolean visible) { 297 if (mDestroyed) return; 298 mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget(); 299 } 300 301 public void userActivity() { 302 if (mDestroyed) return; 303 mWorker.removeMessages(W.USER_ACTIVITY); 304 mWorker.sendEmptyMessage(W.USER_ACTIVITY); 305 } 306 307 public void setRingerMode(int value, boolean external) { 308 if (mDestroyed) return; 309 mWorker.obtainMessage(W.SET_RINGER_MODE, value, external ? 1 : 0).sendToTarget(); 310 } 311 312 public void setZenMode(int value) { 313 if (mDestroyed) return; 314 mWorker.obtainMessage(W.SET_ZEN_MODE, value, 0).sendToTarget(); 315 } 316 317 public void setExitCondition(Condition condition) { 318 if (mDestroyed) return; 319 mWorker.obtainMessage(W.SET_EXIT_CONDITION, condition).sendToTarget(); 320 } 321 322 public void setStreamMute(int stream, boolean mute) { 323 if (mDestroyed) return; 324 mWorker.obtainMessage(W.SET_STREAM_MUTE, stream, mute ? 1 : 0).sendToTarget(); 325 } 326 327 public void setStreamVolume(int stream, int level) { 328 if (mDestroyed) return; 329 mWorker.obtainMessage(W.SET_STREAM_VOLUME, stream, level).sendToTarget(); 330 } 331 332 public void setActiveStream(int stream) { 333 if (mDestroyed) return; 334 mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget(); 335 } 336 337 public void setEnableDialogs(boolean volumeUi, boolean safetyWarning) { 338 mShowVolumeDialog = volumeUi; 339 mShowSafetyWarning = safetyWarning; 340 } 341 342 @Override 343 public void scheduleTouchFeedback() { 344 mLastToggledRingerOn = System.currentTimeMillis(); 345 } 346 347 private void playTouchFeedback() { 348 if (System.currentTimeMillis() - mLastToggledRingerOn < TOUCH_FEEDBACK_TIMEOUT_MS) { 349 try { 350 mAudioService.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD); 351 } catch (RemoteException e) { 352 // ignore 353 } 354 } 355 } 356 357 public void vibrate(VibrationEffect effect) { 358 if (mHasVibrator) { 359 mVibrator.vibrate(effect, SONIFICIATION_VIBRATION_ATTRIBUTES); 360 } 361 } 362 363 public boolean hasVibrator() { 364 return mHasVibrator; 365 } 366 367 private void onNotifyVisibleW(boolean visible) { 368 if (mDestroyed) return; 369 mAudio.notifyVolumeControllerVisible(mVolumeController, visible); 370 if (!visible) { 371 if (updateActiveStreamW(-1)) { 372 mCallbacks.onStateChanged(mState); 373 } 374 } 375 } 376 377 private void onUserActivityW() { 378 synchronized (this) { 379 if (mUserActivityListener != null) { 380 mUserActivityListener.onUserActivity(); 381 } 382 } 383 } 384 385 private void onShowSafetyWarningW(int flags) { 386 if (mShowSafetyWarning) { 387 mCallbacks.onShowSafetyWarning(flags); 388 } 389 } 390 391 private void onGetCaptionsComponentStateW(boolean fromTooltip) { 392 try { 393 String componentNameString = mContext.getString( 394 com.android.internal.R.string.config_defaultSystemCaptionsService); 395 if (TextUtils.isEmpty(componentNameString)) { 396 // component doesn't exist 397 mCallbacks.onCaptionComponentStateChanged(false, fromTooltip); 398 return; 399 } 400 401 if (D.BUG) { 402 Log.i(TAG, String.format( 403 "isCaptionsServiceEnabled componentNameString=%s", componentNameString)); 404 } 405 406 ComponentName componentName = ComponentName.unflattenFromString(componentNameString); 407 if (componentName == null) { 408 mCallbacks.onCaptionComponentStateChanged(false, fromTooltip); 409 return; 410 } 411 412 PackageManager packageManager = mContext.getPackageManager(); 413 mCallbacks.onCaptionComponentStateChanged( 414 packageManager.getComponentEnabledSetting(componentName) 415 == PackageManager.COMPONENT_ENABLED_STATE_ENABLED, fromTooltip); 416 } catch (Exception ex) { 417 Log.e(TAG, 418 "isCaptionsServiceEnabled failed to check for captions component", ex); 419 mCallbacks.onCaptionComponentStateChanged(false, fromTooltip); 420 } 421 } 422 423 private void onAccessibilityModeChanged(Boolean showA11yStream) { 424 mCallbacks.onAccessibilityModeChanged(showA11yStream); 425 } 426 427 private boolean checkRoutedToBluetoothW(int stream) { 428 boolean changed = false; 429 if (stream == AudioManager.STREAM_MUSIC) { 430 final boolean routedToBluetooth = 431 (mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) & 432 (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP | 433 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | 434 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0; 435 changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth); 436 } 437 return changed; 438 } 439 440 private void updateStatusBar() { 441 if (mStatusBar == null) { 442 mStatusBar = SysUiServiceProvider.getComponent(mContext, StatusBar.class); 443 } 444 } 445 446 private boolean shouldShowUI(int flags) { 447 updateStatusBar(); 448 // if status bar isn't null, check if phone is in AOD, else check flags 449 // since we could be using a different status bar 450 return mStatusBar != null ? 451 mStatusBar.getWakefulnessState() != WakefulnessLifecycle.WAKEFULNESS_ASLEEP 452 && mStatusBar.getWakefulnessState() != 453 WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP 454 && mStatusBar.isDeviceInteractive() 455 && (flags & AudioManager.FLAG_SHOW_UI) != 0 && mShowVolumeDialog 456 : mShowVolumeDialog && (flags & AudioManager.FLAG_SHOW_UI) != 0; 457 } 458 459 boolean onVolumeChangedW(int stream, int flags) { 460 final boolean showUI = shouldShowUI(flags); 461 final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0; 462 final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0; 463 final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0; 464 boolean changed = false; 465 if (showUI) { 466 changed |= updateActiveStreamW(stream); 467 } 468 int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream); 469 changed |= updateStreamLevelW(stream, lastAudibleStreamVolume); 470 changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream); 471 if (changed) { 472 mCallbacks.onStateChanged(mState); 473 } 474 if (showUI) { 475 mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED); 476 } 477 if (showVibrateHint) { 478 mCallbacks.onShowVibrateHint(); 479 } 480 if (showSilentHint) { 481 mCallbacks.onShowSilentHint(); 482 } 483 if (changed && fromKey) { 484 Events.writeEvent(mContext, Events.EVENT_KEY, stream, lastAudibleStreamVolume); 485 } 486 return changed; 487 } 488 489 private boolean updateActiveStreamW(int activeStream) { 490 if (activeStream == mState.activeStream) return false; 491 mState.activeStream = activeStream; 492 Events.writeEvent(mContext, Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream); 493 if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream); 494 final int s = activeStream < DYNAMIC_STREAM_START_INDEX ? activeStream : -1; 495 if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s); 496 mAudio.forceVolumeControlStream(s); 497 return true; 498 } 499 500 private StreamState streamStateW(int stream) { 501 StreamState ss = mState.states.get(stream); 502 if (ss == null) { 503 ss = new StreamState(); 504 mState.states.put(stream, ss); 505 } 506 return ss; 507 } 508 509 private void onGetStateW() { 510 for (int stream : STREAMS.keySet()) { 511 updateStreamLevelW(stream, getAudioManagerStreamVolume(stream)); 512 streamStateW(stream).levelMin = getAudioManagerStreamMinVolume(stream); 513 streamStateW(stream).levelMax = Math.max(1, getAudioManagerStreamMaxVolume(stream)); 514 updateStreamMuteW(stream, mAudio.isStreamMute(stream)); 515 final StreamState ss = streamStateW(stream); 516 ss.muteSupported = mAudio.isStreamAffectedByMute(stream); 517 ss.name = STREAMS.get(stream); 518 checkRoutedToBluetoothW(stream); 519 } 520 updateRingerModeExternalW(mAudio.getRingerMode()); 521 updateZenModeW(); 522 updateZenConfig(); 523 updateEffectsSuppressorW(mNoMan.getEffectsSuppressor()); 524 mCallbacks.onStateChanged(mState); 525 } 526 527 private boolean updateStreamRoutedToBluetoothW(int stream, boolean routedToBluetooth) { 528 final StreamState ss = streamStateW(stream); 529 if (ss.routedToBluetooth == routedToBluetooth) return false; 530 ss.routedToBluetooth = routedToBluetooth; 531 if (D.BUG) Log.d(TAG, "updateStreamRoutedToBluetoothW stream=" + stream 532 + " routedToBluetooth=" + routedToBluetooth); 533 return true; 534 } 535 536 private boolean updateStreamLevelW(int stream, int level) { 537 final StreamState ss = streamStateW(stream); 538 if (ss.level == level) return false; 539 ss.level = level; 540 if (isLogWorthy(stream)) { 541 Events.writeEvent(mContext, Events.EVENT_LEVEL_CHANGED, stream, level); 542 } 543 return true; 544 } 545 546 private static boolean isLogWorthy(int stream) { 547 switch (stream) { 548 case AudioSystem.STREAM_ALARM: 549 case AudioSystem.STREAM_BLUETOOTH_SCO: 550 case AudioSystem.STREAM_MUSIC: 551 case AudioSystem.STREAM_RING: 552 case AudioSystem.STREAM_SYSTEM: 553 case AudioSystem.STREAM_VOICE_CALL: 554 return true; 555 } 556 return false; 557 } 558 559 private boolean updateStreamMuteW(int stream, boolean muted) { 560 final StreamState ss = streamStateW(stream); 561 if (ss.muted == muted) return false; 562 ss.muted = muted; 563 if (isLogWorthy(stream)) { 564 Events.writeEvent(mContext, Events.EVENT_MUTE_CHANGED, stream, muted); 565 } 566 if (muted && isRinger(stream)) { 567 updateRingerModeInternalW(mAudio.getRingerModeInternal()); 568 } 569 return true; 570 } 571 572 private static boolean isRinger(int stream) { 573 return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION; 574 } 575 576 private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) { 577 if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false; 578 mState.effectsSuppressor = effectsSuppressor; 579 mState.effectsSuppressorName = getApplicationName(mContext, mState.effectsSuppressor); 580 Events.writeEvent(mContext, Events.EVENT_SUPPRESSOR_CHANGED, mState.effectsSuppressor, 581 mState.effectsSuppressorName); 582 return true; 583 } 584 585 private static String getApplicationName(Context context, ComponentName component) { 586 if (component == null) return null; 587 final PackageManager pm = context.getPackageManager(); 588 final String pkg = component.getPackageName(); 589 try { 590 final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0); 591 final String rt = Objects.toString(ai.loadLabel(pm), "").trim(); 592 if (rt.length() > 0) { 593 return rt; 594 } 595 } catch (NameNotFoundException e) {} 596 return pkg; 597 } 598 599 private boolean updateZenModeW() { 600 final int zen = Settings.Global.getInt(mContext.getContentResolver(), 601 Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); 602 if (mState.zenMode == zen) return false; 603 mState.zenMode = zen; 604 Events.writeEvent(mContext, Events.EVENT_ZEN_MODE_CHANGED, zen); 605 return true; 606 } 607 608 private boolean updateZenConfig() { 609 final NotificationManager.Policy policy = 610 mNotificationManager.getConsolidatedNotificationPolicy(); 611 boolean disallowAlarms = (policy.priorityCategories & NotificationManager.Policy 612 .PRIORITY_CATEGORY_ALARMS) == 0; 613 boolean disallowMedia = (policy.priorityCategories & NotificationManager.Policy 614 .PRIORITY_CATEGORY_MEDIA) == 0; 615 boolean disallowSystem = (policy.priorityCategories & NotificationManager.Policy 616 .PRIORITY_CATEGORY_SYSTEM) == 0; 617 boolean disallowRinger = ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(policy); 618 if (mState.disallowAlarms == disallowAlarms 619 && mState.disallowMedia == disallowMedia 620 && mState.disallowRinger == disallowRinger 621 && mState.disallowSystem == disallowSystem) { 622 return false; 623 } 624 mState.disallowAlarms = disallowAlarms; 625 mState.disallowMedia = disallowMedia; 626 mState.disallowSystem = disallowSystem; 627 mState.disallowRinger = disallowRinger; 628 Events.writeEvent(mContext, Events.EVENT_ZEN_CONFIG_CHANGED, "disallowAlarms=" + 629 disallowAlarms + " disallowMedia=" + disallowMedia + " disallowSystem=" + 630 disallowSystem + " disallowRinger=" + disallowRinger); 631 return true; 632 } 633 634 private boolean updateRingerModeExternalW(int rm) { 635 if (rm == mState.ringerModeExternal) return false; 636 mState.ringerModeExternal = rm; 637 Events.writeEvent(mContext, Events.EVENT_EXTERNAL_RINGER_MODE_CHANGED, rm); 638 return true; 639 } 640 641 private boolean updateRingerModeInternalW(int rm) { 642 if (rm == mState.ringerModeInternal) return false; 643 mState.ringerModeInternal = rm; 644 Events.writeEvent(mContext, Events.EVENT_INTERNAL_RINGER_MODE_CHANGED, rm); 645 646 if (mState.ringerModeInternal == RINGER_MODE_NORMAL) { 647 playTouchFeedback(); 648 } 649 650 return true; 651 } 652 653 private void onSetRingerModeW(int mode, boolean external) { 654 if (external) { 655 mAudio.setRingerMode(mode); 656 } else { 657 mAudio.setRingerModeInternal(mode); 658 } 659 } 660 661 private void onSetStreamMuteW(int stream, boolean mute) { 662 mAudio.adjustStreamVolume(stream, mute ? AudioManager.ADJUST_MUTE 663 : AudioManager.ADJUST_UNMUTE, 0); 664 } 665 666 private void onSetStreamVolumeW(int stream, int level) { 667 if (D.BUG) Log.d(TAG, "onSetStreamVolume " + stream + " level=" + level); 668 if (stream >= DYNAMIC_STREAM_START_INDEX) { 669 mMediaSessionsCallbacksW.setStreamVolume(stream, level); 670 return; 671 } 672 setAudioManagerStreamVolume(stream, level, 0); 673 } 674 675 private void onSetActiveStreamW(int stream) { 676 boolean changed = updateActiveStreamW(stream); 677 if (changed) { 678 mCallbacks.onStateChanged(mState); 679 } 680 } 681 682 private void onSetExitConditionW(Condition condition) { 683 mNoMan.setZenMode(mState.zenMode, condition != null ? condition.id : null, TAG); 684 } 685 686 private void onSetZenModeW(int mode) { 687 if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode); 688 mNoMan.setZenMode(mode, null, TAG); 689 } 690 691 private void onDismissRequestedW(int reason) { 692 mCallbacks.onDismissRequested(reason); 693 } 694 695 public void showDndTile(boolean visible) { 696 if (D.BUG) Log.d(TAG, "showDndTile"); 697 DndTile.setVisible(mContext, visible); 698 } 699 700 private final class VC extends IVolumeController.Stub { 701 private final String TAG = VolumeDialogControllerImpl.TAG + ".VC"; 702 703 @Override 704 public void displaySafeVolumeWarning(int flags) throws RemoteException { 705 if (D.BUG) Log.d(TAG, "displaySafeVolumeWarning " 706 + Util.audioManagerFlagsToString(flags)); 707 if (mDestroyed) return; 708 mWorker.obtainMessage(W.SHOW_SAFETY_WARNING, flags, 0).sendToTarget(); 709 } 710 711 @Override 712 public void volumeChanged(int streamType, int flags) throws RemoteException { 713 if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType) 714 + " " + Util.audioManagerFlagsToString(flags)); 715 if (mDestroyed) return; 716 mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget(); 717 } 718 719 @Override 720 public void masterMuteChanged(int flags) throws RemoteException { 721 if (D.BUG) Log.d(TAG, "masterMuteChanged"); 722 } 723 724 @Override 725 public void setLayoutDirection(int layoutDirection) throws RemoteException { 726 if (D.BUG) Log.d(TAG, "setLayoutDirection"); 727 if (mDestroyed) return; 728 mWorker.obtainMessage(W.LAYOUT_DIRECTION_CHANGED, layoutDirection, 0).sendToTarget(); 729 } 730 731 @Override 732 public void dismiss() throws RemoteException { 733 if (D.BUG) Log.d(TAG, "dismiss requested"); 734 if (mDestroyed) return; 735 mWorker.obtainMessage(W.DISMISS_REQUESTED, Events.DISMISS_REASON_VOLUME_CONTROLLER, 0) 736 .sendToTarget(); 737 mWorker.sendEmptyMessage(W.DISMISS_REQUESTED); 738 } 739 740 @Override 741 public void setA11yMode(int mode) { 742 if (D.BUG) Log.d(TAG, "setA11yMode to " + mode); 743 if (mDestroyed) return; 744 switch (mode) { 745 case VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME: 746 // "legacy" mode 747 mShowA11yStream = false; 748 break; 749 case VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME: 750 mShowA11yStream = true; 751 break; 752 default: 753 Log.e(TAG, "Invalid accessibility mode " + mode); 754 break; 755 } 756 mWorker.obtainMessage(W.ACCESSIBILITY_MODE_CHANGED, mShowA11yStream).sendToTarget(); 757 } 758 } 759 760 private final class W extends Handler { 761 private static final int VOLUME_CHANGED = 1; 762 private static final int DISMISS_REQUESTED = 2; 763 private static final int GET_STATE = 3; 764 private static final int SET_RINGER_MODE = 4; 765 private static final int SET_ZEN_MODE = 5; 766 private static final int SET_EXIT_CONDITION = 6; 767 private static final int SET_STREAM_MUTE = 7; 768 private static final int LAYOUT_DIRECTION_CHANGED = 8; 769 private static final int CONFIGURATION_CHANGED = 9; 770 private static final int SET_STREAM_VOLUME = 10; 771 private static final int SET_ACTIVE_STREAM = 11; 772 private static final int NOTIFY_VISIBLE = 12; 773 private static final int USER_ACTIVITY = 13; 774 private static final int SHOW_SAFETY_WARNING = 14; 775 private static final int ACCESSIBILITY_MODE_CHANGED = 15; 776 private static final int GET_CAPTIONS_COMPONENT_STATE = 16; 777 778 W(Looper looper) { 779 super(looper); 780 } 781 782 @Override 783 public void handleMessage(Message msg) { 784 switch (msg.what) { 785 case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break; 786 case DISMISS_REQUESTED: onDismissRequestedW(msg.arg1); break; 787 case GET_STATE: onGetStateW(); break; 788 case SET_RINGER_MODE: onSetRingerModeW(msg.arg1, msg.arg2 != 0); break; 789 case SET_ZEN_MODE: onSetZenModeW(msg.arg1); break; 790 case SET_EXIT_CONDITION: onSetExitConditionW((Condition) msg.obj); break; 791 case SET_STREAM_MUTE: onSetStreamMuteW(msg.arg1, msg.arg2 != 0); break; 792 case LAYOUT_DIRECTION_CHANGED: mCallbacks.onLayoutDirectionChanged(msg.arg1); break; 793 case CONFIGURATION_CHANGED: mCallbacks.onConfigurationChanged(); break; 794 case SET_STREAM_VOLUME: onSetStreamVolumeW(msg.arg1, msg.arg2); break; 795 case SET_ACTIVE_STREAM: onSetActiveStreamW(msg.arg1); break; 796 case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break; 797 case USER_ACTIVITY: onUserActivityW(); break; 798 case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break; 799 case GET_CAPTIONS_COMPONENT_STATE: 800 onGetCaptionsComponentStateW((Boolean) msg.obj); break; 801 case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj); 802 } 803 } 804 } 805 806 class C implements Callbacks { 807 private final HashMap<Callbacks, Handler> mCallbackMap = new HashMap<>(); 808 809 public void add(Callbacks callback, Handler handler) { 810 if (callback == null || handler == null) throw new IllegalArgumentException(); 811 mCallbackMap.put(callback, handler); 812 } 813 814 public void remove(Callbacks callback) { 815 mCallbackMap.remove(callback); 816 } 817 818 @Override 819 public void onShowRequested(final int reason) { 820 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 821 entry.getValue().post(new Runnable() { 822 @Override 823 public void run() { 824 entry.getKey().onShowRequested(reason); 825 } 826 }); 827 } 828 } 829 830 @Override 831 public void onDismissRequested(final int reason) { 832 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 833 entry.getValue().post(new Runnable() { 834 @Override 835 public void run() { 836 entry.getKey().onDismissRequested(reason); 837 } 838 }); 839 } 840 } 841 842 @Override 843 public void onStateChanged(final State state) { 844 final long time = System.currentTimeMillis(); 845 final State copy = state.copy(); 846 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 847 entry.getValue().post(new Runnable() { 848 @Override 849 public void run() { 850 entry.getKey().onStateChanged(copy); 851 } 852 }); 853 } 854 Events.writeState(time, copy); 855 } 856 857 @Override 858 public void onLayoutDirectionChanged(final int layoutDirection) { 859 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 860 entry.getValue().post(new Runnable() { 861 @Override 862 public void run() { 863 entry.getKey().onLayoutDirectionChanged(layoutDirection); 864 } 865 }); 866 } 867 } 868 869 @Override 870 public void onConfigurationChanged() { 871 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 872 entry.getValue().post(new Runnable() { 873 @Override 874 public void run() { 875 entry.getKey().onConfigurationChanged(); 876 } 877 }); 878 } 879 } 880 881 @Override 882 public void onShowVibrateHint() { 883 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 884 entry.getValue().post(new Runnable() { 885 @Override 886 public void run() { 887 entry.getKey().onShowVibrateHint(); 888 } 889 }); 890 } 891 } 892 893 @Override 894 public void onShowSilentHint() { 895 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 896 entry.getValue().post(new Runnable() { 897 @Override 898 public void run() { 899 entry.getKey().onShowSilentHint(); 900 } 901 }); 902 } 903 } 904 905 @Override 906 public void onScreenOff() { 907 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 908 entry.getValue().post(new Runnable() { 909 @Override 910 public void run() { 911 entry.getKey().onScreenOff(); 912 } 913 }); 914 } 915 } 916 917 @Override 918 public void onShowSafetyWarning(final int flags) { 919 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 920 entry.getValue().post(new Runnable() { 921 @Override 922 public void run() { 923 entry.getKey().onShowSafetyWarning(flags); 924 } 925 }); 926 } 927 } 928 929 @Override 930 public void onAccessibilityModeChanged(Boolean showA11yStream) { 931 boolean show = showA11yStream == null ? false : showA11yStream; 932 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 933 entry.getValue().post(new Runnable() { 934 @Override 935 public void run() { 936 entry.getKey().onAccessibilityModeChanged(show); 937 } 938 }); 939 } 940 } 941 942 @Override 943 public void onCaptionComponentStateChanged( 944 Boolean isComponentEnabled, Boolean fromTooltip) { 945 boolean componentEnabled = isComponentEnabled == null ? false : isComponentEnabled; 946 for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { 947 entry.getValue().post( 948 () -> entry.getKey().onCaptionComponentStateChanged( 949 componentEnabled, fromTooltip)); 950 } 951 } 952 } 953 954 955 private final class SettingObserver extends ContentObserver { 956 private final Uri ZEN_MODE_URI = 957 Settings.Global.getUriFor(Settings.Global.ZEN_MODE); 958 private final Uri ZEN_MODE_CONFIG_URI = 959 Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG); 960 961 public SettingObserver(Handler handler) { 962 super(handler); 963 } 964 965 public void init() { 966 mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this); 967 mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this); 968 } 969 970 public void destroy() { 971 mContext.getContentResolver().unregisterContentObserver(this); 972 } 973 974 @Override 975 public void onChange(boolean selfChange, Uri uri) { 976 boolean changed = false; 977 if (ZEN_MODE_URI.equals(uri)) { 978 changed = updateZenModeW(); 979 } 980 if (ZEN_MODE_CONFIG_URI.equals(uri)) { 981 changed |= updateZenConfig(); 982 } 983 984 if (changed) { 985 mCallbacks.onStateChanged(mState); 986 } 987 } 988 } 989 990 private final class Receiver extends BroadcastReceiver { 991 992 public void init() { 993 final IntentFilter filter = new IntentFilter(); 994 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 995 filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION); 996 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); 997 filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); 998 filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION); 999 filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); 1000 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 1001 filter.addAction(Intent.ACTION_SCREEN_OFF); 1002 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 1003 mContext.registerReceiver(this, filter, null, mWorker); 1004 } 1005 1006 public void destroy() { 1007 mContext.unregisterReceiver(this); 1008 } 1009 1010 @Override 1011 public void onReceive(Context context, Intent intent) { 1012 final String action = intent.getAction(); 1013 boolean changed = false; 1014 if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { 1015 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 1016 final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); 1017 final int oldLevel = intent 1018 .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1); 1019 if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream 1020 + " level=" + level + " oldLevel=" + oldLevel); 1021 changed = updateStreamLevelW(stream, level); 1022 } else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) { 1023 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 1024 final int devices = intent 1025 .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, -1); 1026 final int oldDevices = intent 1027 .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, -1); 1028 if (D.BUG) Log.d(TAG, "onReceive STREAM_DEVICES_CHANGED_ACTION stream=" 1029 + stream + " devices=" + devices + " oldDevices=" + oldDevices); 1030 changed = checkRoutedToBluetoothW(stream); 1031 changed |= onVolumeChangedW(stream, 0); 1032 } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { 1033 final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1); 1034 if (isInitialStickyBroadcast()) mState.ringerModeExternal = rm; 1035 if (D.BUG) Log.d(TAG, "onReceive RINGER_MODE_CHANGED_ACTION rm=" 1036 + Util.ringerModeToString(rm)); 1037 changed = updateRingerModeExternalW(rm); 1038 } else if (action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) { 1039 final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1); 1040 if (isInitialStickyBroadcast()) mState.ringerModeInternal = rm; 1041 if (D.BUG) Log.d(TAG, "onReceive INTERNAL_RINGER_MODE_CHANGED_ACTION rm=" 1042 + Util.ringerModeToString(rm)); 1043 changed = updateRingerModeInternalW(rm); 1044 } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) { 1045 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 1046 final boolean muted = intent 1047 .getBooleanExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false); 1048 if (D.BUG) Log.d(TAG, "onReceive STREAM_MUTE_CHANGED_ACTION stream=" + stream 1049 + " muted=" + muted); 1050 changed = updateStreamMuteW(stream, muted); 1051 } else if (action.equals(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)) { 1052 if (D.BUG) Log.d(TAG, "onReceive ACTION_EFFECTS_SUPPRESSOR_CHANGED"); 1053 changed = updateEffectsSuppressorW(mNoMan.getEffectsSuppressor()); 1054 } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { 1055 if (D.BUG) Log.d(TAG, "onReceive ACTION_CONFIGURATION_CHANGED"); 1056 mCallbacks.onConfigurationChanged(); 1057 } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { 1058 if (D.BUG) Log.d(TAG, "onReceive ACTION_SCREEN_OFF"); 1059 mCallbacks.onScreenOff(); 1060 } else if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) { 1061 if (D.BUG) Log.d(TAG, "onReceive ACTION_CLOSE_SYSTEM_DIALOGS"); 1062 dismiss(); 1063 } 1064 if (changed) { 1065 mCallbacks.onStateChanged(mState); 1066 } 1067 } 1068 } 1069 1070 protected final class MediaSessionsCallbacks implements MediaSessions.Callbacks { 1071 private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>(); 1072 1073 private int mNextStream = DYNAMIC_STREAM_START_INDEX; 1074 1075 @Override 1076 public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) { 1077 addStream(token, "onRemoteUpdate"); 1078 final int stream = mRemoteStreams.get(token); 1079 boolean changed = mState.states.indexOfKey(stream) < 0; 1080 final StreamState ss = streamStateW(stream); 1081 ss.dynamic = true; 1082 ss.levelMin = 0; 1083 ss.levelMax = pi.getMaxVolume(); 1084 if (ss.level != pi.getCurrentVolume()) { 1085 ss.level = pi.getCurrentVolume(); 1086 changed = true; 1087 } 1088 if (!Objects.equals(ss.remoteLabel, name)) { 1089 ss.name = -1; 1090 ss.remoteLabel = name; 1091 changed = true; 1092 } 1093 if (changed) { 1094 if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level 1095 + " of " + ss.levelMax); 1096 mCallbacks.onStateChanged(mState); 1097 } 1098 } 1099 1100 @Override 1101 public void onRemoteVolumeChanged(Token token, int flags) { 1102 addStream(token, "onRemoteVolumeChanged"); 1103 final int stream = mRemoteStreams.get(token); 1104 final boolean showUI = shouldShowUI(flags); 1105 boolean changed = updateActiveStreamW(stream); 1106 if (showUI) { 1107 changed |= checkRoutedToBluetoothW(AudioManager.STREAM_MUSIC); 1108 } 1109 if (changed) { 1110 mCallbacks.onStateChanged(mState); 1111 } 1112 if (showUI) { 1113 mCallbacks.onShowRequested(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED); 1114 } 1115 } 1116 1117 @Override 1118 public void onRemoteRemoved(Token token) { 1119 if (!mRemoteStreams.containsKey(token)) { 1120 if (D.BUG) Log.d(TAG, "onRemoteRemoved: stream doesn't exist, " 1121 + "aborting remote removed for token:" + token.toString()); 1122 return; 1123 } 1124 final int stream = mRemoteStreams.get(token); 1125 mState.states.remove(stream); 1126 if (mState.activeStream == stream) { 1127 updateActiveStreamW(-1); 1128 } 1129 mCallbacks.onStateChanged(mState); 1130 } 1131 1132 public void setStreamVolume(int stream, int level) { 1133 final Token t = findToken(stream); 1134 if (t == null) { 1135 Log.w(TAG, "setStreamVolume: No token found for stream: " + stream); 1136 return; 1137 } 1138 mMediaSessions.setVolume(t, level); 1139 } 1140 1141 private Token findToken(int stream) { 1142 for (Map.Entry<Token, Integer> entry : mRemoteStreams.entrySet()) { 1143 if (entry.getValue().equals(stream)) { 1144 return entry.getKey(); 1145 } 1146 } 1147 return null; 1148 } 1149 1150 private void addStream(Token token, String triggeringMethod) { 1151 if (!mRemoteStreams.containsKey(token)) { 1152 mRemoteStreams.put(token, mNextStream); 1153 if (D.BUG) Log.d(TAG, triggeringMethod + ": added stream " + mNextStream 1154 + " from token + "+ token.toString()); 1155 mNextStream++; 1156 } 1157 } 1158 } 1159 1160 public interface UserActivityListener { 1161 void onUserActivity(); 1162 } 1163 } 1164