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.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; 28 import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID; 29 30 import android.animation.ArgbEvaluator; 31 import android.animation.ValueAnimator; 32 import android.annotation.NonNull; 33 import android.annotation.Nullable; 34 import android.app.ActivityManager; 35 import android.content.BroadcastReceiver; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.IntentFilter; 39 import android.content.res.ColorStateList; 40 import android.content.res.Resources; 41 import android.content.res.TypedArray; 42 import android.database.ContentObserver; 43 import android.graphics.Insets; 44 import android.graphics.PixelFormat; 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.ImageView; 75 76 import com.android.systemui.CoreStartable; 77 import com.android.systemui.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 // Trusted overlay so touches outside the touchable area are allowed to pass through 267 lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS 268 | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY 269 | WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW; 270 lp.setTitle("ImmersiveModeConfirmation"); 271 lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation; 272 lp.token = getWindowToken(); 273 return lp; 274 } 275 getBubbleLayoutParams()276 private FrameLayout.LayoutParams getBubbleLayoutParams() { 277 return new FrameLayout.LayoutParams( 278 mSysUiContext.getResources().getDimensionPixelSize( 279 R.dimen.immersive_mode_cling_width), 280 ViewGroup.LayoutParams.WRAP_CONTENT, 281 Gravity.CENTER_HORIZONTAL | Gravity.TOP); 282 } 283 284 /** 285 * @return the window token that's used by all ImmersiveModeConfirmation windows. 286 */ getWindowToken()287 IBinder getWindowToken() { 288 return mWindowToken; 289 } 290 291 @Override start()292 public void start() { 293 if (CLIENT_TRANSIENT || CLIENT_IMMERSIVE_CONFIRMATION) { 294 mCommandQueue.addCallback(this); 295 296 final Resources r = mSysUiContext.getResources(); 297 mShowDelayMs = r.getInteger(R.integer.dock_enter_exit_duration) * 3L; 298 mCanSystemBarsBeShownByUser = !r.getBoolean( 299 R.bool.config_remoteInsetsControllerControlsSystemBars) || r.getBoolean( 300 R.bool.config_remoteInsetsControllerSystemBarsCanBeShownByUserAction); 301 IVrManager vrManager = IVrManager.Stub.asInterface( 302 ServiceManager.getService(Context.VR_SERVICE)); 303 if (vrManager != null) { 304 try { 305 mVrModeEnabled = vrManager.getVrModeState(); 306 vrManager.registerListener(mVrStateCallbacks); 307 mVrStateCallbacks.onVrStateChanged(mVrModeEnabled); 308 } catch (RemoteException e) { 309 // Ignore, we cannot do anything if we failed to access vr manager. 310 } 311 } 312 TaskStackChangeListeners.getInstance().registerTaskStackListener(this); 313 mContentObserver = new ContentObserver(mHandler) { 314 @Override 315 public void onChange(boolean selfChange) { 316 onSettingChanged(mSysUiContext.getUserId()); 317 } 318 }; 319 320 // Register to listen for changes in Settings.Secure settings. 321 mSecureSettings.registerContentObserverForUser( 322 Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, mContentObserver, 323 UserHandle.USER_CURRENT); 324 mSecureSettings.registerContentObserverForUser( 325 Settings.Secure.USER_SETUP_COMPLETE, mContentObserver, 326 UserHandle.USER_CURRENT); 327 } 328 } 329 330 private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { 331 @Override 332 public void onVrStateChanged(boolean enabled) { 333 mVrModeEnabled = enabled; 334 if (mVrModeEnabled) { 335 mHandler.removeMessages(H.SHOW); 336 mHandler.sendEmptyMessage(H.HIDE); 337 } 338 } 339 }; 340 341 private class ClingWindowView extends FrameLayout { 342 private static final int BGCOLOR = 0x80000000; 343 private static final int OFFSET_DP = 96; 344 private static final int ANIMATION_DURATION = 250; 345 346 private final Runnable mConfirm; 347 private final ColorDrawable mColor = new ColorDrawable(0); 348 private final Interpolator mInterpolator; 349 private ValueAnimator mColorAnim; 350 private ViewGroup mClingLayout; 351 352 private Runnable mUpdateLayoutRunnable = new Runnable() { 353 @Override 354 public void run() { 355 if (mClingLayout != null && mClingLayout.getParent() != null) { 356 mClingLayout.setLayoutParams(getBubbleLayoutParams()); 357 } 358 } 359 }; 360 361 private ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = 362 new ViewTreeObserver.OnComputeInternalInsetsListener() { 363 private final int[] mTmpInt2 = new int[2]; 364 365 @Override 366 public void onComputeInternalInsets( 367 ViewTreeObserver.InternalInsetsInfo inoutInfo) { 368 // Set touchable region to cover the cling layout. 369 mClingLayout.getLocationInWindow(mTmpInt2); 370 inoutInfo.setTouchableInsets( 371 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 372 inoutInfo.touchableRegion.set( 373 mTmpInt2[0], 374 mTmpInt2[1], 375 mTmpInt2[0] + mClingLayout.getWidth(), 376 mTmpInt2[1] + mClingLayout.getHeight()); 377 } 378 }; 379 380 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 381 @Override 382 public void onReceive(Context context, Intent intent) { 383 if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) { 384 post(mUpdateLayoutRunnable); 385 } 386 } 387 }; 388 ClingWindowView(Context context, Runnable confirm)389 ClingWindowView(Context context, Runnable confirm) { 390 super(context); 391 mConfirm = confirm; 392 setBackground(mColor); 393 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 394 mInterpolator = AnimationUtils 395 .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in); 396 } 397 398 @Override onAttachedToWindow()399 public void onAttachedToWindow() { 400 super.onAttachedToWindow(); 401 402 DisplayMetrics metrics = new DisplayMetrics(); 403 mContext.getDisplay().getMetrics(metrics); 404 float density = metrics.density; 405 406 getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener); 407 408 // create the confirmation cling 409 mClingLayout = (ViewGroup) 410 View.inflate(mSysUiContext, R.layout.immersive_mode_cling, null); 411 412 TypedArray ta = mDisplayContext.obtainStyledAttributes( 413 new int[]{android.R.attr.colorAccent}); 414 int colorAccent = ta.getColor(0, 0); 415 ta.recycle(); 416 mClingLayout.setBackgroundColor(colorAccent); 417 ImageView expandMore = mClingLayout.findViewById(R.id.immersive_cling_ic_expand_more); 418 if (expandMore != null) { 419 expandMore.setImageTintList(ColorStateList.valueOf(colorAccent)); 420 } 421 ImageView lightBgCirc = mClingLayout.findViewById(R.id.immersive_cling_back_bg_light); 422 if (lightBgCirc != null) { 423 // Set transparency to 50% 424 lightBgCirc.setImageAlpha(128); 425 } 426 427 final Button ok = mClingLayout.findViewById(R.id.ok); 428 ok.setOnClickListener(new OnClickListener() { 429 @Override 430 public void onClick(View v) { 431 mConfirm.run(); 432 } 433 }); 434 addView(mClingLayout, getBubbleLayoutParams()); 435 436 if (ActivityManager.isHighEndGfx()) { 437 final View cling = mClingLayout; 438 cling.setAlpha(0f); 439 cling.setTranslationY(-OFFSET_DP * density); 440 441 postOnAnimation(new Runnable() { 442 @Override 443 public void run() { 444 cling.animate() 445 .alpha(1f) 446 .translationY(0) 447 .setDuration(ANIMATION_DURATION) 448 .setInterpolator(mInterpolator) 449 .withLayer() 450 .start(); 451 452 mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR); 453 mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 454 @Override 455 public void onAnimationUpdate(ValueAnimator animation) { 456 final int c = (Integer) animation.getAnimatedValue(); 457 mColor.setColor(c); 458 } 459 }); 460 mColorAnim.setDuration(ANIMATION_DURATION); 461 mColorAnim.setInterpolator(mInterpolator); 462 mColorAnim.start(); 463 } 464 }); 465 } else { 466 mColor.setColor(BGCOLOR); 467 } 468 469 mContext.registerReceiver(mReceiver, 470 new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)); 471 } 472 473 @Override onDetachedFromWindow()474 public void onDetachedFromWindow() { 475 mContext.unregisterReceiver(mReceiver); 476 } 477 478 @Override onTouchEvent(MotionEvent motion)479 public boolean onTouchEvent(MotionEvent motion) { 480 return true; 481 } 482 483 @Override onApplyWindowInsets(WindowInsets insets)484 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 485 // we will be hiding the nav bar, so layout as if it's already hidden 486 return new WindowInsets.Builder(insets).setInsets( 487 Type.systemBars(), Insets.NONE).build(); 488 } 489 } 490 491 /** 492 * To get window manager for the display. 493 * 494 * @return the WindowManager specifying with the {@code rootDisplayAreaId} to attach the 495 * confirmation window. 496 */ 497 @NonNull createWindowManager(int rootDisplayAreaId)498 private WindowManager createWindowManager(int rootDisplayAreaId) { 499 if (mWindowManager != null) { 500 throw new IllegalStateException( 501 "Must not create a new WindowManager while there is an existing one"); 502 } 503 // Create window context to specify the RootDisplayArea 504 final Bundle options = getOptionsForWindowContext(rootDisplayAreaId); 505 mWindowContextRootDisplayAreaId = rootDisplayAreaId; 506 mWindowContext = mDisplayContext.createWindowContext( 507 IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options); 508 mWindowManager = mWindowContext.getSystemService(WindowManager.class); 509 return mWindowManager; 510 } 511 512 /** 513 * Returns options that specify the {@link RootDisplayArea} to attach the confirmation window. 514 * {@code null} if the {@code rootDisplayAreaId} is {@link FEATURE_UNDEFINED}. 515 */ 516 @Nullable getOptionsForWindowContext(int rootDisplayAreaId)517 private Bundle getOptionsForWindowContext(int rootDisplayAreaId) { 518 // In case we don't care which root display area the window manager is specifying. 519 if (rootDisplayAreaId == FEATURE_UNDEFINED) { 520 return null; 521 } 522 523 final Bundle options = new Bundle(); 524 options.putInt(KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId); 525 return options; 526 } 527 handleShow(int rootDisplayAreaId)528 private void handleShow(int rootDisplayAreaId) { 529 if (mClingWindow != null) { 530 if (rootDisplayAreaId == mWindowContextRootDisplayAreaId) { 531 if (DEBUG) Log.d(TAG, "Immersive mode confirmation has already been shown"); 532 return; 533 } else { 534 // Hide the existing confirmation before show a new one in the new root. 535 if (DEBUG) Log.d(TAG, "Immersive mode confirmation was shown in a different root"); 536 handleHide(); 537 } 538 } 539 if (DEBUG) Log.d(TAG, "Showing immersive mode confirmation"); 540 mClingWindow = new ClingWindowView(mDisplayContext, mConfirm); 541 // show the confirmation 542 final WindowManager.LayoutParams lp = getClingWindowLayoutParams(); 543 try { 544 createWindowManager(rootDisplayAreaId).addView(mClingWindow, lp); 545 } catch (WindowManager.InvalidDisplayException e) { 546 Log.w(TAG, "Fail to show the immersive confirmation window because of " + e); 547 } 548 } 549 550 private final Runnable mConfirm = new Runnable() { 551 @Override 552 public void run() { 553 if (DEBUG) Log.d(TAG, "mConfirm.run()"); 554 if (!sConfirmed) { 555 sConfirmed = true; 556 saveSetting(mDisplayContext); 557 } 558 handleHide(); 559 } 560 }; 561 562 private final class H extends Handler { 563 private static final int SHOW = 1; 564 private static final int HIDE = 2; 565 H(Looper looper)566 H(Looper looper) { 567 super(looper); 568 } 569 570 @Override handleMessage(Message msg)571 public void handleMessage(Message msg) { 572 if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { 573 return; 574 } 575 switch(msg.what) { 576 case SHOW: 577 handleShow(msg.arg1); 578 break; 579 case HIDE: 580 handleHide(); 581 break; 582 } 583 } 584 } 585 586 @Override onLockTaskModeChanged(int lockTaskState)587 public void onLockTaskModeChanged(int lockTaskState) { 588 mLockTaskState = lockTaskState; 589 } 590 } 591