/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.keyguard; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.text.format.DateFormat; import android.util.AttributeSet; import android.util.Slog; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.TextClock; import com.android.internal.widget.LockPatternUtils; import java.util.ArrayList; import java.util.TimeZone; public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwitchListener, OnLongClickListener, ChallengeLayout.OnBouncerStateChangedListener { ZInterpolator mZInterpolator = new ZInterpolator(0.5f); private static float CAMERA_DISTANCE = 10000; protected static float OVERSCROLL_MAX_ROTATION = 30; private static final boolean PERFORM_OVERSCROLL_ROTATION = true; private static final int FLAG_HAS_LOCAL_HOUR = 0x1; private static final int FLAG_HAS_LOCAL_MINUTE = 0x2; protected KeyguardViewStateManager mViewStateManager; private LockPatternUtils mLockPatternUtils; // Related to the fading in / out background outlines public static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375; public static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100; protected AnimatorSet mChildrenOutlineFadeAnimation; protected int mScreenCenter; private boolean mHasMeasure = false; boolean showHintsAfterLayout = false; private static final long CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT = 30000; private static final String TAG = "KeyguardWidgetPager"; private boolean mCenterSmallWidgetsVertically; private int mPage = 0; private Callbacks mCallbacks; private int mWidgetToResetAfterFadeOut; protected boolean mShowingInitialHints = false; // A temporary handle to the Add-Widget view private View mAddWidgetView; private int mLastWidthMeasureSpec; private int mLastHeightMeasureSpec; // Bouncer private int mBouncerZoomInOutDuration = 250; private float BOUNCER_SCALE_FACTOR = 0.67f; // Background worker thread: used here for persistence, also made available to widget frames private final HandlerThread mBackgroundWorkerThread; private final Handler mBackgroundWorkerHandler; private boolean mCameraEventInProgress; public KeyguardWidgetPager(Context context, AttributeSet attrs) { this(context, attrs, 0); } public KeyguardWidgetPager(Context context) { this(null, null, 0); } public KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } setPageSwitchListener(this); mBackgroundWorkerThread = new HandlerThread("KeyguardWidgetPager Worker"); mBackgroundWorkerThread.start(); mBackgroundWorkerHandler = new Handler(mBackgroundWorkerThread.getLooper()); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); // Clean up the worker thread mBackgroundWorkerThread.quit(); } public void setViewStateManager(KeyguardViewStateManager viewStateManager) { mViewStateManager = viewStateManager; } public void setLockPatternUtils(LockPatternUtils l) { mLockPatternUtils = l; } @Override public void onPageSwitching(View newPage, int newPageIndex) { if (mViewStateManager != null) { mViewStateManager.onPageSwitching(newPage, newPageIndex); } } @Override public void onPageSwitched(View newPage, int newPageIndex) { boolean showingClock = false; if (newPage instanceof ViewGroup) { ViewGroup vg = (ViewGroup) newPage; if (vg.getChildAt(0) instanceof KeyguardStatusView) { showingClock = true; } } if (newPage != null && findClockInHierarchy(newPage) == (FLAG_HAS_LOCAL_HOUR | FLAG_HAS_LOCAL_MINUTE)) { showingClock = true; } // Disable the status bar clock if we're showing the default status widget if (showingClock) { setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_CLOCK); } else { setSystemUiVisibility(getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK); } // Extend the display timeout if the user switches pages if (mPage != newPageIndex) { int oldPageIndex = mPage; mPage = newPageIndex; userActivity(); KeyguardWidgetFrame oldWidgetPage = getWidgetPageAt(oldPageIndex); if (oldWidgetPage != null) { oldWidgetPage.onActive(false); } KeyguardWidgetFrame newWidgetPage = getWidgetPageAt(newPageIndex); if (newWidgetPage != null) { newWidgetPage.onActive(true); newWidgetPage.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); newWidgetPage.requestAccessibilityFocus(); } if (mParent != null && AccessibilityManager.getInstance(mContext).isEnabled()) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_SCROLLED); onInitializeAccessibilityEvent(event); onPopulateAccessibilityEvent(event); mParent.requestSendAccessibilityEvent(this, event); } } if (mViewStateManager != null) { mViewStateManager.onPageSwitched(newPage, newPageIndex); } } @Override public void onPageBeginWarp() { showOutlinesAndSidePages(); mViewStateManager.onPageBeginWarp(); } @Override public void onPageEndWarp() { // if we're moving to the warp page, then immediately hide the other widgets. int duration = getPageWarpIndex() == getNextPage() ? 0 : -1; animateOutlinesAndSidePages(false, duration); mViewStateManager.onPageEndWarp(); } @Override public void sendAccessibilityEvent(int eventType) { if (eventType != AccessibilityEvent.TYPE_VIEW_SCROLLED || isPageMoving()) { super.sendAccessibilityEvent(eventType); } } private void updateWidgetFramesImportantForAccessibility() { final int pageCount = getPageCount(); for (int i = 0; i < pageCount; i++) { KeyguardWidgetFrame frame = getWidgetPageAt(i); updateWidgetFrameImportantForAccessibility(frame); } } private void updateWidgetFrameImportantForAccessibility(KeyguardWidgetFrame frame) { if (frame.getContentAlpha() <= 0) { frame.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); } else { frame.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } } private void userActivity() { if (mCallbacks != null) { mCallbacks.onUserActivityTimeoutChanged(); mCallbacks.userActivity(); } } @Override public boolean onTouchEvent(MotionEvent ev) { return captureUserInteraction(ev) || super.onTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return captureUserInteraction(ev) || super.onInterceptTouchEvent(ev); } private boolean captureUserInteraction(MotionEvent ev) { KeyguardWidgetFrame currentWidgetPage = getWidgetPageAt(getCurrentPage()); return currentWidgetPage != null && currentWidgetPage.onUserInteraction(ev); } public void showPagingFeedback() { // Nothing yet. } public long getUserActivityTimeout() { View page = getPageAt(mPage); if (page instanceof ViewGroup) { ViewGroup vg = (ViewGroup) page; View view = vg.getChildAt(0); if (!(view instanceof KeyguardStatusView) && !(view instanceof KeyguardMultiUserSelectorView)) { return CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT; } } return -1; } public void setCallbacks(Callbacks callbacks) { mCallbacks = callbacks; } public interface Callbacks { public void userActivity(); public void onUserActivityTimeoutChanged(); public void onAddView(View v); public void onRemoveView(View v, boolean deletePermanently); public void onRemoveViewAnimationCompleted(); } public void addWidget(View widget) { addWidget(widget, -1); } public void onRemoveView(View v, final boolean deletePermanently) { final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); if (mCallbacks != null) { mCallbacks.onRemoveView(v, deletePermanently); } mBackgroundWorkerHandler.post(new Runnable() { @Override public void run() { mLockPatternUtils.removeAppWidget(appWidgetId); } }); } @Override public void onRemoveViewAnimationCompleted() { if (mCallbacks != null) { mCallbacks.onRemoveViewAnimationCompleted(); } } public void onAddView(View v, final int index) { final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); final int[] pagesRange = new int[mTempVisiblePagesRange.length]; getVisiblePages(pagesRange); boundByReorderablePages(true, pagesRange); if (mCallbacks != null) { mCallbacks.onAddView(v); } // Subtract from the index to take into account pages before the reorderable // pages (e.g. the "add widget" page) mBackgroundWorkerHandler.post(new Runnable() { @Override public void run() { mLockPatternUtils.addAppWidget(appWidgetId, index - pagesRange[0]); } }); } /* * We wrap widgets in a special frame which handles drawing the over scroll foreground. */ public void addWidget(View widget, int pageIndex) { KeyguardWidgetFrame frame; // All views contained herein should be wrapped in a KeyguardWidgetFrame if (!(widget instanceof KeyguardWidgetFrame)) { frame = new KeyguardWidgetFrame(getContext()); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); lp.gravity = Gravity.TOP; // The framework adds a default padding to AppWidgetHostView. We don't need this padding // for the Keyguard, so we override it to be 0. widget.setPadding(0, 0, 0, 0); frame.addView(widget, lp); // We set whether or not this widget supports vertical resizing. if (widget instanceof AppWidgetHostView) { AppWidgetHostView awhv = (AppWidgetHostView) widget; AppWidgetProviderInfo info = awhv.getAppWidgetInfo(); if ((info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) { frame.setWidgetLockedSmall(false); } else { // Lock the widget to be small. frame.setWidgetLockedSmall(true); if (mCenterSmallWidgetsVertically) { lp.gravity = Gravity.CENTER; } } } } else { frame = (KeyguardWidgetFrame) widget; } ViewGroup.LayoutParams pageLp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); frame.setOnLongClickListener(this); frame.setWorkerHandler(mBackgroundWorkerHandler); if (pageIndex == -1) { addView(frame, pageLp); } else { addView(frame, pageIndex, pageLp); } // Update the frame content description. View content = (widget == frame) ? frame.getContent() : widget; if (content != null) { String contentDescription = mContext.getString( R.string.keyguard_accessibility_widget, content.getContentDescription()); frame.setContentDescription(contentDescription); } updateWidgetFrameImportantForAccessibility(frame); } /** * Use addWidget() instead. * @deprecated */ @Override public void addView(View child, int index) { enforceKeyguardWidgetFrame(child); super.addView(child, index); } /** * Use addWidget() instead. * @deprecated */ @Override public void addView(View child, int width, int height) { enforceKeyguardWidgetFrame(child); super.addView(child, width, height); } /** * Use addWidget() instead. * @deprecated */ @Override public void addView(View child, LayoutParams params) { enforceKeyguardWidgetFrame(child); super.addView(child, params); } /** * Use addWidget() instead. * @deprecated */ @Override public void addView(View child, int index, LayoutParams params) { enforceKeyguardWidgetFrame(child); super.addView(child, index, params); } private void enforceKeyguardWidgetFrame(View child) { if (!(child instanceof KeyguardWidgetFrame)) { throw new IllegalArgumentException( "KeyguardWidgetPager children must be KeyguardWidgetFrames"); } } public KeyguardWidgetFrame getWidgetPageAt(int index) { // This is always a valid cast as we've guarded the ability to return (KeyguardWidgetFrame) getChildAt(index); } protected void onUnhandledTap(MotionEvent ev) { showPagingFeedback(); } @Override protected void onPageBeginMoving() { if (mViewStateManager != null) { mViewStateManager.onPageBeginMoving(); } if (!isReordering(false)) { showOutlinesAndSidePages(); } userActivity(); } @Override protected void onPageEndMoving() { if (mViewStateManager != null) { mViewStateManager.onPageEndMoving(); } // In the reordering case, the pages will be faded appropriately on completion // of the zoom in animation. if (!isReordering(false)) { hideOutlinesAndSidePages(); } } protected void enablePageContentLayers() { int children = getChildCount(); for (int i = 0; i < children; i++) { getWidgetPageAt(i).enableHardwareLayersForContent(); } } protected void disablePageContentLayers() { int children = getChildCount(); for (int i = 0; i < children; i++) { getWidgetPageAt(i).disableHardwareLayersForContent(); } } /* * This interpolator emulates the rate at which the perceived scale of an object changes * as its distance from a camera increases. When this interpolator is applied to a scale * animation on a view, it evokes the sense that the object is shrinking due to moving away * from the camera. */ static class ZInterpolator implements TimeInterpolator { private float focalLength; public ZInterpolator(float foc) { focalLength = foc; } public float getInterpolation(float input) { return (1.0f - focalLength / (focalLength + input)) / (1.0f - focalLength / (focalLength + 1.0f)); } } @Override protected void overScroll(float amount) { acceleratedOverScroll(amount); } float backgroundAlphaInterpolator(float r) { return Math.min(1f, r); } private void updatePageAlphaValues(int screenCenter) { } public float getAlphaForPage(int screenCenter, int index, boolean showSidePages) { if (isWarping()) { return index == getPageWarpIndex() ? 1.0f : 0.0f; } if (showSidePages) { return 1f; } else { return index == mCurrentPage ? 1.0f : 0f; } } public float getOutlineAlphaForPage(int screenCenter, int index, boolean showSidePages) { if (showSidePages) { return getAlphaForPage(screenCenter, index, showSidePages) * KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER; } else { return 0f; } } protected boolean isOverScrollChild(int index, float scrollProgress) { boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; return (isInOverscroll && (index == 0 && scrollProgress < 0 || index == getChildCount() - 1 && scrollProgress > 0)); } @Override protected void screenScrolled(int screenCenter) { mScreenCenter = screenCenter; updatePageAlphaValues(screenCenter); for (int i = 0; i < getChildCount(); i++) { KeyguardWidgetFrame v = getWidgetPageAt(i); if (v == mDragView) continue; if (v != null) { float scrollProgress = getScrollProgress(screenCenter, v, i); v.setCameraDistance(mDensity * CAMERA_DISTANCE); if (isOverScrollChild(i, scrollProgress) && PERFORM_OVERSCROLL_ROTATION) { float pivotX = v.getMeasuredWidth() / 2; float pivotY = v.getMeasuredHeight() / 2; v.setPivotX(pivotX); v.setPivotY(pivotY); v.setRotationY(- OVERSCROLL_MAX_ROTATION * scrollProgress); v.setOverScrollAmount(Math.abs(scrollProgress), scrollProgress < 0); } else { v.setRotationY(0f); v.setOverScrollAmount(0, false); } float alpha = v.getAlpha(); // If the view has 0 alpha, we set it to be invisible so as to prevent // it from accepting touches if (alpha == 0) { v.setVisibility(INVISIBLE); } else if (v.getVisibility() != VISIBLE) { v.setVisibility(VISIBLE); } } } } public boolean isWidgetPage(int pageIndex) { if (pageIndex < 0 || pageIndex >= getChildCount()) { return false; } View v = getChildAt(pageIndex); if (v != null && v instanceof KeyguardWidgetFrame) { KeyguardWidgetFrame kwf = (KeyguardWidgetFrame) v; return kwf.getContentAppWidgetId() != AppWidgetManager.INVALID_APPWIDGET_ID; } return false; } /** * Returns the bounded set of pages that are re-orderable. The range is fully inclusive. */ @Override void boundByReorderablePages(boolean isReordering, int[] range) { if (isReordering) { // Remove non-widget pages from the range while (range[1] >= range[0] && !isWidgetPage(range[1])) { range[1]--; } while (range[0] <= range[1] && !isWidgetPage(range[0])) { range[0]++; } } } protected void reorderStarting() { showOutlinesAndSidePages(); } @Override protected void onStartReordering() { super.onStartReordering(); enablePageContentLayers(); reorderStarting(); } @Override protected void onEndReordering() { super.onEndReordering(); hideOutlinesAndSidePages(); } void showOutlinesAndSidePages() { animateOutlinesAndSidePages(true); } void hideOutlinesAndSidePages() { animateOutlinesAndSidePages(false); } void updateChildrenContentAlpha(float sidePageAlpha) { int count = getChildCount(); for (int i = 0; i < count; i++) { KeyguardWidgetFrame child = getWidgetPageAt(i); if (i != mCurrentPage) { child.setBackgroundAlpha(sidePageAlpha); child.setContentAlpha(0f); } else { child.setBackgroundAlpha(0f); child.setContentAlpha(1f); } } } public void showInitialPageHints() { mShowingInitialHints = true; updateChildrenContentAlpha(KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER); } @Override void setCurrentPage(int currentPage) { super.setCurrentPage(currentPage); updateChildrenContentAlpha(0.0f); updateWidgetFramesImportantForAccessibility(); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mHasMeasure = false; } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mLastWidthMeasureSpec = widthMeasureSpec; mLastHeightMeasureSpec = heightMeasureSpec; int maxChallengeTop = -1; View parent = (View) getParent(); boolean challengeShowing = false; // Widget pages need to know where the top of the sliding challenge is so that they // now how big the widget should be when the challenge is up. We compute it here and // then propagate it to each of our children. if (parent.getParent() instanceof SlidingChallengeLayout) { SlidingChallengeLayout scl = (SlidingChallengeLayout) parent.getParent(); int top = scl.getMaxChallengeTop(); // This is a bit evil, but we need to map a coordinate relative to the SCL into a // coordinate relative to our children, hence we subtract the top padding.s maxChallengeTop = top - getPaddingTop(); challengeShowing = scl.isChallengeShowing(); int count = getChildCount(); for (int i = 0; i < count; i++) { KeyguardWidgetFrame frame = getWidgetPageAt(i); frame.setMaxChallengeTop(maxChallengeTop); // On the very first measure pass, if the challenge is showing, we need to make sure // that the widget on the current page is small. if (challengeShowing && i == mCurrentPage && !mHasMeasure) { frame.shrinkWidget(true); } } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); mHasMeasure = true; } void animateOutlinesAndSidePages(final boolean show) { animateOutlinesAndSidePages(show, -1); } public void setWidgetToResetOnPageFadeOut(int widget) { mWidgetToResetAfterFadeOut = widget; } public int getWidgetToResetOnPageFadeOut() { return mWidgetToResetAfterFadeOut; } void animateOutlinesAndSidePages(final boolean show, int duration) { if (mChildrenOutlineFadeAnimation != null) { mChildrenOutlineFadeAnimation.cancel(); mChildrenOutlineFadeAnimation = null; } int count = getChildCount(); PropertyValuesHolder alpha; ArrayList anims = new ArrayList(); if (duration == -1) { duration = show ? CHILDREN_OUTLINE_FADE_IN_DURATION : CHILDREN_OUTLINE_FADE_OUT_DURATION; } int curPage = getNextPage(); for (int i = 0; i < count; i++) { float finalContentAlpha; if (show) { finalContentAlpha = getAlphaForPage(mScreenCenter, i, true); } else if (!show && i == curPage) { finalContentAlpha = 1f; } else { finalContentAlpha = 0f; } KeyguardWidgetFrame child = getWidgetPageAt(i); alpha = PropertyValuesHolder.ofFloat("contentAlpha", finalContentAlpha); ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(child, alpha); anims.add(a); float finalOutlineAlpha = show ? getOutlineAlphaForPage(mScreenCenter, i, true) : 0f; child.fadeFrame(this, show, finalOutlineAlpha, duration); } mChildrenOutlineFadeAnimation = new AnimatorSet(); mChildrenOutlineFadeAnimation.playTogether(anims); mChildrenOutlineFadeAnimation.setDuration(duration); mChildrenOutlineFadeAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { if (show) { enablePageContentLayers(); } } @Override public void onAnimationEnd(Animator animation) { if (!show) { disablePageContentLayers(); KeyguardWidgetFrame frame = getWidgetPageAt(mWidgetToResetAfterFadeOut); if (frame != null && !(frame == getWidgetPageAt(mCurrentPage) && mViewStateManager.isChallengeOverlapping())) { frame.resetSize(); } mWidgetToResetAfterFadeOut = -1; mShowingInitialHints = false; } updateWidgetFramesImportantForAccessibility(); } }); mChildrenOutlineFadeAnimation.start(); } @Override public boolean onLongClick(View v) { // Disallow long pressing to reorder if the challenge is showing boolean isChallengeOverlapping = mViewStateManager.isChallengeShowing() && mViewStateManager.isChallengeOverlapping(); if (!isChallengeOverlapping && startReordering()) { return true; } return false; } public void removeWidget(View view) { if (view instanceof KeyguardWidgetFrame) { removeView(view); } else { // Assume view was wrapped by a KeyguardWidgetFrame in KeyguardWidgetPager#addWidget(). // This supports legacy hard-coded "widgets" like KeyguardTransportControlView. int pos = getWidgetPageIndex(view); if (pos != -1) { KeyguardWidgetFrame frame = (KeyguardWidgetFrame) getChildAt(pos); frame.removeView(view); removeView(frame); } else { Slog.w(TAG, "removeWidget() can't find:" + view); } } } public int getWidgetPageIndex(View view) { if (view instanceof KeyguardWidgetFrame) { return indexOfChild(view); } else { // View was wrapped by a KeyguardWidgetFrame by KeyguardWidgetPager#addWidget() return indexOfChild((KeyguardWidgetFrame)view.getParent()); } } @Override protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) { KeyguardWidgetFrame child = getWidgetPageAt(viewIndex); child.setIsHoveringOverDeleteDropTarget(isHovering); } // ChallengeLayout.OnBouncerStateChangedListener @Override public void onBouncerStateChanged(boolean bouncerActive) { if (bouncerActive) { zoomOutToBouncer(); } else { zoomInFromBouncer(); } } void setBouncerAnimationDuration(int duration) { mBouncerZoomInOutDuration = duration; } // Zoom in after the bouncer is dismissed void zoomInFromBouncer() { if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) { mZoomInOutAnim.cancel(); } final View currentPage = getPageAt(getCurrentPage()); if (currentPage.getScaleX() < 1f || currentPage.getScaleY() < 1f) { mZoomInOutAnim = new AnimatorSet(); mZoomInOutAnim.playTogether( ObjectAnimator.ofFloat(currentPage, "scaleX", 1f), ObjectAnimator.ofFloat(currentPage , "scaleY", 1f)); mZoomInOutAnim.setDuration(mBouncerZoomInOutDuration); mZoomInOutAnim.setInterpolator(new DecelerateInterpolator(1.5f)); mZoomInOutAnim.start(); } if (currentPage instanceof KeyguardWidgetFrame) { ((KeyguardWidgetFrame)currentPage).onBouncerShowing(false); } } // Zoom out after the bouncer is initiated void zoomOutToBouncer() { if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) { mZoomInOutAnim.cancel(); } int curPage = getCurrentPage(); View currentPage = getPageAt(curPage); if (shouldSetTopAlignedPivotForWidget(curPage)) { currentPage.setPivotY(0); // Note: we are working around the issue that setting the x-pivot to the same value as it // was does not actually work. currentPage.setPivotX(0); currentPage.setPivotX(currentPage.getMeasuredWidth() / 2); } if (currentPage != null && (!(currentPage.getScaleX() < 1f || currentPage.getScaleY() < 1f))) { mZoomInOutAnim = new AnimatorSet(); mZoomInOutAnim.playTogether( ObjectAnimator.ofFloat(currentPage, "scaleX", BOUNCER_SCALE_FACTOR), ObjectAnimator.ofFloat(currentPage, "scaleY", BOUNCER_SCALE_FACTOR)); mZoomInOutAnim.setDuration(mBouncerZoomInOutDuration); mZoomInOutAnim.setInterpolator(new DecelerateInterpolator(1.5f)); mZoomInOutAnim.start(); } if (currentPage instanceof KeyguardWidgetFrame) { ((KeyguardWidgetFrame)currentPage).onBouncerShowing(true); } } void setAddWidgetEnabled(boolean enabled) { if (mAddWidgetView != null && enabled) { addView(mAddWidgetView, 0); // We need to force measure the PagedView so that the calls to update the scroll // position below work measure(mLastWidthMeasureSpec, mLastHeightMeasureSpec); // Bump up the current page to account for the addition of the new page setCurrentPage(mCurrentPage + 1); mAddWidgetView = null; } else if (mAddWidgetView == null && !enabled) { View addWidget = findViewById(R.id.keyguard_add_widget); if (addWidget != null) { mAddWidgetView = addWidget; removeView(addWidget); } } } boolean isAddPage(int pageIndex) { View v = getChildAt(pageIndex); return v != null && v.getId() == R.id.keyguard_add_widget; } boolean isCameraPage(int pageIndex) { View v = getChildAt(pageIndex); return v != null && v instanceof CameraWidgetFrame; } @Override protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) { return !isCameraPage(childIndex) && super.shouldSetTopAlignedPivotForWidget(childIndex); } /** * Search given {@link View} hierarchy for {@link TextClock} instances that * show various time components. Returns combination of * {@link #FLAG_HAS_LOCAL_HOUR} and {@link #FLAG_HAS_LOCAL_MINUTE}. */ private static int findClockInHierarchy(View view) { if (view instanceof TextClock) { return getClockFlags((TextClock) view); } else if (view instanceof ViewGroup) { int flags = 0; final ViewGroup group = (ViewGroup) view; final int size = group.getChildCount(); for (int i = 0; i < size; i++) { flags |= findClockInHierarchy(group.getChildAt(i)); } return flags; } else { return 0; } } /** * Return combination of {@link #FLAG_HAS_LOCAL_HOUR} and * {@link #FLAG_HAS_LOCAL_MINUTE} describing the time represented described * by the given {@link TextClock}. */ private static int getClockFlags(TextClock clock) { int flags = 0; final String timeZone = clock.getTimeZone(); if (timeZone != null && !TimeZone.getDefault().equals(TimeZone.getTimeZone(timeZone))) { // Ignore clocks showing another timezone return 0; } final CharSequence format = clock.getFormat(); final char hour = clock.is24HourModeEnabled() ? DateFormat.HOUR_OF_DAY : DateFormat.HOUR; if (DateFormat.hasDesignator(format, hour)) { flags |= FLAG_HAS_LOCAL_HOUR; } if (DateFormat.hasDesignator(format, DateFormat.MINUTE)) { flags |= FLAG_HAS_LOCAL_MINUTE; } return flags; } public void handleExternalCameraEvent(MotionEvent event) { beginCameraEvent(); int cameraPage = getPageCount() - 1; boolean endWarp = false; if (isCameraPage(cameraPage) || mCameraEventInProgress) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // Once we start dispatching camera events, we must continue to do so // to keep event dispatch happy. mCameraEventInProgress = true; userActivity(); startPageWarp(cameraPage); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mCameraEventInProgress = false; endWarp = isWarping(); break; } dispatchTouchEvent(event); // This has to happen after the event has been handled by the real widget pager if (endWarp) stopPageWarp(); } endCameraEvent(); } }