CameraWidgetFrame.java revision 57f928fc4afc02d2fe45523efea687eee7f86c02
1/* 2 * Copyright (C) 2012 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.keyguard; 18 19import android.content.Context; 20import android.content.pm.PackageManager.NameNotFoundException; 21import android.graphics.Bitmap; 22import android.graphics.Canvas; 23import android.graphics.Color; 24import android.graphics.Point; 25import android.os.Handler; 26import android.os.SystemClock; 27import android.util.Log; 28import android.view.LayoutInflater; 29import android.view.MotionEvent; 30import android.view.View; 31import android.view.ViewGroup; 32import android.view.WindowManager; 33import android.widget.FrameLayout; 34import android.widget.ImageView; 35import android.widget.ImageView.ScaleType; 36 37import com.android.internal.R; 38import com.android.internal.policy.impl.keyguard.KeyguardActivityLauncher.CameraWidgetInfo; 39 40public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnClickListener { 41 private static final String TAG = CameraWidgetFrame.class.getSimpleName(); 42 private static final boolean DEBUG = KeyguardHostView.DEBUG; 43 private static final int WIDGET_ANIMATION_DURATION = 250; // ms 44 private static final int WIDGET_WAIT_DURATION = 650; // ms 45 private static final int RECOVERY_DELAY = 1000; // ms 46 47 interface Callbacks { 48 void onLaunchingCamera(); 49 void onCameraLaunchedSuccessfully(); 50 void onCameraLaunchedUnsuccessfully(); 51 } 52 53 private final Handler mHandler = new Handler(); 54 private final KeyguardActivityLauncher mActivityLauncher; 55 private final Callbacks mCallbacks; 56 private final WindowManager mWindowManager; 57 private final Point mRenderedSize = new Point(); 58 private final int[] mScreenLocation = new int[2]; 59 60 private View mWidgetView; 61 private long mLaunchCameraStart; 62 private boolean mActive; 63 private boolean mTransitioning; 64 private boolean mRecovering; 65 private boolean mDown; 66 67 private final Runnable mTransitionToCameraRunnable = new Runnable() { 68 @Override 69 public void run() { 70 transitionToCamera(); 71 }}; 72 73 private final Runnable mTransitionToCameraEndAction = new Runnable() { 74 @Override 75 public void run() { 76 if (!mTransitioning) 77 return; 78 Handler worker = getWorkerHandler() != null ? getWorkerHandler() : mHandler; 79 mLaunchCameraStart = SystemClock.uptimeMillis(); 80 if (DEBUG) Log.d(TAG, "Launching camera at " + mLaunchCameraStart); 81 mActivityLauncher.launchCamera(worker, mSecureCameraActivityStartedRunnable); 82 }}; 83 84 private final Runnable mRecoverRunnable = new Runnable() { 85 @Override 86 public void run() { 87 recover(); 88 }}; 89 90 private final Runnable mRecoverEndAction = new Runnable() { 91 @Override 92 public void run() { 93 if (!mRecovering) 94 return; 95 mCallbacks.onCameraLaunchedUnsuccessfully(); 96 reset(); 97 }}; 98 99 private final Runnable mRenderRunnable = new Runnable() { 100 @Override 101 public void run() { 102 render(); 103 }}; 104 105 private final Runnable mSecureCameraActivityStartedRunnable = new Runnable() { 106 @Override 107 public void run() { 108 onSecureCameraActivityStarted(); 109 } 110 }; 111 112 private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { 113 private boolean mShowing; 114 void onKeyguardVisibilityChanged(boolean showing) { 115 if (mShowing == showing) 116 return; 117 mShowing = showing; 118 CameraWidgetFrame.this.onKeyguardVisibilityChanged(mShowing); 119 }; 120 }; 121 122 private CameraWidgetFrame(Context context, Callbacks callbacks, 123 KeyguardActivityLauncher activityLauncher) { 124 super(context); 125 mCallbacks = callbacks; 126 mActivityLauncher = activityLauncher; 127 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 128 KeyguardUpdateMonitor.getInstance(context).registerCallback(mCallback); 129 if (DEBUG) Log.d(TAG, "new CameraWidgetFrame instance " + instanceId()); 130 } 131 132 public static CameraWidgetFrame create(Context context, Callbacks callbacks, 133 KeyguardActivityLauncher launcher) { 134 if (context == null || callbacks == null || launcher == null) 135 return null; 136 137 CameraWidgetInfo widgetInfo = launcher.getCameraWidgetInfo(); 138 if (widgetInfo == null) 139 return null; 140 View widgetView = widgetInfo.layoutId > 0 ? 141 inflateWidgetView(context, widgetInfo) : 142 inflateGenericWidgetView(context); 143 if (widgetView == null) 144 return null; 145 146 ImageView preview = new ImageView(context); 147 preview.setLayoutParams(new FrameLayout.LayoutParams( 148 FrameLayout.LayoutParams.MATCH_PARENT, 149 FrameLayout.LayoutParams.MATCH_PARENT)); 150 preview.setScaleType(ScaleType.FIT_CENTER); 151 preview.setContentDescription(preview.getContext().getString( 152 R.string.keyguard_accessibility_camera)); 153 CameraWidgetFrame cameraWidgetFrame = new CameraWidgetFrame(context, callbacks, launcher); 154 cameraWidgetFrame.addView(preview); 155 cameraWidgetFrame.mWidgetView = widgetView; 156 preview.setOnClickListener(cameraWidgetFrame); 157 return cameraWidgetFrame; 158 } 159 160 private static View inflateWidgetView(Context context, CameraWidgetInfo widgetInfo) { 161 if (DEBUG) Log.d(TAG, "inflateWidgetView: " + widgetInfo.contextPackage); 162 View widgetView = null; 163 Exception exception = null; 164 try { 165 Context cameraContext = context.createPackageContext( 166 widgetInfo.contextPackage, Context.CONTEXT_RESTRICTED); 167 LayoutInflater cameraInflater = (LayoutInflater) 168 cameraContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 169 cameraInflater = cameraInflater.cloneInContext(cameraContext); 170 widgetView = cameraInflater.inflate(widgetInfo.layoutId, null, false); 171 } catch (NameNotFoundException e) { 172 exception = e; 173 } catch (RuntimeException e) { 174 exception = e; 175 } 176 if (exception != null) { 177 Log.w(TAG, "Error creating camera widget view", exception); 178 } 179 return widgetView; 180 } 181 182 private static View inflateGenericWidgetView(Context context) { 183 if (DEBUG) Log.d(TAG, "inflateGenericWidgetView"); 184 ImageView iv = new ImageView(context); 185 iv.setImageResource(com.android.internal.R.drawable.ic_lockscreen_camera); 186 iv.setScaleType(ScaleType.CENTER); 187 iv.setBackgroundColor(Color.argb(127, 0, 0, 0)); 188 return iv; 189 } 190 191 public void render() { 192 final Throwable[] thrown = new Throwable[1]; 193 final Bitmap[] offscreen = new Bitmap[1]; 194 try { 195 final int width = getRootView().getWidth(); 196 final int height = getRootView().getHeight(); 197 if (mRenderedSize.x == width && mRenderedSize.y == height) { 198 if (DEBUG) Log.d(TAG, String.format("Already rendered at size=%sx%s", 199 width, height)); 200 return; 201 } 202 if (width == 0 || height == 0) { 203 return; 204 } 205 final long start = SystemClock.uptimeMillis(); 206 offscreen[0] = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 207 final Canvas c = new Canvas(offscreen[0]); 208 mWidgetView.measure( 209 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 210 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 211 mWidgetView.layout(0, 0, width, height); 212 mWidgetView.draw(c); 213 214 final long end = SystemClock.uptimeMillis(); 215 if (DEBUG) Log.d(TAG, String.format( 216 "Rendered camera widget in %sms size=%sx%s instance=%s at %s", 217 end - start, 218 width, height, 219 instanceId(), 220 end)); 221 mRenderedSize.set(width, height); 222 } catch (Throwable t) { 223 thrown[0] = t; 224 } 225 226 mHandler.post(new Runnable() { 227 @Override 228 public void run() { 229 if (thrown[0] == null) { 230 try { 231 ((ImageView) getChildAt(0)).setImageBitmap(offscreen[0]); 232 } catch (Throwable t) { 233 thrown[0] = t; 234 } 235 } 236 if (thrown[0] == null) 237 return; 238 239 Log.w(TAG, "Error rendering camera widget", thrown[0]); 240 try { 241 removeAllViews(); 242 final View genericView = inflateGenericWidgetView(mContext); 243 addView(genericView); 244 } catch (Throwable t) { 245 Log.w(TAG, "Error inflating generic camera widget", t); 246 } 247 }}); 248 } 249 250 private void transitionToCamera() { 251 if (mTransitioning || mDown) return; 252 253 mTransitioning = true; 254 255 final View child = getChildAt(0); 256 final View root = getRootView(); 257 258 final int startWidth = child.getWidth(); 259 final int startHeight = child.getHeight(); 260 261 final int finishWidth = root.getWidth(); 262 final int finishHeight = root.getHeight(); 263 264 final float scaleX = (float) finishWidth / startWidth; 265 final float scaleY = (float) finishHeight / startHeight; 266 final float scale = Math.round( Math.max(scaleX, scaleY) * 100) / 100f; 267 268 final int[] loc = new int[2]; 269 root.getLocationInWindow(loc); 270 final int finishCenter = loc[1] + finishHeight / 2; 271 272 child.getLocationInWindow(loc); 273 final int startCenter = loc[1] + startHeight / 2; 274 275 if (DEBUG) Log.d(TAG, String.format("Transitioning to camera. " + 276 "(start=%sx%s, finish=%sx%s, scale=%s,%s, startCenter=%s, finishCenter=%s)", 277 startWidth, startHeight, 278 finishWidth, finishHeight, 279 scaleX, scaleY, 280 startCenter, finishCenter)); 281 282 enableWindowExitAnimation(false); 283 animate() 284 .scaleX(scale) 285 .scaleY(scale) 286 .translationY(finishCenter - startCenter) 287 .setDuration(WIDGET_ANIMATION_DURATION) 288 .withEndAction(mTransitionToCameraEndAction) 289 .start(); 290 291 mCallbacks.onLaunchingCamera(); 292 } 293 294 private void recover() { 295 if (DEBUG) Log.d(TAG, "recovering at " + SystemClock.uptimeMillis()); 296 mRecovering = true; 297 animate() 298 .scaleX(1) 299 .scaleY(1) 300 .translationY(0) 301 .setDuration(WIDGET_ANIMATION_DURATION) 302 .withEndAction(mRecoverEndAction) 303 .start(); 304 } 305 306 @Override 307 public void onClick(View v) { 308 if (DEBUG) Log.d(TAG, "clicked"); 309 if (mTransitioning) return; 310 if (mActive) { 311 cancelTransitionToCamera(); 312 transitionToCamera(); 313 } 314 } 315 316 @Override 317 protected void onDetachedFromWindow() { 318 if (DEBUG) Log.d(TAG, "onDetachedFromWindow: instance " + instanceId() 319 + " at " + SystemClock.uptimeMillis()); 320 super.onDetachedFromWindow(); 321 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mCallback); 322 cancelTransitionToCamera(); 323 mHandler.removeCallbacks(mRecoverRunnable); 324 } 325 326 @Override 327 public void onActive(boolean isActive) { 328 mActive = isActive; 329 if (mActive) { 330 rescheduleTransitionToCamera(); 331 } else { 332 reset(); 333 } 334 } 335 336 @Override 337 public boolean onUserInteraction(MotionEvent event) { 338 if (mTransitioning) { 339 if (DEBUG) Log.d(TAG, "onUserInteraction eaten: mTransitioning"); 340 return true; 341 } 342 343 getLocationOnScreen(mScreenLocation); 344 int rawBottom = mScreenLocation[1] + getHeight(); 345 if (event.getRawY() > rawBottom) { 346 if (DEBUG) Log.d(TAG, "onUserInteraction eaten: below widget"); 347 return true; 348 } 349 350 int action = event.getAction(); 351 mDown = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE; 352 if (mActive) { 353 rescheduleTransitionToCamera(); 354 } 355 if (DEBUG) Log.d(TAG, "onUserInteraction observed, not eaten"); 356 return false; 357 } 358 359 @Override 360 protected void onFocusLost() { 361 if (DEBUG) Log.d(TAG, "onFocusLost at " + SystemClock.uptimeMillis()); 362 cancelTransitionToCamera(); 363 super.onFocusLost(); 364 } 365 366 public void onScreenTurnedOff() { 367 if (DEBUG) Log.d(TAG, "onScreenTurnedOff"); 368 reset(); 369 } 370 371 private void rescheduleTransitionToCamera() { 372 if (DEBUG) Log.d(TAG, "rescheduleTransitionToCamera at " + SystemClock.uptimeMillis()); 373 mHandler.removeCallbacks(mTransitionToCameraRunnable); 374 mHandler.postDelayed(mTransitionToCameraRunnable, WIDGET_WAIT_DURATION); 375 } 376 377 private void cancelTransitionToCamera() { 378 if (DEBUG) Log.d(TAG, "cancelTransitionToCamera at " + SystemClock.uptimeMillis()); 379 mHandler.removeCallbacks(mTransitionToCameraRunnable); 380 } 381 382 private void onCameraLaunched() { 383 mCallbacks.onCameraLaunchedSuccessfully(); 384 reset(); 385 } 386 387 private void reset() { 388 if (DEBUG) Log.d(TAG, "reset at " + SystemClock.uptimeMillis()); 389 mLaunchCameraStart = 0; 390 mTransitioning = false; 391 mRecovering = false; 392 mDown = false; 393 cancelTransitionToCamera(); 394 mHandler.removeCallbacks(mRecoverRunnable); 395 animate().cancel(); 396 setScaleX(1); 397 setScaleY(1); 398 setTranslationY(0); 399 enableWindowExitAnimation(true); 400 } 401 402 @Override 403 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 404 if (DEBUG) Log.d(TAG, String.format("onSizeChanged new=%sx%s old=%sx%s at %s", 405 w, h, oldw, oldh, SystemClock.uptimeMillis())); 406 final Handler worker = getWorkerHandler(); 407 (worker != null ? worker : mHandler).post(mRenderRunnable); 408 super.onSizeChanged(w, h, oldw, oldh); 409 } 410 411 private void enableWindowExitAnimation(boolean isEnabled) { 412 View root = getRootView(); 413 ViewGroup.LayoutParams lp = root.getLayoutParams(); 414 if (!(lp instanceof WindowManager.LayoutParams)) 415 return; 416 WindowManager.LayoutParams wlp = (WindowManager.LayoutParams) lp; 417 int newWindowAnimations = isEnabled ? com.android.internal.R.style.Animation_LockScreen : 0; 418 if (newWindowAnimations != wlp.windowAnimations) { 419 if (DEBUG) Log.d(TAG, "setting windowAnimations to: " + newWindowAnimations 420 + " at " + SystemClock.uptimeMillis()); 421 wlp.windowAnimations = newWindowAnimations; 422 mWindowManager.updateViewLayout(root, wlp); 423 } 424 } 425 426 private void onKeyguardVisibilityChanged(boolean showing) { 427 if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged " + showing 428 + " at " + SystemClock.uptimeMillis()); 429 if (mTransitioning && !showing) { 430 mTransitioning = false; 431 mRecovering = false; 432 mHandler.removeCallbacks(mRecoverRunnable); 433 if (mLaunchCameraStart > 0) { 434 long launchTime = SystemClock.uptimeMillis() - mLaunchCameraStart; 435 if (DEBUG) Log.d(TAG, String.format("Camera took %sms to launch", launchTime)); 436 mLaunchCameraStart = 0; 437 onCameraLaunched(); 438 } 439 } 440 } 441 442 private void onSecureCameraActivityStarted() { 443 if (DEBUG) Log.d(TAG, "onSecureCameraActivityStarted at " + SystemClock.uptimeMillis()); 444 mHandler.postDelayed(mRecoverRunnable, RECOVERY_DELAY); 445 } 446 447 private String instanceId() { 448 return Integer.toHexString(hashCode()); 449 } 450} 451