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