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