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 package com.android.keyguard;
17 
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.TimeInterpolator;
24 import android.appwidget.AppWidgetHostView;
25 import android.appwidget.AppWidgetManager;
26 import android.appwidget.AppWidgetProviderInfo;
27 import android.content.Context;
28 import android.os.Handler;
29 import android.os.HandlerThread;
30 import android.text.format.DateFormat;
31 import android.util.AttributeSet;
32 import android.util.Slog;
33 import android.view.Gravity;
34 import android.view.MotionEvent;
35 import android.view.View;
36 import android.view.View.OnLongClickListener;
37 import android.view.ViewGroup;
38 import android.view.accessibility.AccessibilityEvent;
39 import android.view.accessibility.AccessibilityManager;
40 import android.view.animation.DecelerateInterpolator;
41 import android.widget.FrameLayout;
42 import android.widget.TextClock;
43 
44 import com.android.internal.widget.LockPatternUtils;
45 
46 import java.util.ArrayList;
47 import java.util.TimeZone;
48 
49 public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwitchListener,
50         OnLongClickListener, ChallengeLayout.OnBouncerStateChangedListener {
51 
52     ZInterpolator mZInterpolator = new ZInterpolator(0.5f);
53     private static float CAMERA_DISTANCE = 10000;
54     protected static float OVERSCROLL_MAX_ROTATION = 30;
55     private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
56 
57     private static final int FLAG_HAS_LOCAL_HOUR = 0x1;
58     private static final int FLAG_HAS_LOCAL_MINUTE = 0x2;
59 
60     protected KeyguardViewStateManager mViewStateManager;
61     private LockPatternUtils mLockPatternUtils;
62 
63     // Related to the fading in / out background outlines
64     public static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
65     public static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
66     protected AnimatorSet mChildrenOutlineFadeAnimation;
67     protected int mScreenCenter;
68     private boolean mHasMeasure = false;
69     boolean showHintsAfterLayout = false;
70 
71     private static final long CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT = 30000;
72     private static final String TAG = "KeyguardWidgetPager";
73     private boolean mCenterSmallWidgetsVertically;
74 
75     private int mPage = 0;
76     private Callbacks mCallbacks;
77 
78     private int mWidgetToResetAfterFadeOut;
79     protected boolean mShowingInitialHints = false;
80 
81     // A temporary handle to the Add-Widget view
82     private View mAddWidgetView;
83     private int mLastWidthMeasureSpec;
84     private int mLastHeightMeasureSpec;
85 
86     // Bouncer
87     private int mBouncerZoomInOutDuration = 250;
88     private float BOUNCER_SCALE_FACTOR = 0.67f;
89 
90     // Background worker thread: used here for persistence, also made available to widget frames
91     private final HandlerThread mBackgroundWorkerThread;
92     private final Handler mBackgroundWorkerHandler;
93     private boolean mCameraEventInProgress;
94 
KeyguardWidgetPager(Context context, AttributeSet attrs)95     public KeyguardWidgetPager(Context context, AttributeSet attrs) {
96         this(context, attrs, 0);
97     }
98 
KeyguardWidgetPager(Context context)99     public KeyguardWidgetPager(Context context) {
100         this(null, null, 0);
101     }
102 
KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle)103     public KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle) {
104         super(context, attrs, defStyle);
105         if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
106             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
107         }
108 
109         setPageSwitchListener(this);
110 
111         mBackgroundWorkerThread = new HandlerThread("KeyguardWidgetPager Worker");
112         mBackgroundWorkerThread.start();
113         mBackgroundWorkerHandler = new Handler(mBackgroundWorkerThread.getLooper());
114     }
115 
116     @Override
onDetachedFromWindow()117     protected void onDetachedFromWindow() {
118         super.onDetachedFromWindow();
119 
120         // Clean up the worker thread
121         mBackgroundWorkerThread.quit();
122     }
123 
setViewStateManager(KeyguardViewStateManager viewStateManager)124     public void setViewStateManager(KeyguardViewStateManager viewStateManager) {
125         mViewStateManager = viewStateManager;
126     }
127 
setLockPatternUtils(LockPatternUtils l)128     public void setLockPatternUtils(LockPatternUtils l) {
129         mLockPatternUtils = l;
130     }
131 
132     @Override
onPageSwitching(View newPage, int newPageIndex)133     public void onPageSwitching(View newPage, int newPageIndex) {
134         if (mViewStateManager != null) {
135             mViewStateManager.onPageSwitching(newPage, newPageIndex);
136         }
137     }
138 
139     @Override
onPageSwitched(View newPage, int newPageIndex)140     public void onPageSwitched(View newPage, int newPageIndex) {
141         boolean showingClock = false;
142         if (newPage instanceof ViewGroup) {
143             ViewGroup vg = (ViewGroup) newPage;
144             if (vg.getChildAt(0) instanceof KeyguardStatusView) {
145                 showingClock = true;
146             }
147         }
148 
149         if (newPage != null &&
150                 findClockInHierarchy(newPage) == (FLAG_HAS_LOCAL_HOUR | FLAG_HAS_LOCAL_MINUTE)) {
151             showingClock = true;
152         }
153 
154         // Disable the status bar clock if we're showing the default status widget
155         if (showingClock) {
156             setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_CLOCK);
157         } else {
158             setSystemUiVisibility(getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK);
159         }
160 
161         // Extend the display timeout if the user switches pages
162         if (mPage != newPageIndex) {
163             int oldPageIndex = mPage;
164             mPage = newPageIndex;
165             userActivity();
166             KeyguardWidgetFrame oldWidgetPage = getWidgetPageAt(oldPageIndex);
167             if (oldWidgetPage != null) {
168                 oldWidgetPage.onActive(false);
169             }
170             KeyguardWidgetFrame newWidgetPage = getWidgetPageAt(newPageIndex);
171             if (newWidgetPage != null) {
172                 newWidgetPage.onActive(true);
173                 newWidgetPage.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
174                 newWidgetPage.requestAccessibilityFocus();
175             }
176             if (mParent != null && AccessibilityManager.getInstance(mContext).isEnabled()) {
177                 AccessibilityEvent event = AccessibilityEvent.obtain(
178                         AccessibilityEvent.TYPE_VIEW_SCROLLED);
179                 onInitializeAccessibilityEvent(event);
180                 onPopulateAccessibilityEvent(event);
181                 mParent.requestSendAccessibilityEvent(this, event);
182             }
183         }
184         if (mViewStateManager != null) {
185             mViewStateManager.onPageSwitched(newPage, newPageIndex);
186         }
187     }
188 
189     @Override
onPageBeginWarp()190     public void onPageBeginWarp() {
191         showOutlinesAndSidePages();
192         mViewStateManager.onPageBeginWarp();
193     }
194 
195     @Override
onPageEndWarp()196     public void onPageEndWarp() {
197         // if we're moving to the warp page, then immediately hide the other widgets.
198         int duration = getPageWarpIndex() == getNextPage() ? 0 : -1;
199         animateOutlinesAndSidePages(false, duration);
200         mViewStateManager.onPageEndWarp();
201     }
202 
203     @Override
sendAccessibilityEvent(int eventType)204     public void sendAccessibilityEvent(int eventType) {
205         if (eventType != AccessibilityEvent.TYPE_VIEW_SCROLLED || isPageMoving()) {
206             super.sendAccessibilityEvent(eventType);
207         }
208     }
209 
updateWidgetFramesImportantForAccessibility()210     private void updateWidgetFramesImportantForAccessibility() {
211         final int pageCount = getPageCount();
212         for (int i = 0; i < pageCount; i++) {
213             KeyguardWidgetFrame frame = getWidgetPageAt(i);
214             updateWidgetFrameImportantForAccessibility(frame);
215         }
216     }
217 
updateWidgetFrameImportantForAccessibility(KeyguardWidgetFrame frame)218     private void updateWidgetFrameImportantForAccessibility(KeyguardWidgetFrame frame) {
219         if (frame.getContentAlpha() <= 0) {
220             frame.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
221         } else {
222             frame.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
223         }
224     }
225 
userActivity()226     private void userActivity() {
227         if (mCallbacks != null) {
228             mCallbacks.onUserActivityTimeoutChanged();
229             mCallbacks.userActivity();
230         }
231     }
232 
233     @Override
onTouchEvent(MotionEvent ev)234     public boolean onTouchEvent(MotionEvent ev) {
235         return captureUserInteraction(ev) || super.onTouchEvent(ev);
236     }
237 
238     @Override
onInterceptTouchEvent(MotionEvent ev)239     public boolean onInterceptTouchEvent(MotionEvent ev) {
240         return captureUserInteraction(ev) || super.onInterceptTouchEvent(ev);
241     }
242 
captureUserInteraction(MotionEvent ev)243     private boolean captureUserInteraction(MotionEvent ev) {
244         KeyguardWidgetFrame currentWidgetPage = getWidgetPageAt(getCurrentPage());
245         return currentWidgetPage != null && currentWidgetPage.onUserInteraction(ev);
246     }
247 
showPagingFeedback()248     public void showPagingFeedback() {
249         // Nothing yet.
250     }
251 
getUserActivityTimeout()252     public long getUserActivityTimeout() {
253         View page = getPageAt(mPage);
254         if (page instanceof ViewGroup) {
255             ViewGroup vg = (ViewGroup) page;
256             View view = vg.getChildAt(0);
257             if (!(view instanceof KeyguardStatusView)
258                     && !(view instanceof KeyguardMultiUserSelectorView)) {
259                 return CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT;
260             }
261         }
262         return -1;
263     }
264 
setCallbacks(Callbacks callbacks)265     public void setCallbacks(Callbacks callbacks) {
266         mCallbacks = callbacks;
267     }
268 
269     public interface Callbacks {
userActivity()270         public void userActivity();
onUserActivityTimeoutChanged()271         public void onUserActivityTimeoutChanged();
onAddView(View v)272         public void onAddView(View v);
onRemoveView(View v, boolean deletePermanently)273         public void onRemoveView(View v, boolean deletePermanently);
onRemoveViewAnimationCompleted()274         public void onRemoveViewAnimationCompleted();
275     }
276 
addWidget(View widget)277     public void addWidget(View widget) {
278         addWidget(widget, -1);
279     }
280 
onRemoveView(View v, final boolean deletePermanently)281     public void onRemoveView(View v, final boolean deletePermanently) {
282         final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId();
283         if (mCallbacks != null) {
284             mCallbacks.onRemoveView(v, deletePermanently);
285         }
286         mBackgroundWorkerHandler.post(new Runnable() {
287             @Override
288             public void run() {
289                 mLockPatternUtils.removeAppWidget(appWidgetId);
290             }
291         });
292     }
293 
294     @Override
onRemoveViewAnimationCompleted()295     public void onRemoveViewAnimationCompleted() {
296         if (mCallbacks != null) {
297             mCallbacks.onRemoveViewAnimationCompleted();
298         }
299     }
300 
onAddView(View v, final int index)301     public void onAddView(View v, final int index) {
302         final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId();
303         final int[] pagesRange = new int[mTempVisiblePagesRange.length];
304         getVisiblePages(pagesRange);
305         boundByReorderablePages(true, pagesRange);
306         if (mCallbacks != null) {
307             mCallbacks.onAddView(v);
308         }
309         // Subtract from the index to take into account pages before the reorderable
310         // pages (e.g. the "add widget" page)
311         mBackgroundWorkerHandler.post(new Runnable() {
312             @Override
313             public void run() {
314                 mLockPatternUtils.addAppWidget(appWidgetId, index - pagesRange[0]);
315             }
316         });
317     }
318 
319     /*
320      * We wrap widgets in a special frame which handles drawing the over scroll foreground.
321      */
addWidget(View widget, int pageIndex)322     public void addWidget(View widget, int pageIndex) {
323         KeyguardWidgetFrame frame;
324         // All views contained herein should be wrapped in a KeyguardWidgetFrame
325         if (!(widget instanceof KeyguardWidgetFrame)) {
326             frame = new KeyguardWidgetFrame(getContext());
327             FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
328                     LayoutParams.MATCH_PARENT);
329             lp.gravity = Gravity.TOP;
330 
331             // The framework adds a default padding to AppWidgetHostView. We don't need this padding
332             // for the Keyguard, so we override it to be 0.
333             widget.setPadding(0,  0, 0, 0);
334             frame.addView(widget, lp);
335 
336             // We set whether or not this widget supports vertical resizing.
337             if (widget instanceof AppWidgetHostView) {
338                 AppWidgetHostView awhv = (AppWidgetHostView) widget;
339                 AppWidgetProviderInfo info = awhv.getAppWidgetInfo();
340                 if ((info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) {
341                     frame.setWidgetLockedSmall(false);
342                 } else {
343                     // Lock the widget to be small.
344                     frame.setWidgetLockedSmall(true);
345                     if (mCenterSmallWidgetsVertically) {
346                         lp.gravity = Gravity.CENTER;
347                     }
348                 }
349             }
350         } else {
351             frame = (KeyguardWidgetFrame) widget;
352         }
353 
354         ViewGroup.LayoutParams pageLp = new ViewGroup.LayoutParams(
355                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
356         frame.setOnLongClickListener(this);
357         frame.setWorkerHandler(mBackgroundWorkerHandler);
358 
359         if (pageIndex == -1) {
360             addView(frame, pageLp);
361         } else {
362             addView(frame, pageIndex, pageLp);
363         }
364 
365         // Update the frame content description.
366         View content = (widget == frame) ?  frame.getContent() : widget;
367         if (content != null) {
368             String contentDescription = mContext.getString(
369                 R.string.keyguard_accessibility_widget,
370                 content.getContentDescription());
371             frame.setContentDescription(contentDescription);
372         }
373         updateWidgetFrameImportantForAccessibility(frame);
374     }
375 
376     /**
377      * Use addWidget() instead.
378      * @deprecated
379      */
380     @Override
addView(View child, int index)381     public void addView(View child, int index) {
382         enforceKeyguardWidgetFrame(child);
383         super.addView(child, index);
384     }
385 
386     /**
387      * Use addWidget() instead.
388      * @deprecated
389      */
390     @Override
addView(View child, int width, int height)391     public void addView(View child, int width, int height) {
392         enforceKeyguardWidgetFrame(child);
393         super.addView(child, width, height);
394     }
395 
396     /**
397      * Use addWidget() instead.
398      * @deprecated
399      */
400     @Override
addView(View child, LayoutParams params)401     public void addView(View child, LayoutParams params) {
402         enforceKeyguardWidgetFrame(child);
403         super.addView(child, params);
404     }
405 
406     /**
407      * Use addWidget() instead.
408      * @deprecated
409      */
410     @Override
addView(View child, int index, LayoutParams params)411     public void addView(View child, int index, LayoutParams params) {
412         enforceKeyguardWidgetFrame(child);
413         super.addView(child, index, params);
414     }
415 
enforceKeyguardWidgetFrame(View child)416     private void enforceKeyguardWidgetFrame(View child) {
417         if (!(child instanceof KeyguardWidgetFrame)) {
418             throw new IllegalArgumentException(
419                     "KeyguardWidgetPager children must be KeyguardWidgetFrames");
420         }
421     }
422 
getWidgetPageAt(int index)423     public KeyguardWidgetFrame getWidgetPageAt(int index) {
424         // This is always a valid cast as we've guarded the ability to
425         return (KeyguardWidgetFrame) getChildAt(index);
426     }
427 
onUnhandledTap(MotionEvent ev)428     protected void onUnhandledTap(MotionEvent ev) {
429         showPagingFeedback();
430     }
431 
432     @Override
onPageBeginMoving()433     protected void onPageBeginMoving() {
434         if (mViewStateManager != null) {
435             mViewStateManager.onPageBeginMoving();
436         }
437         if (!isReordering(false)) {
438             showOutlinesAndSidePages();
439         }
440         userActivity();
441     }
442 
443     @Override
onPageEndMoving()444     protected void onPageEndMoving() {
445         if (mViewStateManager != null) {
446             mViewStateManager.onPageEndMoving();
447         }
448 
449         // In the reordering case, the pages will be faded appropriately on completion
450         // of the zoom in animation.
451         if (!isReordering(false)) {
452             hideOutlinesAndSidePages();
453         }
454     }
455 
enablePageContentLayers()456     protected void enablePageContentLayers() {
457         int children = getChildCount();
458         for (int i = 0; i < children; i++) {
459             getWidgetPageAt(i).enableHardwareLayersForContent();
460         }
461     }
462 
disablePageContentLayers()463     protected void disablePageContentLayers() {
464         int children = getChildCount();
465         for (int i = 0; i < children; i++) {
466             getWidgetPageAt(i).disableHardwareLayersForContent();
467         }
468     }
469 
470     /*
471      * This interpolator emulates the rate at which the perceived scale of an object changes
472      * as its distance from a camera increases. When this interpolator is applied to a scale
473      * animation on a view, it evokes the sense that the object is shrinking due to moving away
474      * from the camera.
475      */
476     static class ZInterpolator implements TimeInterpolator {
477         private float focalLength;
478 
ZInterpolator(float foc)479         public ZInterpolator(float foc) {
480             focalLength = foc;
481         }
482 
getInterpolation(float input)483         public float getInterpolation(float input) {
484             return (1.0f - focalLength / (focalLength + input)) /
485                 (1.0f - focalLength / (focalLength + 1.0f));
486         }
487     }
488 
489     @Override
overScroll(float amount)490     protected void overScroll(float amount) {
491         acceleratedOverScroll(amount);
492     }
493 
backgroundAlphaInterpolator(float r)494     float backgroundAlphaInterpolator(float r) {
495         return Math.min(1f, r);
496     }
497 
updatePageAlphaValues(int screenCenter)498     private void updatePageAlphaValues(int screenCenter) {
499     }
500 
getAlphaForPage(int screenCenter, int index, boolean showSidePages)501     public float getAlphaForPage(int screenCenter, int index, boolean showSidePages) {
502         if (isWarping()) {
503             return index == getPageWarpIndex() ? 1.0f : 0.0f;
504         }
505         if (showSidePages) {
506             return 1f;
507         } else {
508             return index == mCurrentPage ? 1.0f : 0f;
509         }
510     }
511 
getOutlineAlphaForPage(int screenCenter, int index, boolean showSidePages)512     public float getOutlineAlphaForPage(int screenCenter, int index, boolean showSidePages) {
513         if (showSidePages) {
514             return getAlphaForPage(screenCenter, index, showSidePages)
515                     * KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER;
516         } else {
517             return 0f;
518         }
519     }
520 
isOverScrollChild(int index, float scrollProgress)521     protected boolean isOverScrollChild(int index, float scrollProgress) {
522         boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
523         return (isInOverscroll && (index == 0 && scrollProgress < 0 ||
524                 index == getChildCount() - 1 && scrollProgress > 0));
525     }
526 
527     @Override
screenScrolled(int screenCenter)528     protected void screenScrolled(int screenCenter) {
529         mScreenCenter = screenCenter;
530         updatePageAlphaValues(screenCenter);
531         for (int i = 0; i < getChildCount(); i++) {
532             KeyguardWidgetFrame v = getWidgetPageAt(i);
533             if (v == mDragView) continue;
534             if (v != null) {
535                 float scrollProgress = getScrollProgress(screenCenter, v, i);
536 
537                 v.setCameraDistance(mDensity * CAMERA_DISTANCE);
538 
539                 if (isOverScrollChild(i, scrollProgress) && PERFORM_OVERSCROLL_ROTATION) {
540                     float pivotX = v.getMeasuredWidth() / 2;
541                     float pivotY = v.getMeasuredHeight() / 2;
542                     v.setPivotX(pivotX);
543                     v.setPivotY(pivotY);
544                     v.setRotationY(- OVERSCROLL_MAX_ROTATION * scrollProgress);
545                     v.setOverScrollAmount(Math.abs(scrollProgress), scrollProgress < 0);
546                 } else {
547                     v.setRotationY(0f);
548                     v.setOverScrollAmount(0, false);
549                 }
550 
551                 float alpha = v.getAlpha();
552                 // If the view has 0 alpha, we set it to be invisible so as to prevent
553                 // it from accepting touches
554                 if (alpha == 0) {
555                     v.setVisibility(INVISIBLE);
556                 } else if (v.getVisibility() != VISIBLE) {
557                     v.setVisibility(VISIBLE);
558                 }
559             }
560         }
561     }
562 
563     public boolean isWidgetPage(int pageIndex) {
564         if (pageIndex < 0 || pageIndex >= getChildCount()) {
565             return false;
566         }
567         View v = getChildAt(pageIndex);
568         if (v != null && v instanceof KeyguardWidgetFrame) {
569             KeyguardWidgetFrame kwf = (KeyguardWidgetFrame) v;
570             return kwf.getContentAppWidgetId() != AppWidgetManager.INVALID_APPWIDGET_ID;
571         }
572         return false;
573     }
574 
575     /**
576      * Returns the bounded set of pages that are re-orderable.  The range is fully inclusive.
577      */
578     @Override
579     void boundByReorderablePages(boolean isReordering, int[] range) {
580         if (isReordering) {
581             // Remove non-widget pages from the range
582             while (range[1] >= range[0] && !isWidgetPage(range[1])) {
583                 range[1]--;
584             }
585             while (range[0] <= range[1] && !isWidgetPage(range[0])) {
586                 range[0]++;
587             }
588         }
589     }
590 
591     protected void reorderStarting() {
592         showOutlinesAndSidePages();
593     }
594 
595     @Override
596     protected void onStartReordering() {
597         super.onStartReordering();
598         enablePageContentLayers();
599         reorderStarting();
600     }
601 
602     @Override
603     protected void onEndReordering() {
604         super.onEndReordering();
605         hideOutlinesAndSidePages();
606     }
607 
608     void showOutlinesAndSidePages() {
609         animateOutlinesAndSidePages(true);
610     }
611 
612     void hideOutlinesAndSidePages() {
613         animateOutlinesAndSidePages(false);
614     }
615 
616     void updateChildrenContentAlpha(float sidePageAlpha) {
617         int count = getChildCount();
618         for (int i = 0; i < count; i++) {
619             KeyguardWidgetFrame child = getWidgetPageAt(i);
620             if (i != mCurrentPage) {
621                 child.setBackgroundAlpha(sidePageAlpha);
622                 child.setContentAlpha(0f);
623             } else {
624                 child.setBackgroundAlpha(0f);
625                 child.setContentAlpha(1f);
626             }
627         }
628     }
629 
630     public void showInitialPageHints() {
631         mShowingInitialHints = true;
632         updateChildrenContentAlpha(KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER);
633     }
634 
635     @Override
636     void setCurrentPage(int currentPage) {
637         super.setCurrentPage(currentPage);
638         updateChildrenContentAlpha(0.0f);
639         updateWidgetFramesImportantForAccessibility();
640     }
641 
642     @Override
643     public void onAttachedToWindow() {
644         super.onAttachedToWindow();
645         mHasMeasure = false;
646     }
647 
648     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
649         mLastWidthMeasureSpec = widthMeasureSpec;
650         mLastHeightMeasureSpec = heightMeasureSpec;
651 
652         int maxChallengeTop = -1;
653         View parent = (View) getParent();
654         boolean challengeShowing = false;
655         // Widget pages need to know where the top of the sliding challenge is so that they
656         // now how big the widget should be when the challenge is up. We compute it here and
657         // then propagate it to each of our children.
658         if (parent.getParent() instanceof SlidingChallengeLayout) {
659             SlidingChallengeLayout scl = (SlidingChallengeLayout) parent.getParent();
660             int top = scl.getMaxChallengeTop();
661 
662             // This is a bit evil, but we need to map a coordinate relative to the SCL into a
663             // coordinate relative to our children, hence we subtract the top padding.s
664             maxChallengeTop = top - getPaddingTop();
665             challengeShowing = scl.isChallengeShowing();
666 
667             int count = getChildCount();
668             for (int i = 0; i < count; i++) {
669                 KeyguardWidgetFrame frame = getWidgetPageAt(i);
670                 frame.setMaxChallengeTop(maxChallengeTop);
671                 // On the very first measure pass, if the challenge is showing, we need to make sure
672                 // that the widget on the current page is small.
673                 if (challengeShowing && i == mCurrentPage && !mHasMeasure) {
674                     frame.shrinkWidget(true);
675                 }
676             }
677         }
678         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
679         mHasMeasure = true;
680     }
681 
682     void animateOutlinesAndSidePages(final boolean show) {
683         animateOutlinesAndSidePages(show, -1);
684     }
685 
686     public void setWidgetToResetOnPageFadeOut(int widget) {
687         mWidgetToResetAfterFadeOut = widget;
688     }
689 
690     public int getWidgetToResetOnPageFadeOut() {
691         return mWidgetToResetAfterFadeOut;
692     }
693 
694     void animateOutlinesAndSidePages(final boolean show, int duration) {
695         if (mChildrenOutlineFadeAnimation != null) {
696             mChildrenOutlineFadeAnimation.cancel();
697             mChildrenOutlineFadeAnimation = null;
698         }
699         int count = getChildCount();
700         PropertyValuesHolder alpha;
701         ArrayList<Animator> anims = new ArrayList<Animator>();
702 
703         if (duration == -1) {
704             duration = show ? CHILDREN_OUTLINE_FADE_IN_DURATION :
705                 CHILDREN_OUTLINE_FADE_OUT_DURATION;
706         }
707 
708         int curPage = getNextPage();
709         for (int i = 0; i < count; i++) {
710             float finalContentAlpha;
711             if (show) {
712                 finalContentAlpha = getAlphaForPage(mScreenCenter, i, true);
713             } else if (!show && i == curPage) {
714                 finalContentAlpha = 1f;
715             } else {
716                 finalContentAlpha = 0f;
717             }
718             KeyguardWidgetFrame child = getWidgetPageAt(i);
719 
720             alpha = PropertyValuesHolder.ofFloat("contentAlpha", finalContentAlpha);
721             ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(child, alpha);
722             anims.add(a);
723 
724             float finalOutlineAlpha = show ? getOutlineAlphaForPage(mScreenCenter, i, true) : 0f;
725             child.fadeFrame(this, show, finalOutlineAlpha, duration);
726         }
727 
728         mChildrenOutlineFadeAnimation = new AnimatorSet();
729         mChildrenOutlineFadeAnimation.playTogether(anims);
730 
731         mChildrenOutlineFadeAnimation.setDuration(duration);
732         mChildrenOutlineFadeAnimation.addListener(new AnimatorListenerAdapter() {
733             @Override
734             public void onAnimationStart(Animator animation) {
735                 if (show) {
736                     enablePageContentLayers();
737                 }
738             }
739 
740             @Override
741             public void onAnimationEnd(Animator animation) {
742                 if (!show) {
743                     disablePageContentLayers();
744                     KeyguardWidgetFrame frame = getWidgetPageAt(mWidgetToResetAfterFadeOut);
745                     if (frame != null && !(frame == getWidgetPageAt(mCurrentPage) &&
746                             mViewStateManager.isChallengeOverlapping())) {
747                         frame.resetSize();
748                     }
749                     mWidgetToResetAfterFadeOut = -1;
750                     mShowingInitialHints = false;
751                 }
752                 updateWidgetFramesImportantForAccessibility();
753             }
754         });
755         mChildrenOutlineFadeAnimation.start();
756     }
757 
758     @Override
759     public boolean onLongClick(View v) {
760         // Disallow long pressing to reorder if the challenge is showing
761         boolean isChallengeOverlapping = mViewStateManager.isChallengeShowing() &&
762                 mViewStateManager.isChallengeOverlapping();
763         if (!isChallengeOverlapping && startReordering()) {
764             return true;
765         }
766         return false;
767     }
768 
769     public void removeWidget(View view) {
770         if (view instanceof KeyguardWidgetFrame) {
771             removeView(view);
772         } else {
773             // Assume view was wrapped by a KeyguardWidgetFrame in KeyguardWidgetPager#addWidget().
774             // This supports legacy hard-coded "widgets" like KeyguardTransportControlView.
775             int pos = getWidgetPageIndex(view);
776             if (pos != -1) {
777                 KeyguardWidgetFrame frame = (KeyguardWidgetFrame) getChildAt(pos);
778                 frame.removeView(view);
779                 removeView(frame);
780             } else {
781                 Slog.w(TAG, "removeWidget() can't find:" + view);
782             }
783         }
784     }
785 
786     public int getWidgetPageIndex(View view) {
787         if (view instanceof KeyguardWidgetFrame) {
788             return indexOfChild(view);
789         } else {
790             // View was wrapped by a KeyguardWidgetFrame by KeyguardWidgetPager#addWidget()
791             return indexOfChild((KeyguardWidgetFrame)view.getParent());
792         }
793     }
794 
795     @Override
796     protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {
797         KeyguardWidgetFrame child = getWidgetPageAt(viewIndex);
798         child.setIsHoveringOverDeleteDropTarget(isHovering);
799     }
800 
801     // ChallengeLayout.OnBouncerStateChangedListener
802     @Override
803     public void onBouncerStateChanged(boolean bouncerActive) {
804         if (bouncerActive) {
805             zoomOutToBouncer();
806         } else {
807             zoomInFromBouncer();
808         }
809     }
810 
811     void setBouncerAnimationDuration(int duration) {
812         mBouncerZoomInOutDuration = duration;
813     }
814 
815     // Zoom in after the bouncer is dismissed
816     void zoomInFromBouncer() {
817         if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
818             mZoomInOutAnim.cancel();
819         }
820         final View currentPage = getPageAt(getCurrentPage());
821         if (currentPage.getScaleX() < 1f || currentPage.getScaleY() < 1f) {
822             mZoomInOutAnim = new AnimatorSet();
823             mZoomInOutAnim.playTogether(
824                     ObjectAnimator.ofFloat(currentPage, "scaleX", 1f),
825                     ObjectAnimator.ofFloat(currentPage , "scaleY", 1f));
826             mZoomInOutAnim.setDuration(mBouncerZoomInOutDuration);
827             mZoomInOutAnim.setInterpolator(new DecelerateInterpolator(1.5f));
828             mZoomInOutAnim.start();
829         }
830         if (currentPage instanceof KeyguardWidgetFrame) {
831             ((KeyguardWidgetFrame)currentPage).onBouncerShowing(false);
832         }
833     }
834 
835     // Zoom out after the bouncer is initiated
836     void zoomOutToBouncer() {
837         if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
838             mZoomInOutAnim.cancel();
839         }
840         int curPage = getCurrentPage();
841         View currentPage = getPageAt(curPage);
842         if (shouldSetTopAlignedPivotForWidget(curPage)) {
843             currentPage.setPivotY(0);
844             // Note: we are working around the issue that setting the x-pivot to the same value as it
845             //       was does not actually work.
846             currentPage.setPivotX(0);
847             currentPage.setPivotX(currentPage.getMeasuredWidth() / 2);
848         }
849         if (!(currentPage.getScaleX() < 1f || currentPage.getScaleY() < 1f)) {
850             mZoomInOutAnim = new AnimatorSet();
851             mZoomInOutAnim.playTogether(
852                     ObjectAnimator.ofFloat(currentPage, "scaleX", BOUNCER_SCALE_FACTOR),
853                     ObjectAnimator.ofFloat(currentPage, "scaleY", BOUNCER_SCALE_FACTOR));
854             mZoomInOutAnim.setDuration(mBouncerZoomInOutDuration);
855             mZoomInOutAnim.setInterpolator(new DecelerateInterpolator(1.5f));
856             mZoomInOutAnim.start();
857         }
858         if (currentPage instanceof KeyguardWidgetFrame) {
859             ((KeyguardWidgetFrame)currentPage).onBouncerShowing(true);
860         }
861     }
862 
863     void setAddWidgetEnabled(boolean enabled) {
864         if (mAddWidgetView != null && enabled) {
865             addView(mAddWidgetView, 0);
866             // We need to force measure the PagedView so that the calls to update the scroll
867             // position below work
868             measure(mLastWidthMeasureSpec, mLastHeightMeasureSpec);
869             // Bump up the current page to account for the addition of the new page
870             setCurrentPage(mCurrentPage + 1);
871             mAddWidgetView = null;
872         } else if (mAddWidgetView == null && !enabled) {
873             View addWidget = findViewById(R.id.keyguard_add_widget);
874             if (addWidget != null) {
875                 mAddWidgetView = addWidget;
876                 removeView(addWidget);
877             }
878         }
879     }
880 
881     boolean isAddPage(int pageIndex) {
882         View v = getChildAt(pageIndex);
883         return v != null && v.getId() == R.id.keyguard_add_widget;
884     }
885 
886     boolean isCameraPage(int pageIndex) {
887         View v = getChildAt(pageIndex);
888         return v != null && v instanceof CameraWidgetFrame;
889     }
890 
891     @Override
892     protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) {
893         return !isCameraPage(childIndex) && super.shouldSetTopAlignedPivotForWidget(childIndex);
894     }
895 
896     /**
897      * Search given {@link View} hierarchy for {@link TextClock} instances that
898      * show various time components. Returns combination of
899      * {@link #FLAG_HAS_LOCAL_HOUR} and {@link #FLAG_HAS_LOCAL_MINUTE}.
900      */
901     private static int findClockInHierarchy(View view) {
902         if (view instanceof TextClock) {
903             return getClockFlags((TextClock) view);
904         } else if (view instanceof ViewGroup) {
905             int flags = 0;
906             final ViewGroup group = (ViewGroup) view;
907             final int size = group.getChildCount();
908             for (int i = 0; i < size; i++) {
909                 flags |= findClockInHierarchy(group.getChildAt(i));
910             }
911             return flags;
912         } else {
913             return 0;
914         }
915     }
916 
917     /**
918      * Return combination of {@link #FLAG_HAS_LOCAL_HOUR} and
919      * {@link #FLAG_HAS_LOCAL_MINUTE} describing the time represented described
920      * by the given {@link TextClock}.
921      */
922     private static int getClockFlags(TextClock clock) {
923         int flags = 0;
924 
925         final String timeZone = clock.getTimeZone();
926         if (timeZone != null && !TimeZone.getDefault().equals(TimeZone.getTimeZone(timeZone))) {
927             // Ignore clocks showing another timezone
928             return 0;
929         }
930 
931         final CharSequence format = clock.getFormat();
932         final char hour = clock.is24HourModeEnabled() ? DateFormat.HOUR_OF_DAY
933                 : DateFormat.HOUR;
934 
935         if (DateFormat.hasDesignator(format, hour)) {
936             flags |= FLAG_HAS_LOCAL_HOUR;
937         }
938         if (DateFormat.hasDesignator(format, DateFormat.MINUTE)) {
939             flags |= FLAG_HAS_LOCAL_MINUTE;
940         }
941 
942         return flags;
943     }
944 
945     public void handleExternalCameraEvent(MotionEvent event) {
946         beginCameraEvent();
947         int cameraPage = getPageCount() - 1;
948         boolean endWarp = false;
949         if (isCameraPage(cameraPage) || mCameraEventInProgress) {
950             switch (event.getAction()) {
951                 case MotionEvent.ACTION_DOWN:
952                     // Once we start dispatching camera events, we must continue to do so
953                     // to keep event dispatch happy.
954                     mCameraEventInProgress = true;
955                     userActivity();
956                     break;
957                 case MotionEvent.ACTION_UP:
958                 case MotionEvent.ACTION_CANCEL:
959                     mCameraEventInProgress = false;
960                     break;
961             }
962             dispatchTouchEvent(event);
963         }
964         endCameraEvent();
965     }
966 
967 }
968