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