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