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