did not properly trigger abortion of the camera animation. Also minorly simplified the touch slop calculation BUG: 12036618 Change-Id: If01b48c815200066b32a37ffc44949edde1376f9
968 lines
34 KiB
Java
968 lines
34 KiB
Java
/*
|
|
* 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<Animator> anims = new ArrayList<Animator>();
|
|
|
|
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.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();
|
|
break;
|
|
case MotionEvent.ACTION_UP:
|
|
case MotionEvent.ACTION_CANCEL:
|
|
mCameraEventInProgress = false;
|
|
break;
|
|
}
|
|
dispatchTouchEvent(event);
|
|
}
|
|
endCameraEvent();
|
|
}
|
|
|
|
}
|