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