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