ImmersiveModeConfirmation.java revision b60ebb7d382f79dd8da6f1a6fcb36f0f620d01b9
1/* 2 * Copyright (C) 2013 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 17package com.android.server.policy; 18 19import android.animation.ArgbEvaluator; 20import android.animation.ValueAnimator; 21import android.app.ActivityManager; 22import android.app.ActivityThread; 23import android.content.BroadcastReceiver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.graphics.PixelFormat; 28import android.graphics.drawable.ColorDrawable; 29import android.os.Binder; 30import android.os.Handler; 31import android.os.IBinder; 32import android.os.Message; 33import android.os.RemoteException; 34import android.os.ServiceManager; 35import android.os.UserHandle; 36import android.os.UserManager; 37import android.provider.Settings; 38import android.service.vr.IVrManager; 39import android.service.vr.IVrStateCallbacks; 40import android.util.DisplayMetrics; 41import android.util.Slog; 42import android.view.Gravity; 43import android.view.MotionEvent; 44import android.view.View; 45import android.view.ViewGroup; 46import android.view.ViewTreeObserver; 47import android.view.WindowManager; 48import android.view.animation.Animation; 49import android.view.animation.AnimationUtils; 50import android.view.animation.Interpolator; 51import android.widget.Button; 52import android.widget.FrameLayout; 53 54import com.android.internal.R; 55import com.android.server.vr.VrManagerService; 56 57/** 58 * Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden 59 * entering immersive mode. 60 */ 61public class ImmersiveModeConfirmation { 62 private static final String TAG = "ImmersiveModeConfirmation"; 63 private static final boolean DEBUG = false; 64 private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution 65 private static final String CONFIRMED = "confirmed"; 66 67 private final Context mContext; 68 private final H mHandler; 69 private final long mShowDelayMs; 70 private final long mPanicThresholdMs; 71 private final IBinder mWindowToken = new Binder(); 72 73 private boolean mConfirmed; 74 private ClingWindowView mClingWindow; 75 private long mPanicTime; 76 private WindowManager mWindowManager; 77 private int mCurrentUserId; 78 // Local copy of vr mode enabled state, to avoid calling into VrManager with 79 // the lock held. 80 boolean mVrModeEnabled = false; 81 82 public ImmersiveModeConfirmation(Context context) { 83 mContext = ActivityThread.currentActivityThread().getSystemUiContext(); 84 mHandler = new H(); 85 mShowDelayMs = getNavBarExitDuration() * 3; 86 mPanicThresholdMs = context.getResources() 87 .getInteger(R.integer.config_immersive_mode_confirmation_panic); 88 mWindowManager = (WindowManager) 89 mContext.getSystemService(Context.WINDOW_SERVICE); 90 } 91 92 private long getNavBarExitDuration() { 93 Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit); 94 return exit != null ? exit.getDuration() : 0; 95 } 96 97 public void loadSetting(int currentUserId) { 98 mConfirmed = false; 99 mCurrentUserId = currentUserId; 100 if (DEBUG) Slog.d(TAG, String.format("loadSetting() mCurrentUserId=%d", mCurrentUserId)); 101 String value = null; 102 try { 103 value = Settings.Secure.getStringForUser(mContext.getContentResolver(), 104 Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, 105 UserHandle.USER_CURRENT); 106 mConfirmed = CONFIRMED.equals(value); 107 if (DEBUG) Slog.d(TAG, "Loaded mConfirmed=" + mConfirmed); 108 } catch (Throwable t) { 109 Slog.w(TAG, "Error loading confirmations, value=" + value, t); 110 } 111 } 112 113 private void saveSetting() { 114 if (DEBUG) Slog.d(TAG, "saveSetting()"); 115 try { 116 final String value = mConfirmed ? CONFIRMED : null; 117 Settings.Secure.putStringForUser(mContext.getContentResolver(), 118 Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, 119 value, 120 UserHandle.USER_CURRENT); 121 if (DEBUG) Slog.d(TAG, "Saved value=" + value); 122 } catch (Throwable t) { 123 Slog.w(TAG, "Error saving confirmations, mConfirmed=" + mConfirmed, t); 124 } 125 } 126 127 void systemReady() { 128 IVrManager vrManager = IVrManager.Stub.asInterface( 129 ServiceManager.getService(Context.VR_SERVICE)); 130 if (vrManager != null) { 131 try { 132 vrManager.registerListener(mVrStateCallbacks); 133 mVrModeEnabled = vrManager.getVrModeState(); 134 } catch (RemoteException re) { 135 } 136 } 137 } 138 139 public void immersiveModeChangedLw(String pkg, boolean isImmersiveMode, 140 boolean userSetupComplete, boolean navBarEmpty) { 141 mHandler.removeMessages(H.SHOW); 142 if (isImmersiveMode) { 143 final boolean disabled = PolicyControl.disableImmersiveConfirmation(pkg); 144 if (DEBUG) Slog.d(TAG, String.format("immersiveModeChanged() disabled=%s mConfirmed=%s", 145 disabled, mConfirmed)); 146 if (!disabled 147 && (DEBUG_SHOW_EVERY_TIME || !mConfirmed) 148 && userSetupComplete 149 && !mVrModeEnabled 150 && !navBarEmpty 151 && !UserManager.isDeviceInDemoMode(mContext)) { 152 mHandler.sendEmptyMessageDelayed(H.SHOW, mShowDelayMs); 153 } 154 } else { 155 mHandler.sendEmptyMessage(H.HIDE); 156 } 157 } 158 159 public boolean onPowerKeyDown(boolean isScreenOn, long time, boolean inImmersiveMode, 160 boolean navBarEmpty) { 161 if (!isScreenOn && (time - mPanicTime < mPanicThresholdMs)) { 162 // turning the screen back on within the panic threshold 163 return mClingWindow == null; 164 } 165 if (isScreenOn && inImmersiveMode && !navBarEmpty) { 166 // turning the screen off, remember if we were in immersive mode 167 mPanicTime = time; 168 } else { 169 mPanicTime = 0; 170 } 171 return false; 172 } 173 174 public void confirmCurrentPrompt() { 175 if (mClingWindow != null) { 176 if (DEBUG) Slog.d(TAG, "confirmCurrentPrompt()"); 177 mHandler.post(mConfirm); 178 } 179 } 180 181 private void handleHide() { 182 if (mClingWindow != null) { 183 if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation"); 184 mWindowManager.removeView(mClingWindow); 185 mClingWindow = null; 186 } 187 } 188 189 public WindowManager.LayoutParams getClingWindowLayoutParams() { 190 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 191 ViewGroup.LayoutParams.MATCH_PARENT, 192 ViewGroup.LayoutParams.MATCH_PARENT, 193 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 194 0 195 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 196 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED 197 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 198 , 199 PixelFormat.TRANSLUCENT); 200 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; 201 lp.setTitle("ImmersiveModeConfirmation"); 202 lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation; 203 lp.token = getWindowToken(); 204 return lp; 205 } 206 207 public FrameLayout.LayoutParams getBubbleLayoutParams() { 208 return new FrameLayout.LayoutParams( 209 mContext.getResources().getDimensionPixelSize( 210 R.dimen.immersive_mode_cling_width), 211 ViewGroup.LayoutParams.WRAP_CONTENT, 212 Gravity.CENTER_HORIZONTAL | Gravity.TOP); 213 } 214 215 /** 216 * @return the window token that's used by all ImmersiveModeConfirmation windows. 217 */ 218 public IBinder getWindowToken() { 219 return mWindowToken; 220 } 221 222 private class ClingWindowView extends FrameLayout { 223 private static final int BGCOLOR = 0x80000000; 224 private static final int OFFSET_DP = 96; 225 private static final int ANIMATION_DURATION = 250; 226 227 private final Runnable mConfirm; 228 private final ColorDrawable mColor = new ColorDrawable(0); 229 private final Interpolator mInterpolator; 230 private ValueAnimator mColorAnim; 231 private ViewGroup mClingLayout; 232 233 private Runnable mUpdateLayoutRunnable = new Runnable() { 234 @Override 235 public void run() { 236 if (mClingLayout != null && mClingLayout.getParent() != null) { 237 mClingLayout.setLayoutParams(getBubbleLayoutParams()); 238 } 239 } 240 }; 241 242 private ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = 243 new ViewTreeObserver.OnComputeInternalInsetsListener() { 244 private final int[] mTmpInt2 = new int[2]; 245 246 @Override 247 public void onComputeInternalInsets( 248 ViewTreeObserver.InternalInsetsInfo inoutInfo) { 249 // Set touchable region to cover the cling layout. 250 mClingLayout.getLocationInWindow(mTmpInt2); 251 inoutInfo.setTouchableInsets( 252 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 253 inoutInfo.touchableRegion.set( 254 mTmpInt2[0], 255 mTmpInt2[1], 256 mTmpInt2[0] + mClingLayout.getWidth(), 257 mTmpInt2[1] + mClingLayout.getHeight()); 258 } 259 }; 260 261 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 262 @Override 263 public void onReceive(Context context, Intent intent) { 264 if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) { 265 post(mUpdateLayoutRunnable); 266 } 267 } 268 }; 269 270 public ClingWindowView(Context context, Runnable confirm) { 271 super(context); 272 mConfirm = confirm; 273 setBackground(mColor); 274 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 275 mInterpolator = AnimationUtils 276 .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in); 277 } 278 279 @Override 280 public void onAttachedToWindow() { 281 super.onAttachedToWindow(); 282 283 DisplayMetrics metrics = new DisplayMetrics(); 284 mWindowManager.getDefaultDisplay().getMetrics(metrics); 285 float density = metrics.density; 286 287 getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener); 288 289 // create the confirmation cling 290 mClingLayout = (ViewGroup) 291 View.inflate(getContext(), R.layout.immersive_mode_cling, null); 292 293 final Button ok = (Button) mClingLayout.findViewById(R.id.ok); 294 ok.setOnClickListener(new OnClickListener() { 295 @Override 296 public void onClick(View v) { 297 mConfirm.run(); 298 } 299 }); 300 addView(mClingLayout, getBubbleLayoutParams()); 301 302 if (ActivityManager.isHighEndGfx()) { 303 final View cling = mClingLayout; 304 cling.setAlpha(0f); 305 cling.setTranslationY(-OFFSET_DP * density); 306 307 postOnAnimation(new Runnable() { 308 @Override 309 public void run() { 310 cling.animate() 311 .alpha(1f) 312 .translationY(0) 313 .setDuration(ANIMATION_DURATION) 314 .setInterpolator(mInterpolator) 315 .withLayer() 316 .start(); 317 318 mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR); 319 mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 320 @Override 321 public void onAnimationUpdate(ValueAnimator animation) { 322 final int c = (Integer) animation.getAnimatedValue(); 323 mColor.setColor(c); 324 } 325 }); 326 mColorAnim.setDuration(ANIMATION_DURATION); 327 mColorAnim.setInterpolator(mInterpolator); 328 mColorAnim.start(); 329 } 330 }); 331 } else { 332 mColor.setColor(BGCOLOR); 333 } 334 335 mContext.registerReceiver(mReceiver, 336 new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)); 337 } 338 339 @Override 340 public void onDetachedFromWindow() { 341 mContext.unregisterReceiver(mReceiver); 342 } 343 344 @Override 345 public boolean onTouchEvent(MotionEvent motion) { 346 return true; 347 } 348 } 349 350 private void handleShow() { 351 if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation"); 352 353 mClingWindow = new ClingWindowView(mContext, mConfirm); 354 355 // we will be hiding the nav bar, so layout as if it's already hidden 356 mClingWindow.setSystemUiVisibility( 357 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 358 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 359 360 // show the confirmation 361 WindowManager.LayoutParams lp = getClingWindowLayoutParams(); 362 mWindowManager.addView(mClingWindow, lp); 363 } 364 365 private final Runnable mConfirm = new Runnable() { 366 @Override 367 public void run() { 368 if (DEBUG) Slog.d(TAG, "mConfirm.run()"); 369 if (!mConfirmed) { 370 mConfirmed = true; 371 saveSetting(); 372 } 373 handleHide(); 374 } 375 }; 376 377 private final class H extends Handler { 378 private static final int SHOW = 1; 379 private static final int HIDE = 2; 380 381 @Override 382 public void handleMessage(Message msg) { 383 switch(msg.what) { 384 case SHOW: 385 handleShow(); 386 break; 387 case HIDE: 388 handleHide(); 389 break; 390 } 391 } 392 } 393 394 private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { 395 @Override 396 public void onVrStateChanged(boolean enabled) throws RemoteException { 397 mVrModeEnabled = enabled; 398 if (mVrModeEnabled) { 399 mHandler.removeMessages(H.SHOW); 400 mHandler.sendEmptyMessage(H.HIDE); 401 } 402 } 403 }; 404} 405