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