1 /* 2 * Copyright (C) 2012 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 android.hardware.display; 18 19 import android.content.Context; 20 import android.content.pm.ParceledListSlice; 21 import android.content.res.Resources; 22 import android.graphics.Point; 23 import android.hardware.display.DisplayManager.DisplayListener; 24 import android.media.projection.IMediaProjection; 25 import android.media.projection.MediaProjection; 26 import android.os.Handler; 27 import android.os.IBinder; 28 import android.os.Looper; 29 import android.os.Message; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.text.TextUtils; 33 import android.util.Log; 34 import android.util.Pair; 35 import android.util.SparseArray; 36 import android.view.Display; 37 import android.view.DisplayAdjustments; 38 import android.view.DisplayInfo; 39 import android.view.Surface; 40 41 import java.util.ArrayList; 42 import java.util.Collections; 43 import java.util.List; 44 45 /** 46 * Manager communication with the display manager service on behalf of 47 * an application process. You're probably looking for {@link DisplayManager}. 48 * 49 * @hide 50 */ 51 public final class DisplayManagerGlobal { 52 private static final String TAG = "DisplayManager"; 53 private static final boolean DEBUG = false; 54 55 // True if display info and display ids should be cached. 56 // 57 // FIXME: The cache is currently disabled because it's unclear whether we have the 58 // necessary guarantees that the caches will always be flushed before clients 59 // attempt to observe their new state. For example, depending on the order 60 // in which the binder transactions take place, we might have a problem where 61 // an application could start processing a configuration change due to a display 62 // orientation change before the display info cache has actually been invalidated. 63 private static final boolean USE_CACHE = false; 64 65 public static final int EVENT_DISPLAY_ADDED = 1; 66 public static final int EVENT_DISPLAY_CHANGED = 2; 67 public static final int EVENT_DISPLAY_REMOVED = 3; 68 69 private static DisplayManagerGlobal sInstance; 70 71 private final Object mLock = new Object(); 72 73 private final IDisplayManager mDm; 74 75 private DisplayManagerCallback mCallback; 76 private final ArrayList<DisplayListenerDelegate> mDisplayListeners = 77 new ArrayList<DisplayListenerDelegate>(); 78 79 private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>(); 80 private int[] mDisplayIdCache; 81 82 private int mWifiDisplayScanNestCount; 83 DisplayManagerGlobal(IDisplayManager dm)84 private DisplayManagerGlobal(IDisplayManager dm) { 85 mDm = dm; 86 } 87 88 /** 89 * Gets an instance of the display manager global singleton. 90 * 91 * @return The display manager instance, may be null early in system startup 92 * before the display manager has been fully initialized. 93 */ getInstance()94 public static DisplayManagerGlobal getInstance() { 95 synchronized (DisplayManagerGlobal.class) { 96 if (sInstance == null) { 97 IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE); 98 if (b != null) { 99 sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b)); 100 } 101 } 102 return sInstance; 103 } 104 } 105 106 /** 107 * Get information about a particular logical display. 108 * 109 * @param displayId The logical display id. 110 * @return Information about the specified display, or null if it does not exist. 111 * This object belongs to an internal cache and should be treated as if it were immutable. 112 */ getDisplayInfo(int displayId)113 public DisplayInfo getDisplayInfo(int displayId) { 114 try { 115 synchronized (mLock) { 116 DisplayInfo info; 117 if (USE_CACHE) { 118 info = mDisplayInfoCache.get(displayId); 119 if (info != null) { 120 return info; 121 } 122 } 123 124 info = mDm.getDisplayInfo(displayId); 125 if (info == null) { 126 return null; 127 } 128 129 if (USE_CACHE) { 130 mDisplayInfoCache.put(displayId, info); 131 } 132 registerCallbackIfNeededLocked(); 133 134 if (DEBUG) { 135 Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info); 136 } 137 return info; 138 } 139 } catch (RemoteException ex) { 140 throw ex.rethrowFromSystemServer(); 141 } 142 } 143 144 /** 145 * Gets all currently valid logical display ids. 146 * 147 * @return An array containing all display ids. 148 */ getDisplayIds()149 public int[] getDisplayIds() { 150 try { 151 synchronized (mLock) { 152 if (USE_CACHE) { 153 if (mDisplayIdCache != null) { 154 return mDisplayIdCache; 155 } 156 } 157 158 int[] displayIds = mDm.getDisplayIds(); 159 if (USE_CACHE) { 160 mDisplayIdCache = displayIds; 161 } 162 registerCallbackIfNeededLocked(); 163 return displayIds; 164 } 165 } catch (RemoteException ex) { 166 throw ex.rethrowFromSystemServer(); 167 } 168 } 169 170 /** 171 * Gets information about a logical display. 172 * 173 * The display metrics may be adjusted to provide compatibility 174 * for legacy applications or limited screen areas. 175 * 176 * @param displayId The logical display id. 177 * @param daj The compatibility info and activityToken. 178 * @return The display object, or null if there is no display with the given id. 179 */ getCompatibleDisplay(int displayId, DisplayAdjustments daj)180 public Display getCompatibleDisplay(int displayId, DisplayAdjustments daj) { 181 DisplayInfo displayInfo = getDisplayInfo(displayId); 182 if (displayInfo == null) { 183 return null; 184 } 185 return new Display(this, displayId, displayInfo, daj); 186 } 187 188 /** 189 * Gets information about a logical display. 190 * 191 * The display metrics may be adjusted to provide compatibility 192 * for legacy applications or limited screen areas. 193 * 194 * @param displayId The logical display id. 195 * @param resources Resources providing compatibility info. 196 * @return The display object, or null if there is no display with the given id. 197 */ getCompatibleDisplay(int displayId, Resources resources)198 public Display getCompatibleDisplay(int displayId, Resources resources) { 199 DisplayInfo displayInfo = getDisplayInfo(displayId); 200 if (displayInfo == null) { 201 return null; 202 } 203 return new Display(this, displayId, displayInfo, resources); 204 } 205 206 /** 207 * Gets information about a logical display without applying any compatibility metrics. 208 * 209 * @param displayId The logical display id. 210 * @return The display object, or null if there is no display with the given id. 211 */ getRealDisplay(int displayId)212 public Display getRealDisplay(int displayId) { 213 return getCompatibleDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); 214 } 215 registerDisplayListener(DisplayListener listener, Handler handler)216 public void registerDisplayListener(DisplayListener listener, Handler handler) { 217 if (listener == null) { 218 throw new IllegalArgumentException("listener must not be null"); 219 } 220 221 synchronized (mLock) { 222 int index = findDisplayListenerLocked(listener); 223 if (index < 0) { 224 mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); 225 registerCallbackIfNeededLocked(); 226 } 227 } 228 } 229 unregisterDisplayListener(DisplayListener listener)230 public void unregisterDisplayListener(DisplayListener listener) { 231 if (listener == null) { 232 throw new IllegalArgumentException("listener must not be null"); 233 } 234 235 synchronized (mLock) { 236 int index = findDisplayListenerLocked(listener); 237 if (index >= 0) { 238 DisplayListenerDelegate d = mDisplayListeners.get(index); 239 d.clearEvents(); 240 mDisplayListeners.remove(index); 241 } 242 } 243 } 244 findDisplayListenerLocked(DisplayListener listener)245 private int findDisplayListenerLocked(DisplayListener listener) { 246 final int numListeners = mDisplayListeners.size(); 247 for (int i = 0; i < numListeners; i++) { 248 if (mDisplayListeners.get(i).mListener == listener) { 249 return i; 250 } 251 } 252 return -1; 253 } 254 registerCallbackIfNeededLocked()255 private void registerCallbackIfNeededLocked() { 256 if (mCallback == null) { 257 mCallback = new DisplayManagerCallback(); 258 try { 259 mDm.registerCallback(mCallback); 260 } catch (RemoteException ex) { 261 throw ex.rethrowFromSystemServer(); 262 } 263 } 264 } 265 handleDisplayEvent(int displayId, int event)266 private void handleDisplayEvent(int displayId, int event) { 267 synchronized (mLock) { 268 if (USE_CACHE) { 269 mDisplayInfoCache.remove(displayId); 270 271 if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) { 272 mDisplayIdCache = null; 273 } 274 } 275 276 final int numListeners = mDisplayListeners.size(); 277 for (int i = 0; i < numListeners; i++) { 278 mDisplayListeners.get(i).sendDisplayEvent(displayId, event); 279 } 280 } 281 } 282 startWifiDisplayScan()283 public void startWifiDisplayScan() { 284 synchronized (mLock) { 285 if (mWifiDisplayScanNestCount++ == 0) { 286 registerCallbackIfNeededLocked(); 287 try { 288 mDm.startWifiDisplayScan(); 289 } catch (RemoteException ex) { 290 throw ex.rethrowFromSystemServer(); 291 } 292 } 293 } 294 } 295 stopWifiDisplayScan()296 public void stopWifiDisplayScan() { 297 synchronized (mLock) { 298 if (--mWifiDisplayScanNestCount == 0) { 299 try { 300 mDm.stopWifiDisplayScan(); 301 } catch (RemoteException ex) { 302 throw ex.rethrowFromSystemServer(); 303 } 304 } else if (mWifiDisplayScanNestCount < 0) { 305 Log.wtf(TAG, "Wifi display scan nest count became negative: " 306 + mWifiDisplayScanNestCount); 307 mWifiDisplayScanNestCount = 0; 308 } 309 } 310 } 311 connectWifiDisplay(String deviceAddress)312 public void connectWifiDisplay(String deviceAddress) { 313 if (deviceAddress == null) { 314 throw new IllegalArgumentException("deviceAddress must not be null"); 315 } 316 317 try { 318 mDm.connectWifiDisplay(deviceAddress); 319 } catch (RemoteException ex) { 320 throw ex.rethrowFromSystemServer(); 321 } 322 } 323 pauseWifiDisplay()324 public void pauseWifiDisplay() { 325 try { 326 mDm.pauseWifiDisplay(); 327 } catch (RemoteException ex) { 328 throw ex.rethrowFromSystemServer(); 329 } 330 } 331 resumeWifiDisplay()332 public void resumeWifiDisplay() { 333 try { 334 mDm.resumeWifiDisplay(); 335 } catch (RemoteException ex) { 336 throw ex.rethrowFromSystemServer(); 337 } 338 } 339 disconnectWifiDisplay()340 public void disconnectWifiDisplay() { 341 try { 342 mDm.disconnectWifiDisplay(); 343 } catch (RemoteException ex) { 344 throw ex.rethrowFromSystemServer(); 345 } 346 } 347 renameWifiDisplay(String deviceAddress, String alias)348 public void renameWifiDisplay(String deviceAddress, String alias) { 349 if (deviceAddress == null) { 350 throw new IllegalArgumentException("deviceAddress must not be null"); 351 } 352 353 try { 354 mDm.renameWifiDisplay(deviceAddress, alias); 355 } catch (RemoteException ex) { 356 throw ex.rethrowFromSystemServer(); 357 } 358 } 359 forgetWifiDisplay(String deviceAddress)360 public void forgetWifiDisplay(String deviceAddress) { 361 if (deviceAddress == null) { 362 throw new IllegalArgumentException("deviceAddress must not be null"); 363 } 364 365 try { 366 mDm.forgetWifiDisplay(deviceAddress); 367 } catch (RemoteException ex) { 368 throw ex.rethrowFromSystemServer(); 369 } 370 } 371 getWifiDisplayStatus()372 public WifiDisplayStatus getWifiDisplayStatus() { 373 try { 374 return mDm.getWifiDisplayStatus(); 375 } catch (RemoteException ex) { 376 throw ex.rethrowFromSystemServer(); 377 } 378 } 379 requestColorMode(int displayId, int colorMode)380 public void requestColorMode(int displayId, int colorMode) { 381 try { 382 mDm.requestColorMode(displayId, colorMode); 383 } catch (RemoteException ex) { 384 throw ex.rethrowFromSystemServer(); 385 } 386 } 387 388 /** 389 * Set the level of color saturation to apply to the display. 390 */ setSaturationLevel(float level)391 public void setSaturationLevel(float level) { 392 try { 393 mDm.setSaturationLevel(level); 394 } catch (RemoteException ex) { 395 throw ex.rethrowFromSystemServer(); 396 } 397 } 398 createVirtualDisplay(Context context, MediaProjection projection, String name, int width, int height, int densityDpi, Surface surface, int flags, VirtualDisplay.Callback callback, Handler handler, String uniqueId)399 public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection, 400 String name, int width, int height, int densityDpi, Surface surface, int flags, 401 VirtualDisplay.Callback callback, Handler handler, String uniqueId) { 402 if (TextUtils.isEmpty(name)) { 403 throw new IllegalArgumentException("name must be non-null and non-empty"); 404 } 405 if (width <= 0 || height <= 0 || densityDpi <= 0) { 406 throw new IllegalArgumentException("width, height, and densityDpi must be " 407 + "greater than 0"); 408 } 409 410 VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler); 411 IMediaProjection projectionToken = projection != null ? projection.getProjection() : null; 412 int displayId; 413 try { 414 displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken, 415 context.getPackageName(), name, width, height, densityDpi, surface, flags, 416 uniqueId); 417 } catch (RemoteException ex) { 418 throw ex.rethrowFromSystemServer(); 419 } 420 if (displayId < 0) { 421 Log.e(TAG, "Could not create virtual display: " + name); 422 return null; 423 } 424 Display display = getRealDisplay(displayId); 425 if (display == null) { 426 Log.wtf(TAG, "Could not obtain display info for newly created " 427 + "virtual display: " + name); 428 try { 429 mDm.releaseVirtualDisplay(callbackWrapper); 430 } catch (RemoteException ex) { 431 throw ex.rethrowFromSystemServer(); 432 } 433 return null; 434 } 435 return new VirtualDisplay(this, display, callbackWrapper, surface); 436 } 437 setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface)438 public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) { 439 try { 440 mDm.setVirtualDisplaySurface(token, surface); 441 } catch (RemoteException ex) { 442 throw ex.rethrowFromSystemServer(); 443 } 444 } 445 resizeVirtualDisplay(IVirtualDisplayCallback token, int width, int height, int densityDpi)446 public void resizeVirtualDisplay(IVirtualDisplayCallback token, 447 int width, int height, int densityDpi) { 448 try { 449 mDm.resizeVirtualDisplay(token, width, height, densityDpi); 450 } catch (RemoteException ex) { 451 throw ex.rethrowFromSystemServer(); 452 } 453 } 454 releaseVirtualDisplay(IVirtualDisplayCallback token)455 public void releaseVirtualDisplay(IVirtualDisplayCallback token) { 456 try { 457 mDm.releaseVirtualDisplay(token); 458 } catch (RemoteException ex) { 459 throw ex.rethrowFromSystemServer(); 460 } 461 } 462 463 /** 464 * Gets the stable device display size, in pixels. 465 */ getStableDisplaySize()466 public Point getStableDisplaySize() { 467 try { 468 return mDm.getStableDisplaySize(); 469 } catch (RemoteException ex) { 470 throw ex.rethrowFromSystemServer(); 471 } 472 } 473 474 /** 475 * Retrieves brightness change events. 476 */ getBrightnessEvents(String callingPackage)477 public List<BrightnessChangeEvent> getBrightnessEvents(String callingPackage) { 478 try { 479 ParceledListSlice<BrightnessChangeEvent> events = 480 mDm.getBrightnessEvents(callingPackage); 481 if (events == null) { 482 return Collections.emptyList(); 483 } 484 return events.getList(); 485 } catch (RemoteException ex) { 486 throw ex.rethrowFromSystemServer(); 487 } 488 } 489 490 /** 491 * Sets the global brightness configuration for a given user. 492 * 493 * @hide 494 */ setBrightnessConfigurationForUser(BrightnessConfiguration c, int userId, String packageName)495 public void setBrightnessConfigurationForUser(BrightnessConfiguration c, int userId, 496 String packageName) { 497 try { 498 mDm.setBrightnessConfigurationForUser(c, userId, packageName); 499 } catch (RemoteException ex) { 500 throw ex.rethrowFromSystemServer(); 501 } 502 } 503 504 /** 505 * Gets the global brightness configuration for a given user or null if one hasn't been set. 506 * 507 * @hide 508 */ getBrightnessConfigurationForUser(int userId)509 public BrightnessConfiguration getBrightnessConfigurationForUser(int userId) { 510 try { 511 return mDm.getBrightnessConfigurationForUser(userId); 512 } catch (RemoteException ex) { 513 throw ex.rethrowFromSystemServer(); 514 } 515 } 516 517 /** 518 * Gets the default brightness configuration or null if one hasn't been configured. 519 * 520 * @hide 521 */ getDefaultBrightnessConfiguration()522 public BrightnessConfiguration getDefaultBrightnessConfiguration() { 523 try { 524 return mDm.getDefaultBrightnessConfiguration(); 525 } catch (RemoteException ex) { 526 throw ex.rethrowFromSystemServer(); 527 } 528 } 529 530 /** 531 * Temporarily sets the brightness of the display. 532 * <p> 533 * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. 534 * </p> 535 * 536 * @param brightness The brightness value from 0 to 255. 537 * 538 * @hide Requires signature permission. 539 */ setTemporaryBrightness(int brightness)540 public void setTemporaryBrightness(int brightness) { 541 try { 542 mDm.setTemporaryBrightness(brightness); 543 } catch (RemoteException ex) { 544 throw ex.rethrowFromSystemServer(); 545 } 546 } 547 548 /** 549 * Temporarily sets the auto brightness adjustment factor. 550 * <p> 551 * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. 552 * </p> 553 * 554 * @param adjustment The adjustment factor from -1.0 to 1.0. 555 * 556 * @hide Requires signature permission. 557 */ setTemporaryAutoBrightnessAdjustment(float adjustment)558 public void setTemporaryAutoBrightnessAdjustment(float adjustment) { 559 try { 560 mDm.setTemporaryAutoBrightnessAdjustment(adjustment); 561 } catch (RemoteException ex) { 562 throw ex.rethrowFromSystemServer(); 563 } 564 } 565 566 /** 567 * Returns the minimum brightness curve, which guarantess that any brightness curve that dips 568 * below it is rejected by the system. 569 * This prevent auto-brightness from setting the screen so dark as to prevent the user from 570 * resetting or disabling it, and maps lux to the absolute minimum nits that are still readable 571 * in that ambient brightness. 572 * 573 * @return The minimum brightness curve (as lux values and their corresponding nits values). 574 */ getMinimumBrightnessCurve()575 public Pair<float[], float[]> getMinimumBrightnessCurve() { 576 try { 577 Curve curve = mDm.getMinimumBrightnessCurve(); 578 return Pair.create(curve.getX(), curve.getY()); 579 } catch (RemoteException ex) { 580 throw ex.rethrowFromSystemServer(); 581 } 582 } 583 584 /** 585 * Retrieves ambient brightness stats. 586 */ getAmbientBrightnessStats()587 public List<AmbientBrightnessDayStats> getAmbientBrightnessStats() { 588 try { 589 ParceledListSlice<AmbientBrightnessDayStats> stats = mDm.getAmbientBrightnessStats(); 590 if (stats == null) { 591 return Collections.emptyList(); 592 } 593 return stats.getList(); 594 } catch (RemoteException ex) { 595 throw ex.rethrowFromSystemServer(); 596 } 597 } 598 599 private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { 600 @Override onDisplayEvent(int displayId, int event)601 public void onDisplayEvent(int displayId, int event) { 602 if (DEBUG) { 603 Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event); 604 } 605 handleDisplayEvent(displayId, event); 606 } 607 } 608 609 private static final class DisplayListenerDelegate extends Handler { 610 public final DisplayListener mListener; 611 DisplayListenerDelegate(DisplayListener listener, Handler handler)612 public DisplayListenerDelegate(DisplayListener listener, Handler handler) { 613 super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/); 614 mListener = listener; 615 } 616 sendDisplayEvent(int displayId, int event)617 public void sendDisplayEvent(int displayId, int event) { 618 Message msg = obtainMessage(event, displayId, 0); 619 sendMessage(msg); 620 } 621 clearEvents()622 public void clearEvents() { 623 removeCallbacksAndMessages(null); 624 } 625 626 @Override handleMessage(Message msg)627 public void handleMessage(Message msg) { 628 switch (msg.what) { 629 case EVENT_DISPLAY_ADDED: 630 mListener.onDisplayAdded(msg.arg1); 631 break; 632 case EVENT_DISPLAY_CHANGED: 633 mListener.onDisplayChanged(msg.arg1); 634 break; 635 case EVENT_DISPLAY_REMOVED: 636 mListener.onDisplayRemoved(msg.arg1); 637 break; 638 } 639 } 640 } 641 642 private final static class VirtualDisplayCallback extends IVirtualDisplayCallback.Stub { 643 private VirtualDisplayCallbackDelegate mDelegate; 644 VirtualDisplayCallback(VirtualDisplay.Callback callback, Handler handler)645 public VirtualDisplayCallback(VirtualDisplay.Callback callback, Handler handler) { 646 if (callback != null) { 647 mDelegate = new VirtualDisplayCallbackDelegate(callback, handler); 648 } 649 } 650 651 @Override // Binder call onPaused()652 public void onPaused() { 653 if (mDelegate != null) { 654 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_PAUSED); 655 } 656 } 657 658 @Override // Binder call onResumed()659 public void onResumed() { 660 if (mDelegate != null) { 661 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_RESUMED); 662 } 663 } 664 665 @Override // Binder call onStopped()666 public void onStopped() { 667 if (mDelegate != null) { 668 mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_STOPPED); 669 } 670 } 671 } 672 673 private final static class VirtualDisplayCallbackDelegate extends Handler { 674 public static final int MSG_DISPLAY_PAUSED = 0; 675 public static final int MSG_DISPLAY_RESUMED = 1; 676 public static final int MSG_DISPLAY_STOPPED = 2; 677 678 private final VirtualDisplay.Callback mCallback; 679 VirtualDisplayCallbackDelegate(VirtualDisplay.Callback callback, Handler handler)680 public VirtualDisplayCallbackDelegate(VirtualDisplay.Callback callback, 681 Handler handler) { 682 super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/); 683 mCallback = callback; 684 } 685 686 @Override handleMessage(Message msg)687 public void handleMessage(Message msg) { 688 switch (msg.what) { 689 case MSG_DISPLAY_PAUSED: 690 mCallback.onPaused(); 691 break; 692 case MSG_DISPLAY_RESUMED: 693 mCallback.onResumed(); 694 break; 695 case MSG_DISPLAY_STOPPED: 696 mCallback.onStopped(); 697 break; 698 } 699 } 700 } 701 } 702