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