/* * 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.internal.policy.impl.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.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 com.android.internal.widget.LockPatternUtils; import java.util.ArrayList; 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; 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; // 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; 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 showingStatusWidget = false; if (newPage instanceof ViewGroup) { ViewGroup vg = (ViewGroup) newPage; if (vg.getChildAt(0) instanceof KeyguardStatusView) { showingStatusWidget = true; } } // Disable the status bar clock if we're showing the default status widget if (showingStatusWidget) { 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.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 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); } public void addWidget(View widget) { addWidget(widget, -1); } public void onRemoveView(View v) { final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); if (mCallbacks != null) { mCallbacks.onRemoveView(v); } mBackgroundWorkerHandler.post(new Runnable() { @Override public void run() { mLockPatternUtils.removeAppWidget(appWidgetId); } }); } 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( com.android.internal.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) { return 1f; } public float getOutlineAlphaForPage(int screenCenter, int index) { return getAlphaForPage(screenCenter, index) * KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER; } 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); } public void showInitialPageHints() { int count = getChildCount(); for (int i = 0; i < count; i++) { KeyguardWidgetFrame child = getWidgetPageAt(i); if (i != mCurrentPage) { child.fadeFrame(this, true, KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER, CHILDREN_OUTLINE_FADE_IN_DURATION); child.setContentAlpha(0f); } else { child.setBackgroundAlpha(0f); child.setContentAlpha(1f); } } } public void showSidePageHints() { animateOutlinesAndSidePages(true, -1); } @Override void setCurrentPage(int currentPage) { super.setCurrentPage(currentPage); updateWidgetFramesImportantForAccessibility(); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mHasMeasure = false; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); } protected void onMeasure(int widthMeasureSpec, int 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(); } } } 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); } 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) : 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; } 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(); } } // 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(); } } boolean isAddPage(int pageIndex) { View v = getChildAt(pageIndex); return v != null && v.getId() == com.android.internal.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); } }