1 /* 2 * Copyright (C) 2023 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.statusbar; 18 19 import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; 20 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; 21 import static android.app.StatusBarManager.DISABLE_BACK; 22 import static android.app.StatusBarManager.DISABLE_HOME; 23 import static android.app.StatusBarManager.DISABLE_RECENT; 24 import static android.view.Display.DEFAULT_DISPLAY; 25 import static android.view.ViewRootImpl.CLIENT_IMMERSIVE_CONFIRMATION; 26 import static android.view.ViewRootImpl.CLIENT_TRANSIENT; 27 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 28 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; 29 import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID; 30 31 import android.animation.ArgbEvaluator; 32 import android.animation.ValueAnimator; 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.app.ActivityManager; 36 import android.content.BroadcastReceiver; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.IntentFilter; 40 import android.content.res.Resources; 41 import android.database.ContentObserver; 42 import android.graphics.Insets; 43 import android.graphics.PixelFormat; 44 import android.graphics.Rect; 45 import android.graphics.drawable.ColorDrawable; 46 import android.os.Binder; 47 import android.os.Bundle; 48 import android.os.Handler; 49 import android.os.IBinder; 50 import android.os.Looper; 51 import android.os.Message; 52 import android.os.RemoteException; 53 import android.os.ServiceManager; 54 import android.os.UserHandle; 55 import android.os.UserManager; 56 import android.provider.Settings; 57 import android.service.vr.IVrManager; 58 import android.service.vr.IVrStateCallbacks; 59 import android.util.DisplayMetrics; 60 import android.util.Log; 61 import android.view.Display; 62 import android.view.Gravity; 63 import android.view.MotionEvent; 64 import android.view.View; 65 import android.view.ViewGroup; 66 import android.view.ViewTreeObserver; 67 import android.view.WindowInsets; 68 import android.view.WindowInsets.Type; 69 import android.view.WindowManager; 70 import android.view.animation.AnimationUtils; 71 import android.view.animation.Interpolator; 72 import android.widget.Button; 73 import android.widget.FrameLayout; 74 import android.widget.RelativeLayout; 75 76 import com.android.systemui.CoreStartable; 77 import com.android.systemui.res.R; 78 import com.android.systemui.shared.system.TaskStackChangeListener; 79 import com.android.systemui.shared.system.TaskStackChangeListeners; 80 import com.android.systemui.util.settings.SecureSettings; 81 82 import javax.inject.Inject; 83 84 /** 85 * Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden 86 * entering immersive mode. 87 */ 88 public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Callbacks, 89 TaskStackChangeListener { 90 private static final String TAG = "ImmersiveModeConfirm"; 91 private static final boolean DEBUG = false; 92 private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution 93 private static final String CONFIRMED = "confirmed"; 94 private static final int IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE = 95 WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; 96 97 private static boolean sConfirmed; 98 private final SecureSettings mSecureSettings; 99 100 private Context mDisplayContext; 101 private final Context mSysUiContext; 102 private final Handler mHandler = new H(Looper.getMainLooper()); 103 private long mShowDelayMs = 0L; 104 private final IBinder mWindowToken = new Binder(); 105 private final CommandQueue mCommandQueue; 106 107 private ClingWindowView mClingWindow; 108 /** The last {@link WindowManager} that is used to add the confirmation window. */ 109 @Nullable 110 private WindowManager mWindowManager; 111 /** 112 * The WindowContext that is registered with {@link #mWindowManager} with options to specify the 113 * {@link RootDisplayArea} to attach the confirmation window. 114 */ 115 @Nullable 116 private Context mWindowContext; 117 /** 118 * The root display area feature id that the {@link #mWindowContext} is attaching to. 119 */ 120 private int mWindowContextRootDisplayAreaId = FEATURE_UNDEFINED; 121 // Local copy of vr mode enabled state, to avoid calling into VrManager with 122 // the lock held. 123 private boolean mVrModeEnabled = false; 124 private boolean mCanSystemBarsBeShownByUser = true; 125 private int mLockTaskState = LOCK_TASK_MODE_NONE; 126 private boolean mNavBarEmpty; 127 128 private ContentObserver mContentObserver; 129 130 @Inject ImmersiveModeConfirmation(Context context, CommandQueue commandQueue, SecureSettings secureSettings)131 public ImmersiveModeConfirmation(Context context, CommandQueue commandQueue, 132 SecureSettings secureSettings) { 133 mSysUiContext = context; 134 final Display display = mSysUiContext.getDisplay(); 135 mDisplayContext = display.getDisplayId() == DEFAULT_DISPLAY 136 ? mSysUiContext : mSysUiContext.createDisplayContext(display); 137 mCommandQueue = commandQueue; 138 mSecureSettings = secureSettings; 139 } 140 loadSetting(int currentUserId)141 boolean loadSetting(int currentUserId) { 142 final boolean wasConfirmed = sConfirmed; 143 sConfirmed = false; 144 if (DEBUG) Log.d(TAG, String.format("loadSetting() currentUserId=%d", currentUserId)); 145 String value = null; 146 try { 147 value = mSecureSettings.getStringForUser(Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, 148 UserHandle.USER_CURRENT); 149 sConfirmed = CONFIRMED.equals(value); 150 if (DEBUG) Log.d(TAG, "Loaded sConfirmed=" + sConfirmed); 151 } catch (Throwable t) { 152 Log.w(TAG, "Error loading confirmations, value=" + value, t); 153 } 154 return sConfirmed != wasConfirmed; 155 } 156 saveSetting(Context context)157 private static void saveSetting(Context context) { 158 if (DEBUG) Log.d(TAG, "saveSetting()"); 159 try { 160 final String value = sConfirmed ? CONFIRMED : null; 161 Settings.Secure.putStringForUser(context.getContentResolver(), 162 Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, 163 value, 164 UserHandle.USER_CURRENT); 165 if (DEBUG) Log.d(TAG, "Saved value=" + value); 166 } catch (Throwable t) { 167 Log.w(TAG, "Error saving confirmations, sConfirmed=" + sConfirmed, t); 168 } 169 } 170 171 @Override onDisplayRemoved(int displayId)172 public void onDisplayRemoved(int displayId) { 173 if (displayId != mSysUiContext.getDisplayId()) { 174 return; 175 } 176 mHandler.removeMessages(H.SHOW); 177 mHandler.removeMessages(H.HIDE); 178 IVrManager vrManager = IVrManager.Stub.asInterface( 179 ServiceManager.getService(Context.VR_SERVICE)); 180 if (vrManager != null) { 181 try { 182 vrManager.unregisterListener(mVrStateCallbacks); 183 } catch (RemoteException ex) { 184 } 185 } 186 mCommandQueue.removeCallback(this); 187 } 188 onSettingChanged(int currentUserId)189 private void onSettingChanged(int currentUserId) { 190 final boolean changed = loadSetting(currentUserId); 191 // Remove the window if the setting changes to be confirmed. 192 if (changed && sConfirmed) { 193 mHandler.sendEmptyMessage(H.HIDE); 194 } 195 } 196 197 @Override immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode)198 public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) { 199 mHandler.removeMessages(H.SHOW); 200 if (isImmersiveMode) { 201 if (DEBUG) Log.d(TAG, "immersiveModeChanged() sConfirmed=" + sConfirmed); 202 boolean userSetupComplete = (mSecureSettings.getIntForUser( 203 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0); 204 205 if ((DEBUG_SHOW_EVERY_TIME || !sConfirmed) 206 && userSetupComplete 207 && !mVrModeEnabled 208 && mCanSystemBarsBeShownByUser 209 && !mNavBarEmpty 210 && !UserManager.isDeviceInDemoMode(mDisplayContext) 211 && (mLockTaskState != LOCK_TASK_MODE_LOCKED)) { 212 final Message msg = mHandler.obtainMessage( 213 H.SHOW); 214 msg.arg1 = rootDisplayAreaId; 215 mHandler.sendMessageDelayed(msg, mShowDelayMs); 216 } 217 } else { 218 mHandler.sendEmptyMessage(H.HIDE); 219 } 220 } 221 222 @Override disable(int displayId, int disableFlag, int disableFlag2, boolean animate)223 public void disable(int displayId, int disableFlag, int disableFlag2, boolean animate) { 224 if (mSysUiContext.getDisplayId() != displayId) { 225 return; 226 } 227 final int disableNavigationBar = (DISABLE_HOME | DISABLE_BACK | DISABLE_RECENT); 228 mNavBarEmpty = (disableFlag & disableNavigationBar) == disableNavigationBar; 229 } 230 231 @Override confirmImmersivePrompt()232 public void confirmImmersivePrompt() { 233 if (mClingWindow != null) { 234 if (DEBUG) Log.d(TAG, "confirmImmersivePrompt()"); 235 mHandler.post(mConfirm); 236 } 237 } 238 handleHide()239 private void handleHide() { 240 if (mClingWindow != null) { 241 if (DEBUG) Log.d(TAG, "Hiding immersive mode confirmation"); 242 if (mWindowManager != null) { 243 try { 244 mWindowManager.removeView(mClingWindow); 245 } catch (WindowManager.InvalidDisplayException e) { 246 Log.w(TAG, "Fail to hide the immersive confirmation window because of " 247 + e); 248 } 249 mWindowManager = null; 250 mWindowContext = null; 251 } 252 mClingWindow = null; 253 } 254 } 255 getClingWindowLayoutParams()256 private WindowManager.LayoutParams getClingWindowLayoutParams() { 257 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 258 ViewGroup.LayoutParams.MATCH_PARENT, 259 ViewGroup.LayoutParams.MATCH_PARENT, 260 IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, 261 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 262 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED 263 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, 264 PixelFormat.TRANSLUCENT); 265 lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars()); 266 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 267 // Trusted overlay so touches outside the touchable area are allowed to pass through 268 lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS 269 | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY 270 | WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW; 271 lp.setTitle("ImmersiveModeConfirmation"); 272 lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation; 273 lp.token = getWindowToken(); 274 return lp; 275 } 276 getBubbleLayoutParams()277 private FrameLayout.LayoutParams getBubbleLayoutParams() { 278 return new FrameLayout.LayoutParams( 279 getClingWindowWidth(), 280 ViewGroup.LayoutParams.WRAP_CONTENT, 281 Gravity.CENTER_HORIZONTAL | Gravity.TOP); 282 } 283 284 /** 285 * Returns the width of the cling window. 286 */ getClingWindowWidth()287 private int getClingWindowWidth() { 288 return mSysUiContext.getResources().getDimensionPixelSize( 289 R.dimen.immersive_mode_cling_width); 290 } 291 292 /** 293 * @return the window token that's used by all ImmersiveModeConfirmation windows. 294 */ getWindowToken()295 IBinder getWindowToken() { 296 return mWindowToken; 297 } 298 299 @Override start()300 public void start() { 301 if (CLIENT_TRANSIENT || CLIENT_IMMERSIVE_CONFIRMATION) { 302 mCommandQueue.addCallback(this); 303 304 final Resources r = mSysUiContext.getResources(); 305 mShowDelayMs = r.getInteger(R.integer.dock_enter_exit_duration) * 3L; 306 mCanSystemBarsBeShownByUser = !r.getBoolean( 307 R.bool.config_remoteInsetsControllerControlsSystemBars) || r.getBoolean( 308 R.bool.config_remoteInsetsControllerSystemBarsCanBeShownByUserAction); 309 IVrManager vrManager = IVrManager.Stub.asInterface( 310 ServiceManager.getService(Context.VR_SERVICE)); 311 if (vrManager != null) { 312 try { 313 mVrModeEnabled = vrManager.getVrModeState(); 314 vrManager.registerListener(mVrStateCallbacks); 315 mVrStateCallbacks.onVrStateChanged(mVrModeEnabled); 316 } catch (RemoteException e) { 317 // Ignore, we cannot do anything if we failed to access vr manager. 318 } 319 } 320 TaskStackChangeListeners.getInstance().registerTaskStackListener(this); 321 mContentObserver = new ContentObserver(mHandler) { 322 @Override 323 public void onChange(boolean selfChange) { 324 onSettingChanged(mSysUiContext.getUserId()); 325 } 326 }; 327 328 // Register to listen for changes in Settings.Secure settings. 329 mSecureSettings.registerContentObserverForUserSync( 330 Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, mContentObserver, 331 UserHandle.USER_CURRENT); 332 mSecureSettings.registerContentObserverForUserSync( 333 Settings.Secure.USER_SETUP_COMPLETE, mContentObserver, 334 UserHandle.USER_CURRENT); 335 } 336 } 337 338 private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { 339 @Override 340 public void onVrStateChanged(boolean enabled) { 341 mVrModeEnabled = enabled; 342 if (mVrModeEnabled) { 343 mHandler.removeMessages(H.SHOW); 344 mHandler.sendEmptyMessage(H.HIDE); 345 } 346 } 347 }; 348 349 private class ClingWindowView extends FrameLayout { 350 private static final int BGCOLOR = 0x80000000; 351 private static final int OFFSET_DP = 96; 352 private static final int ANIMATION_DURATION = 250; 353 354 private final Runnable mConfirm; 355 private final ColorDrawable mColor = new ColorDrawable(0); 356 private final Interpolator mInterpolator; 357 private ValueAnimator mColorAnim; 358 private ViewGroup mClingLayout; 359 360 private Runnable mUpdateLayoutRunnable = new Runnable() { 361 @Override 362 public void run() { 363 if (mClingLayout != null && mClingLayout.getParent() != null) { 364 mClingLayout.setLayoutParams(getBubbleLayoutParams()); 365 } 366 } 367 }; 368 369 private ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = 370 new ViewTreeObserver.OnComputeInternalInsetsListener() { 371 private final int[] mTmpInt2 = new int[2]; 372 373 @Override 374 public void onComputeInternalInsets( 375 ViewTreeObserver.InternalInsetsInfo inoutInfo) { 376 // Set touchable region to cover the cling layout. 377 mClingLayout.getLocationInWindow(mTmpInt2); 378 inoutInfo.setTouchableInsets( 379 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 380 inoutInfo.touchableRegion.set( 381 mTmpInt2[0], 382 mTmpInt2[1], 383 mTmpInt2[0] + mClingLayout.getWidth(), 384 mTmpInt2[1] + mClingLayout.getHeight()); 385 } 386 }; 387 388 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 389 @Override 390 public void onReceive(Context context, Intent intent) { 391 if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) { 392 post(mUpdateLayoutRunnable); 393 } 394 } 395 }; 396 ClingWindowView(Context context, Runnable confirm)397 ClingWindowView(Context context, Runnable confirm) { 398 super(context); 399 mConfirm = confirm; 400 setBackground(mColor); 401 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 402 mInterpolator = AnimationUtils 403 .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in); 404 } 405 406 @Override onAttachedToWindow()407 public void onAttachedToWindow() { 408 super.onAttachedToWindow(); 409 410 DisplayMetrics metrics = new DisplayMetrics(); 411 mContext.getDisplay().getMetrics(metrics); 412 float density = metrics.density; 413 414 getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener); 415 416 // create the confirmation cling 417 mClingLayout = (ViewGroup) 418 View.inflate(mSysUiContext, R.layout.immersive_mode_cling, null); 419 420 final Button ok = mClingLayout.findViewById(R.id.ok); 421 ok.setOnClickListener(new OnClickListener() { 422 @Override 423 public void onClick(View v) { 424 mConfirm.run(); 425 } 426 }); 427 addView(mClingLayout, getBubbleLayoutParams()); 428 429 if (ActivityManager.isHighEndGfx()) { 430 final View cling = mClingLayout; 431 cling.setAlpha(0f); 432 cling.setTranslationY(-OFFSET_DP * density); 433 434 postOnAnimation(new Runnable() { 435 @Override 436 public void run() { 437 cling.animate() 438 .alpha(1f) 439 .translationY(0) 440 .setDuration(ANIMATION_DURATION) 441 .setInterpolator(mInterpolator) 442 .withLayer() 443 .start(); 444 445 mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR); 446 mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 447 @Override 448 public void onAnimationUpdate(ValueAnimator animation) { 449 final int c = (Integer) animation.getAnimatedValue(); 450 mColor.setColor(c); 451 } 452 }); 453 mColorAnim.setDuration(ANIMATION_DURATION); 454 mColorAnim.setInterpolator(mInterpolator); 455 mColorAnim.start(); 456 } 457 }); 458 } else { 459 mColor.setColor(BGCOLOR); 460 } 461 462 mContext.registerReceiver(mReceiver, 463 new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)); 464 } 465 466 @Override onDetachedFromWindow()467 public void onDetachedFromWindow() { 468 mContext.unregisterReceiver(mReceiver); 469 } 470 471 @Override onTouchEvent(MotionEvent motion)472 public boolean onTouchEvent(MotionEvent motion) { 473 return true; 474 } 475 476 @Override onApplyWindowInsets(WindowInsets insets)477 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 478 // If the top display cutout overlaps with the full-width (windowWidth=-1)/centered 479 // dialog, then adjust the dialog contents by the cutout 480 final int width = getWidth(); 481 final int windowWidth = getClingWindowWidth(); 482 final Rect topDisplayCutout = insets.getDisplayCutout() != null 483 ? insets.getDisplayCutout().getBoundingRectTop() 484 : new Rect(); 485 final boolean intersectsTopCutout = topDisplayCutout.intersects( 486 width - (windowWidth / 2), 0, 487 width + (windowWidth / 2), topDisplayCutout.bottom); 488 if (mClingWindow != null && 489 (windowWidth < 0 || (width > 0 && intersectsTopCutout))) { 490 final View iconView = mClingWindow.findViewById(R.id.immersive_cling_icon); 491 RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) 492 iconView.getLayoutParams(); 493 lp.topMargin = topDisplayCutout.bottom; 494 iconView.setLayoutParams(lp); 495 } 496 // we will be hiding the nav bar, so layout as if it's already hidden 497 return new WindowInsets.Builder(insets).setInsets( 498 Type.systemBars(), Insets.NONE).build(); 499 } 500 } 501 502 /** 503 * To get window manager for the display. 504 * 505 * @return the WindowManager specifying with the {@code rootDisplayAreaId} to attach the 506 * confirmation window. 507 */ 508 @NonNull createWindowManager(int rootDisplayAreaId)509 private WindowManager createWindowManager(int rootDisplayAreaId) { 510 if (mWindowManager != null) { 511 throw new IllegalStateException( 512 "Must not create a new WindowManager while there is an existing one"); 513 } 514 // Create window context to specify the RootDisplayArea 515 final Bundle options = getOptionsForWindowContext(rootDisplayAreaId); 516 mWindowContextRootDisplayAreaId = rootDisplayAreaId; 517 mWindowContext = mDisplayContext.createWindowContext( 518 IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options); 519 mWindowManager = mWindowContext.getSystemService(WindowManager.class); 520 return mWindowManager; 521 } 522 523 /** 524 * Returns options that specify the {@link RootDisplayArea} to attach the confirmation window. 525 * {@code null} if the {@code rootDisplayAreaId} is {@link FEATURE_UNDEFINED}. 526 */ 527 @Nullable getOptionsForWindowContext(int rootDisplayAreaId)528 private Bundle getOptionsForWindowContext(int rootDisplayAreaId) { 529 // In case we don't care which root display area the window manager is specifying. 530 if (rootDisplayAreaId == FEATURE_UNDEFINED) { 531 return null; 532 } 533 534 final Bundle options = new Bundle(); 535 options.putInt(KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId); 536 return options; 537 } 538 handleShow(int rootDisplayAreaId)539 private void handleShow(int rootDisplayAreaId) { 540 if (mClingWindow != null) { 541 if (rootDisplayAreaId == mWindowContextRootDisplayAreaId) { 542 if (DEBUG) Log.d(TAG, "Immersive mode confirmation has already been shown"); 543 return; 544 } else { 545 // Hide the existing confirmation before show a new one in the new root. 546 if (DEBUG) Log.d(TAG, "Immersive mode confirmation was shown in a different root"); 547 handleHide(); 548 } 549 } 550 if (DEBUG) Log.d(TAG, "Showing immersive mode confirmation"); 551 mClingWindow = new ClingWindowView(mDisplayContext, mConfirm); 552 // show the confirmation 553 final WindowManager.LayoutParams lp = getClingWindowLayoutParams(); 554 try { 555 createWindowManager(rootDisplayAreaId).addView(mClingWindow, lp); 556 } catch (WindowManager.InvalidDisplayException e) { 557 Log.w(TAG, "Fail to show the immersive confirmation window because of " + e); 558 } 559 } 560 561 private final Runnable mConfirm = new Runnable() { 562 @Override 563 public void run() { 564 if (DEBUG) Log.d(TAG, "mConfirm.run()"); 565 if (!sConfirmed) { 566 sConfirmed = true; 567 saveSetting(mDisplayContext); 568 } 569 handleHide(); 570 } 571 }; 572 573 private final class H extends Handler { 574 private static final int SHOW = 1; 575 private static final int HIDE = 2; 576 H(Looper looper)577 H(Looper looper) { 578 super(looper); 579 } 580 581 @Override handleMessage(Message msg)582 public void handleMessage(Message msg) { 583 if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { 584 return; 585 } 586 switch(msg.what) { 587 case SHOW: 588 handleShow(msg.arg1); 589 break; 590 case HIDE: 591 handleHide(); 592 break; 593 } 594 } 595 } 596 597 @Override onLockTaskModeChanged(int lockTaskState)598 public void onLockTaskModeChanged(int lockTaskState) { 599 mLockTaskState = lockTaskState; 600 } 601 } 602