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