ImmersiveModeConfirmation.java revision a953b6d968beba01baad095b9ed2e40dbb97f189
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.Animator; 20import android.animation.ArgbEvaluator; 21import android.animation.ValueAnimator; 22import android.app.ActivityManager; 23import android.content.Context; 24import android.graphics.PixelFormat; 25import android.graphics.drawable.ColorDrawable; 26import android.os.Handler; 27import android.os.Message; 28import android.os.UserHandle; 29import android.provider.Settings; 30import android.text.TextUtils; 31import android.util.ArraySet; 32import android.util.DisplayMetrics; 33import android.util.Slog; 34import android.view.Gravity; 35import android.view.MotionEvent; 36import android.view.View; 37import android.view.ViewGroup; 38import android.view.WindowManager; 39import android.view.animation.Animation; 40import android.view.animation.AnimationUtils; 41import android.view.animation.DecelerateInterpolator; 42import android.widget.Button; 43import android.widget.FrameLayout; 44 45import com.android.internal.R; 46 47import java.util.Arrays; 48 49/** 50 * Helper to manage showing/hiding a confirmation prompt when the transient navigation bar 51 * is hidden. 52 */ 53public class TransientNavigationConfirmation { 54 private static final String TAG = "TransientNavigationConfirmation"; 55 private static final boolean DEBUG = false; 56 private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution 57 58 private final Context mContext; 59 private final H mHandler; 60 private final ArraySet<String> mConfirmedPackages = new ArraySet<String>(); 61 private final long mShowDelayMs; 62 private final long mPanicThresholdMs; 63 64 private ClingWindowView mClingWindow; 65 private String mLastPackage; 66 private String mPromptPackage; 67 private long mPanicTime; 68 private String mPanicPackage; 69 private WindowManager mWindowManager; 70 71 public TransientNavigationConfirmation(Context context) { 72 mContext = context; 73 mHandler = new H(); 74 mShowDelayMs = getNavBarExitDuration() * 3; 75 mPanicThresholdMs = context.getResources() 76 .getInteger(R.integer.config_transient_navigation_confirmation_panic); 77 mWindowManager = (WindowManager) 78 mContext.getSystemService(Context.WINDOW_SERVICE); 79 } 80 81 private long getNavBarExitDuration() { 82 Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit); 83 return exit != null ? exit.getDuration() : 0; 84 } 85 86 public void loadSetting() { 87 if (DEBUG) Slog.d(TAG, "loadSetting()"); 88 mConfirmedPackages.clear(); 89 String packages = null; 90 try { 91 packages = Settings.Secure.getStringForUser(mContext.getContentResolver(), 92 Settings.Secure.TRANSIENT_NAV_CONFIRMATIONS, 93 UserHandle.USER_CURRENT); 94 if (packages != null) { 95 mConfirmedPackages.addAll(Arrays.asList(packages.split(","))); 96 if (DEBUG) Slog.d(TAG, "Loaded mConfirmedPackages=" + mConfirmedPackages); 97 } 98 } catch (Throwable t) { 99 Slog.w(TAG, "Error loading confirmations, packages=" + packages, t); 100 } 101 } 102 103 private void saveSetting() { 104 if (DEBUG) Slog.d(TAG, "saveSetting()"); 105 try { 106 final String packages = TextUtils.join(",", mConfirmedPackages); 107 Settings.Secure.putStringForUser(mContext.getContentResolver(), 108 Settings.Secure.TRANSIENT_NAV_CONFIRMATIONS, 109 packages, 110 UserHandle.USER_CURRENT); 111 if (DEBUG) Slog.d(TAG, "Saved packages=" + packages); 112 } catch (Throwable t) { 113 Slog.w(TAG, "Error saving confirmations, mConfirmedPackages=" + mConfirmedPackages, t); 114 } 115 } 116 117 public void transientNavigationChanged(String pkg, boolean isNavTransient) { 118 if (pkg == null) { 119 return; 120 } 121 mHandler.removeMessages(H.SHOW); 122 if (isNavTransient) { 123 mLastPackage = pkg; 124 if (DEBUG_SHOW_EVERY_TIME || !mConfirmedPackages.contains(pkg)) { 125 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.SHOW, pkg), mShowDelayMs); 126 } 127 } else { 128 mLastPackage = null; 129 mHandler.sendEmptyMessage(H.HIDE); 130 } 131 } 132 133 public void onPowerKeyDown(boolean isScreenOn, long time, boolean transientNavigationAllowed) { 134 if (mPanicPackage != null && !isScreenOn && (time - mPanicTime < mPanicThresholdMs)) { 135 // turning the screen back on within the panic threshold 136 unconfirmPackage(mPanicPackage); 137 } 138 if (isScreenOn && transientNavigationAllowed) { 139 // turning the screen off, remember if we were hiding the transient nav 140 mPanicTime = time; 141 mPanicPackage = mLastPackage; 142 } else { 143 mPanicTime = 0; 144 mPanicPackage = null; 145 } 146 } 147 148 public void confirmCurrentPrompt() { 149 mHandler.post(confirmAction(mPromptPackage)); 150 } 151 152 private void unconfirmPackage(String pkg) { 153 if (pkg != null) { 154 if (DEBUG) Slog.d(TAG, "Unconfirming transient navigation for " + pkg); 155 mConfirmedPackages.remove(pkg); 156 saveSetting(); 157 } 158 } 159 160 private void handleHide() { 161 if (mClingWindow != null) { 162 if (DEBUG) Slog.d(TAG, 163 "Hiding transient navigation confirmation for " + mPromptPackage); 164 mWindowManager.removeView(mClingWindow); 165 mClingWindow = null; 166 } 167 } 168 169 private class ClingWindowView extends FrameLayout { 170 private static final int BGCOLOR = 0x80000000; 171 private static final int OFFSET_DP = 48; 172 173 private final ColorDrawable mColor = new ColorDrawable(0); 174 private ValueAnimator mColorAnim; 175 176 public ClingWindowView(Context context) { 177 super(context); 178 setClickable(true); 179 setBackground(mColor); 180 } 181 182 @Override 183 public void onAttachedToWindow() { 184 super.onAttachedToWindow(); 185 186 DisplayMetrics metrics = new DisplayMetrics(); 187 mWindowManager.getDefaultDisplay().getMetrics(metrics); 188 float density = metrics.density; 189 190 // create the confirmation cling 191 final ViewGroup clingLayout = (ViewGroup) 192 View.inflate(getContext(), R.layout.transient_navigation_cling, null); 193 194 final Button ok = (Button) clingLayout.findViewById(R.id.ok); 195 ok.setOnClickListener(new OnClickListener() { 196 @Override 197 public void onClick(View v) { 198 handleHide(); 199 } 200 }); 201 addView(clingLayout, new FrameLayout.LayoutParams( 202 FrameLayout.LayoutParams.MATCH_PARENT, 203 FrameLayout.LayoutParams.WRAP_CONTENT 204 )); 205 206 if (ActivityManager.isHighEndGfx()) { 207 final View bubble = clingLayout.findViewById(R.id.text); 208 bubble.setAlpha(0f); 209 bubble.setTranslationY(-OFFSET_DP*density); 210 bubble.animate() 211 .alpha(1f) 212 .translationY(0) 213 .setDuration(300) 214 .setInterpolator(new DecelerateInterpolator()) 215 .start(); 216 217 ok.setAlpha(0f); 218 ok.setTranslationY(-OFFSET_DP*density); 219 ok.animate().alpha(1f) 220 .translationY(0) 221 .setDuration(300) 222 .setStartDelay(200) 223 .setInterpolator(new DecelerateInterpolator()) 224 .start(); 225 226 mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR); 227 mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 228 @Override 229 public void onAnimationUpdate(ValueAnimator animation) { 230 final int c = (Integer) animation.getAnimatedValue(); 231 mColor.setColor(c); 232 } 233 }); 234 mColorAnim.setDuration(1000); 235 mColorAnim.start(); 236 } else { 237 mColor.setColor(BGCOLOR); 238 } 239 } 240 241 @Override 242 public boolean onTouchEvent(MotionEvent motion) { 243 Slog.v(TAG, "ClingWindowView.onTouchEvent"); 244 return true; 245 } 246 } 247 248 private void handleShow(String pkg) { 249 mPromptPackage = pkg; 250 if (DEBUG) Slog.d(TAG, "Showing transient navigation confirmation for " + pkg); 251 252 mClingWindow = new ClingWindowView(mContext); 253 254 // we will be hiding the nav bar, so layout as if it's already hidden 255 mClingWindow.setSystemUiVisibility( 256 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 257 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 258 259 // show the confirmation 260 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 261 ViewGroup.LayoutParams.MATCH_PARENT, 262 ViewGroup.LayoutParams.MATCH_PARENT, 263 WindowManager.LayoutParams.TYPE_TOAST, 264 0 265 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 266 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 267 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED 268 , 269 PixelFormat.TRANSLUCENT); 270 lp.setTitle("TransientNavigationConfirmation"); 271 lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications; 272 lp.gravity = Gravity.FILL; 273 mWindowManager.addView(mClingWindow, lp); 274 } 275 276 private Runnable confirmAction(final String pkg) { 277 return new Runnable() { 278 @Override 279 public void run() { 280 if (pkg != null && !mConfirmedPackages.contains(pkg)) { 281 if (DEBUG) Slog.d(TAG, "Confirming transient navigation for " + pkg); 282 mConfirmedPackages.add(pkg); 283 saveSetting(); 284 } 285 handleHide(); 286 } 287 }; 288 } 289 290 private final class H extends Handler { 291 private static final int SHOW = 0; 292 private static final int HIDE = 1; 293 294 @Override 295 public void handleMessage(Message msg) { 296 switch(msg.what) { 297 case SHOW: 298 handleShow((String)msg.obj); 299 break; 300 case HIDE: 301 handleHide(); 302 break; 303 } 304 } 305 } 306} 307