CameraWidgetFrame.java revision 26b7bf6e3cf6be009cd4baf59e083bce37633a4a
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.keyguard; 18 19import android.content.Context; 20import android.content.pm.PackageManager.NameNotFoundException; 21import android.graphics.Color; 22import android.graphics.Paint; 23import android.graphics.Point; 24import android.graphics.Rect; 25import android.os.Handler; 26import android.os.SystemClock; 27import android.util.Log; 28import android.view.Gravity; 29import android.view.LayoutInflater; 30import android.view.MotionEvent; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.WindowManager; 34import android.widget.FrameLayout; 35import android.widget.ImageView; 36import android.widget.ImageView.ScaleType; 37 38import com.android.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 CameraWidgetInfo mWidgetInfo; 57 private final WindowManager mWindowManager; 58 private final Point mRenderedSize = new Point(); 59 private final int[] mTmpLoc = new int[2]; 60 61 private long mLaunchCameraStart; 62 private boolean mActive; 63 private boolean mTransitioning; 64 private boolean mDown; 65 66 private final Rect mInsets = new Rect(); 67 68 private FixedSizeFrameLayout mPreview; 69 private View mFullscreenPreview; 70 private View mFakeNavBar; 71 72 private final Runnable mTransitionToCameraRunnable = new Runnable() { 73 @Override 74 public void run() { 75 transitionToCamera(); 76 }}; 77 78 private final Runnable mTransitionToCameraEndAction = new Runnable() { 79 @Override 80 public void run() { 81 if (!mTransitioning) 82 return; 83 Handler worker = getWorkerHandler() != null ? getWorkerHandler() : mHandler; 84 mLaunchCameraStart = SystemClock.uptimeMillis(); 85 if (DEBUG) Log.d(TAG, "Launching camera at " + mLaunchCameraStart); 86 mActivityLauncher.launchCamera(worker, mSecureCameraActivityStartedRunnable); 87 }}; 88 89 private final Runnable mPostTransitionToCameraEndAction = new Runnable() { 90 @Override 91 public void run() { 92 mHandler.post(mTransitionToCameraEndAction); 93 }}; 94 95 private final Runnable mRecoverRunnable = new Runnable() { 96 @Override 97 public void run() { 98 recover(); 99 }}; 100 101 private final Runnable mRenderRunnable = new Runnable() { 102 @Override 103 public void run() { 104 render(); 105 }}; 106 107 private final Runnable mSecureCameraActivityStartedRunnable = new Runnable() { 108 @Override 109 public void run() { 110 onSecureCameraActivityStarted(); 111 } 112 }; 113 114 private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { 115 private boolean mShowing; 116 void onKeyguardVisibilityChanged(boolean showing) { 117 if (mShowing == showing) 118 return; 119 mShowing = showing; 120 CameraWidgetFrame.this.onKeyguardVisibilityChanged(mShowing); 121 }; 122 }; 123 124 private static final class FixedSizeFrameLayout extends FrameLayout { 125 int width; 126 int height; 127 128 FixedSizeFrameLayout(Context context) { 129 super(context); 130 } 131 132 @Override 133 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 134 measureChildren( 135 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 136 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 137 setMeasuredDimension(width, height); 138 } 139 } 140 141 private CameraWidgetFrame(Context context, Callbacks callbacks, 142 KeyguardActivityLauncher activityLauncher, 143 CameraWidgetInfo widgetInfo, View previewWidget) { 144 super(context); 145 mCallbacks = callbacks; 146 mActivityLauncher = activityLauncher; 147 mWidgetInfo = widgetInfo; 148 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 149 KeyguardUpdateMonitor.getInstance(context).registerCallback(mCallback); 150 151 mPreview = new FixedSizeFrameLayout(context); 152 mPreview.addView(previewWidget); 153 addView(mPreview); 154 155 View clickBlocker = new View(context); 156 clickBlocker.setBackgroundColor(Color.TRANSPARENT); 157 clickBlocker.setOnClickListener(this); 158 addView(clickBlocker); 159 160 setContentDescription(context.getString(R.string.keyguard_accessibility_camera)); 161 if (DEBUG) Log.d(TAG, "new CameraWidgetFrame instance " + instanceId()); 162 } 163 164 public static CameraWidgetFrame create(Context context, Callbacks callbacks, 165 KeyguardActivityLauncher launcher) { 166 if (context == null || callbacks == null || launcher == null) 167 return null; 168 169 CameraWidgetInfo widgetInfo = launcher.getCameraWidgetInfo(); 170 if (widgetInfo == null) 171 return null; 172 View previewWidget = getPreviewWidget(context, widgetInfo); 173 if (previewWidget == null) 174 return null; 175 176 return new CameraWidgetFrame(context, callbacks, launcher, widgetInfo, previewWidget); 177 } 178 179 private static View getPreviewWidget(Context context, CameraWidgetInfo widgetInfo) { 180 return widgetInfo.layoutId > 0 ? 181 inflateWidgetView(context, widgetInfo) : 182 inflateGenericWidgetView(context); 183 } 184 185 private static View inflateWidgetView(Context context, CameraWidgetInfo widgetInfo) { 186 if (DEBUG) Log.d(TAG, "inflateWidgetView: " + widgetInfo.contextPackage); 187 View widgetView = null; 188 Exception exception = null; 189 try { 190 Context cameraContext = context.createPackageContext( 191 widgetInfo.contextPackage, Context.CONTEXT_RESTRICTED); 192 LayoutInflater cameraInflater = (LayoutInflater) 193 cameraContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 194 cameraInflater = cameraInflater.cloneInContext(cameraContext); 195 widgetView = cameraInflater.inflate(widgetInfo.layoutId, null, false); 196 } catch (NameNotFoundException e) { 197 exception = e; 198 } catch (RuntimeException e) { 199 exception = e; 200 } 201 if (exception != null) { 202 Log.w(TAG, "Error creating camera widget view", exception); 203 } 204 return widgetView; 205 } 206 207 private static View inflateGenericWidgetView(Context context) { 208 if (DEBUG) Log.d(TAG, "inflateGenericWidgetView"); 209 ImageView iv = new ImageView(context); 210 iv.setImageResource(R.drawable.ic_lockscreen_camera); 211 iv.setScaleType(ScaleType.CENTER); 212 iv.setBackgroundColor(Color.argb(127, 0, 0, 0)); 213 return iv; 214 } 215 216 private void render() { 217 final View root = getRootView(); 218 final int width = root.getWidth() - mInsets.right; // leave room 219 final int height = root.getHeight() - mInsets.bottom; // for bars 220 if (mRenderedSize.x == width && mRenderedSize.y == height) { 221 if (DEBUG) Log.d(TAG, String.format("Already rendered at size=%sx%s %d%%", 222 width, height, (int)(100*mPreview.getScaleX()))); 223 return; 224 } 225 if (width == 0 || height == 0) { 226 return; 227 } 228 229 mPreview.width = width; 230 mPreview.height = height; 231 mPreview.requestLayout(); 232 233 final int thisWidth = getWidth() - getPaddingLeft() - getPaddingRight(); 234 final int thisHeight = getHeight() - getPaddingTop() - getPaddingBottom(); 235 236 final float pvScaleX = (float) thisWidth / width; 237 final float pvScaleY = (float) thisHeight / height; 238 final float pvScale = Math.min(pvScaleX, pvScaleY); 239 240 final int pvWidth = (int) (pvScale * width); 241 final int pvHeight = (int) (pvScale * height); 242 243 final float pvTransX = pvWidth < thisWidth ? (thisWidth - pvWidth) / 2 : 0; 244 final float pvTransY = pvHeight < thisHeight ? (thisHeight - pvHeight) / 2 : 0; 245 246 mPreview.setPivotX(0); 247 mPreview.setPivotY(0); 248 mPreview.setScaleX(pvScale); 249 mPreview.setScaleY(pvScale); 250 mPreview.setTranslationX(pvTransX); 251 mPreview.setTranslationY(pvTransY); 252 253 mRenderedSize.set(width, height); 254 if (DEBUG) Log.d(TAG, String.format("Rendered camera widget size=%sx%s %d%% instance=%s", 255 width, height, (int)(100*mPreview.getScaleX()), instanceId())); 256 } 257 258 private void transitionToCamera() { 259 if (mTransitioning || mDown) return; 260 261 mTransitioning = true; 262 263 enableWindowExitAnimation(false); 264 265 final int navHeight = mInsets.bottom; 266 final int navWidth = mInsets.right; 267 268 mPreview.getLocationInWindow(mTmpLoc); 269 final float pvHeight = mPreview.getHeight() * mPreview.getScaleY(); 270 final float pvCenter = mTmpLoc[1] + pvHeight / 2f; 271 272 final ViewGroup root = (ViewGroup) getRootView(); 273 274 if (DEBUG) { 275 Log.d(TAG, "root = " + root.getLeft() + "," + root.getTop() + " " 276 + root.getWidth() + "x" + root.getHeight()); 277 } 278 279 if (mFullscreenPreview == null) { 280 mFullscreenPreview = getPreviewWidget(mContext, mWidgetInfo); 281 mFullscreenPreview.setClickable(false); 282 root.addView(mFullscreenPreview, new FrameLayout.LayoutParams( 283 root.getWidth() - navWidth, 284 root.getHeight() - navHeight)); 285 } 286 287 final float fsHeight = root.getHeight() - navHeight; 288 final float fsCenter = root.getTop() + fsHeight / 2; 289 290 final float fsScaleY = mPreview.getScaleY(); 291 final float fsTransY = pvCenter - fsCenter; 292 final float fsScaleX = fsScaleY; 293 294 mPreview.setVisibility(View.GONE); 295 mFullscreenPreview.setVisibility(View.VISIBLE); 296 mFullscreenPreview.setTranslationY(fsTransY); 297 mFullscreenPreview.setScaleX(fsScaleX); 298 mFullscreenPreview.setScaleY(fsScaleY); 299 mFullscreenPreview 300 .animate() 301 .scaleX(1) 302 .scaleY(1) 303 .translationX(0) 304 .translationY(0) 305 .setDuration(WIDGET_ANIMATION_DURATION) 306 .withEndAction(mPostTransitionToCameraEndAction) 307 .start(); 308 309 if (navHeight > 0 || navWidth > 0) { 310 final boolean atBottom = navHeight > 0; 311 if (mFakeNavBar == null) { 312 mFakeNavBar = new View(mContext); 313 mFakeNavBar.setBackgroundColor(Color.BLACK); 314 root.addView(mFakeNavBar, new FrameLayout.LayoutParams( 315 atBottom ? FrameLayout.LayoutParams.MATCH_PARENT 316 : navWidth, 317 atBottom ? navHeight 318 : FrameLayout.LayoutParams.MATCH_PARENT, 319 atBottom ? Gravity.BOTTOM|Gravity.FILL_HORIZONTAL 320 : Gravity.RIGHT|Gravity.FILL_VERTICAL)); 321 mFakeNavBar.setPivotY(navHeight); 322 mFakeNavBar.setPivotX(navWidth); 323 } 324 mFakeNavBar.setAlpha(0f); 325 if (atBottom) { 326 mFakeNavBar.setScaleY(0.5f); 327 } else { 328 mFakeNavBar.setScaleX(0.5f); 329 } 330 mFakeNavBar.setVisibility(View.VISIBLE); 331 mFakeNavBar.animate() 332 .alpha(1f) 333 .scaleY(1f) 334 .scaleY(1f) 335 .setDuration(WIDGET_ANIMATION_DURATION) 336 .start(); 337 } 338 mCallbacks.onLaunchingCamera(); 339 } 340 341 private void recover() { 342 if (DEBUG) Log.d(TAG, "recovering at " + SystemClock.uptimeMillis()); 343 mCallbacks.onCameraLaunchedUnsuccessfully(); 344 reset(); 345 } 346 347 @Override 348 public void setOnLongClickListener(OnLongClickListener l) { 349 // ignore 350 } 351 352 @Override 353 public void onClick(View v) { 354 if (DEBUG) Log.d(TAG, "clicked"); 355 if (mTransitioning) return; 356 if (mActive) { 357 cancelTransitionToCamera(); 358 transitionToCamera(); 359 } 360 } 361 362 @Override 363 protected void onDetachedFromWindow() { 364 if (DEBUG) Log.d(TAG, "onDetachedFromWindow: instance " + instanceId() 365 + " at " + SystemClock.uptimeMillis()); 366 super.onDetachedFromWindow(); 367 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mCallback); 368 cancelTransitionToCamera(); 369 mHandler.removeCallbacks(mRecoverRunnable); 370 } 371 372 @Override 373 public void onActive(boolean isActive) { 374 mActive = isActive; 375 if (mActive) { 376 rescheduleTransitionToCamera(); 377 } else { 378 reset(); 379 } 380 } 381 382 @Override 383 public boolean onUserInteraction(MotionEvent event) { 384 if (mTransitioning) { 385 if (DEBUG) Log.d(TAG, "onUserInteraction eaten: mTransitioning"); 386 return true; 387 } 388 389 getLocationOnScreen(mTmpLoc); 390 int rawBottom = mTmpLoc[1] + getHeight(); 391 if (event.getRawY() > rawBottom) { 392 if (DEBUG) Log.d(TAG, "onUserInteraction eaten: below widget"); 393 return true; 394 } 395 396 int action = event.getAction(); 397 mDown = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE; 398 if (mActive) { 399 rescheduleTransitionToCamera(); 400 } 401 if (DEBUG) Log.d(TAG, "onUserInteraction observed, not eaten"); 402 return false; 403 } 404 405 @Override 406 protected void onFocusLost() { 407 if (DEBUG) Log.d(TAG, "onFocusLost at " + SystemClock.uptimeMillis()); 408 cancelTransitionToCamera(); 409 super.onFocusLost(); 410 } 411 412 public void onScreenTurnedOff() { 413 if (DEBUG) Log.d(TAG, "onScreenTurnedOff"); 414 reset(); 415 } 416 417 private void rescheduleTransitionToCamera() { 418 if (DEBUG) Log.d(TAG, "rescheduleTransitionToCamera at " + SystemClock.uptimeMillis()); 419 mHandler.removeCallbacks(mTransitionToCameraRunnable); 420 mHandler.postDelayed(mTransitionToCameraRunnable, WIDGET_WAIT_DURATION); 421 } 422 423 private void cancelTransitionToCamera() { 424 if (DEBUG) Log.d(TAG, "cancelTransitionToCamera at " + SystemClock.uptimeMillis()); 425 mHandler.removeCallbacks(mTransitionToCameraRunnable); 426 } 427 428 private void onCameraLaunched() { 429 mCallbacks.onCameraLaunchedSuccessfully(); 430 reset(); 431 } 432 433 private void reset() { 434 if (DEBUG) Log.d(TAG, "reset at " + SystemClock.uptimeMillis()); 435 mLaunchCameraStart = 0; 436 mTransitioning = false; 437 mDown = false; 438 cancelTransitionToCamera(); 439 mHandler.removeCallbacks(mRecoverRunnable); 440 mPreview.setVisibility(View.VISIBLE); 441 if (mFullscreenPreview != null) { 442 mFullscreenPreview.animate().cancel(); 443 mFullscreenPreview.setVisibility(View.GONE); 444 } 445 if (mFakeNavBar != null) { 446 mFakeNavBar.animate().cancel(); 447 mFakeNavBar.setVisibility(View.GONE); 448 } 449 enableWindowExitAnimation(true); 450 } 451 452 @Override 453 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 454 if (DEBUG) Log.d(TAG, String.format("onSizeChanged new=%sx%s old=%sx%s at %s", 455 w, h, oldw, oldh, SystemClock.uptimeMillis())); 456 if ((w != oldw && oldw > 0) || (h != oldh && oldh > 0)) { 457 // we can't trust the old geometry anymore; force a re-render 458 mRenderedSize.x = mRenderedSize.y = -1; 459 } 460 mHandler.post(mRenderRunnable); 461 super.onSizeChanged(w, h, oldw, oldh); 462 } 463 464 @Override 465 public void onBouncerShowing(boolean showing) { 466 if (showing) { 467 mTransitioning = false; 468 mHandler.post(mRecoverRunnable); 469 } 470 } 471 472 private void enableWindowExitAnimation(boolean isEnabled) { 473 View root = getRootView(); 474 ViewGroup.LayoutParams lp = root.getLayoutParams(); 475 if (!(lp instanceof WindowManager.LayoutParams)) 476 return; 477 WindowManager.LayoutParams wlp = (WindowManager.LayoutParams) lp; 478 int newWindowAnimations = isEnabled ? R.style.Animation_LockScreen : 0; 479 if (newWindowAnimations != wlp.windowAnimations) { 480 if (DEBUG) Log.d(TAG, "setting windowAnimations to: " + newWindowAnimations 481 + " at " + SystemClock.uptimeMillis()); 482 wlp.windowAnimations = newWindowAnimations; 483 mWindowManager.updateViewLayout(root, wlp); 484 } 485 } 486 487 private void onKeyguardVisibilityChanged(boolean showing) { 488 if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged " + showing 489 + " at " + SystemClock.uptimeMillis()); 490 if (mTransitioning && !showing) { 491 mTransitioning = false; 492 mHandler.removeCallbacks(mRecoverRunnable); 493 if (mLaunchCameraStart > 0) { 494 long launchTime = SystemClock.uptimeMillis() - mLaunchCameraStart; 495 if (DEBUG) Log.d(TAG, String.format("Camera took %sms to launch", launchTime)); 496 mLaunchCameraStart = 0; 497 onCameraLaunched(); 498 } 499 } 500 } 501 502 private void onSecureCameraActivityStarted() { 503 if (DEBUG) Log.d(TAG, "onSecureCameraActivityStarted at " + SystemClock.uptimeMillis()); 504 mHandler.postDelayed(mRecoverRunnable, RECOVERY_DELAY); 505 } 506 507 private String instanceId() { 508 return Integer.toHexString(hashCode()); 509 } 510 511 public void setInsets(Rect insets) { 512 if (DEBUG) Log.d(TAG, "setInsets: " + insets); 513 mInsets.set(insets); 514 } 515} 516