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