• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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