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