Using paged view for all-apps tabs

Bug: 72811152
Change-Id: I0cca426d935f079c923b93fe3d4399f87778fe95
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index bb137b0..ccb0a95 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -59,7 +59,7 @@
  * An abstraction of the original Workspace which supports browsing through a
  * sequential list of "pages"
  */
-public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
+public abstract class PagedView<T extends View & PageIndicator> extends ViewGroup {
     private static final String TAG = "PagedView";
     private static final boolean DEBUG = false;
     protected static final int INVALID_PAGE = -1;
@@ -154,7 +154,7 @@
 
     // Page Indicator
     @Thunk int mPageIndicatorViewId;
-    protected PageIndicator mPageIndicator;
+    protected T mPageIndicator;
 
     // Reordering
     // We use the min scale to determine how much to expand the actually PagedView measured
@@ -224,7 +224,6 @@
         mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density);
         mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density);
         mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density);
-        setOnHierarchyChangeListener(this);
         setWillNotDraw(false);
     }
 
@@ -235,9 +234,9 @@
 
     public void initParentViews(View parent) {
         if (mPageIndicatorViewId > -1) {
-            mPageIndicator = (PageIndicator) parent.findViewById(mPageIndicatorViewId);
+            mPageIndicator = parent.findViewById(mPageIndicatorViewId);
             mPageIndicator.setMarkersCount(getChildCount());
-            mPageIndicator.setContentDescription(getPageIndicatorDescription());
+            mPageIndicator.setPageDescription(getPageIndicatorDescription());
         }
     }
 
@@ -282,7 +281,7 @@
         }
     }
 
-    public PageIndicator getPageIndicator() {
+    public T getPageIndicator() {
         return mPageIndicator;
     }
 
@@ -384,7 +383,7 @@
     private void updatePageIndicator() {
         // Update the page indicator (when we aren't reordering)
         if (mPageIndicator != null) {
-            mPageIndicator.setContentDescription(getPageIndicatorDescription());
+            mPageIndicator.setPageDescription(getPageIndicatorDescription());
             if (!isReordering(false)) {
                 mPageIndicator.setActiveMarker(getNextPage());
             }
@@ -748,63 +747,24 @@
         requestLayout();
     }
 
-    @Override
-    public void onChildViewAdded(View parent, View child) {
-        // Update the page indicator, we don't update the page indicator as we
-        // add/remove pages
-        if (mPageIndicator != null && !isReordering(false)) {
-            mPageIndicator.addMarker();
+    private void dispatchPageCountChanged() {
+        if (mPageIndicator != null) {
+            mPageIndicator.setMarkersCount(getChildCount());
         }
-
         // This ensures that when children are added, they get the correct transforms / alphas
         // in accordance with any scroll effects.
         invalidate();
     }
 
     @Override
-    public void onChildViewRemoved(View parent, View child) {
+    public void onViewAdded(View child) {
+        dispatchPageCountChanged();
+    }
+
+    @Override
+    public void onViewRemoved(View child) {
         mCurrentPage = validateNewPage(mCurrentPage);
-        invalidate();
-    }
-
-    private void removeMarkerForView() {
-        // Update the page indicator, we don't update the page indicator as we
-        // add/remove pages
-        if (mPageIndicator != null && !isReordering(false)) {
-            mPageIndicator.removeMarker();
-        }
-    }
-
-    @Override
-    public void removeView(View v) {
-        // XXX: We should find a better way to hook into this before the view
-        // gets removed form its parent...
-        removeMarkerForView();
-        super.removeView(v);
-    }
-    @Override
-    public void removeViewInLayout(View v) {
-        // XXX: We should find a better way to hook into this before the view
-        // gets removed form its parent...
-        removeMarkerForView();
-        super.removeViewInLayout(v);
-    }
-    @Override
-    public void removeViewAt(int index) {
-        // XXX: We should find a better way to hook into this before the view
-        // gets removed form its parent...
-        removeMarkerForView();
-        super.removeViewAt(index);
-    }
-    @Override
-    public void removeAllViewsInLayout() {
-        // Update the page indicator, we don't update the page indicator as we
-        // add/remove pages
-        if (mPageIndicator != null) {
-            mPageIndicator.setMarkersCount(0);
-        }
-
-        super.removeAllViewsInLayout();
+        dispatchPageCountChanged();
     }
 
     protected int getChildOffset(int index) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 2d24028..26b91f2 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -75,6 +75,7 @@
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.uioverrides.UiFactory;
@@ -100,10 +101,9 @@
  * Each page contains a number of icons, folders or widgets the user can
  * interact with. A workspace is meant to be used with a fixed width only.
  */
-public class Workspace extends PagedView
+public class Workspace extends PagedView<WorkspacePageIndicator>
         implements DropTarget, DragSource, View.OnTouchListener,
-        DragController.DragListener, ViewGroup.OnHierarchyChangeListener,
-        Insettable, LauncherStateManager.StateHandler {
+        DragController.DragListener, Insettable, LauncherStateManager.StateHandler {
     private static final String TAG = "Launcher.Workspace";
 
     /** The value that {@link #mTransitionProgress} must be greater than for
@@ -273,9 +273,7 @@
 
         mWallpaperOffset = new WallpaperOffsetInterpolator(this);
 
-        setOnHierarchyChangeListener(this);
         setHapticFeedbackEnabled(false);
-
         initWorkspace();
 
         // Disable multitouch across the workspace/all apps/customize tray
@@ -463,7 +461,7 @@
     }
 
     @Override
-    public void onChildViewAdded(View parent, View child) {
+    public void onViewAdded(View child) {
         if (!(child instanceof CellLayout)) {
             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
         }
@@ -471,7 +469,7 @@
         cl.setOnInterceptTouchListener(this);
         cl.setClickable(true);
         cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-        super.onChildViewAdded(parent, child);
+        super.onViewAdded(child);
     }
 
     boolean isTouchActive() {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index d4277db..a7eae39 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -21,8 +21,6 @@
 import android.os.Process;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
@@ -50,7 +48,6 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
@@ -59,12 +56,8 @@
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.views.BottomUserEducationView;
 
-import java.util.List;
-import java.util.Set;
-
 /**
  * The all apps view container.
  */
@@ -80,9 +73,8 @@
 
     private SearchUiManager mSearchUiManager;
     private View mSearchContainer;
-    private InterceptingViewPager mViewPager;
+    private AllAppsPagedView mViewPager;
     private FloatingHeaderView mHeader;
-    private TabsPagerAdapter mTabsPagerAdapter;
 
     private SpannableStringBuilder mSearchQueryBuilder = null;
 
@@ -184,7 +176,7 @@
     }
 
     public AllAppsRecyclerView getActiveRecyclerView() {
-        if (!mUsingTabs || mViewPager.getCurrentItem() == 0) {
+        if (!mUsingTabs || mViewPager.getNextPage() == 0) {
             return mAH[AdapterHolder.MAIN].recyclerView;
         } else {
             return mAH[AdapterHolder.WORK].recyclerView;
@@ -329,9 +321,8 @@
         if (mUsingTabs) {
             mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
             mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
-            setupWorkProfileTabs();
+            onTabChanged(mViewPager.getNextPage());
         } else {
-            mTabsPagerAdapter = null;
             mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
             mAH[AdapterHolder.WORK].recyclerView = null;
         }
@@ -355,50 +346,35 @@
         int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
         View newView = LayoutInflater.from(getContext()).inflate(layout, this, false);
         addView(newView, index);
-        mViewPager = showTabs ? (InterceptingViewPager) newView : null;
+        if (showTabs) {
+            mViewPager = (AllAppsPagedView) newView;
+            mViewPager.initParentViews(this);
+            mViewPager.getPageIndicator().setContainerView(this);
+        } else {
+            mViewPager = null;
+        }
     }
 
     public View getRecyclerViewContainer() {
         return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
     }
 
-    private void setupWorkProfileTabs() {
-        if (mTabsPagerAdapter != null) {
-            return;
+    public void onTabChanged(int pos) {
+        mHeader.setMainActive(pos == 0);
+        reset();
+        applyTouchDelegate();
+        if (mAH[pos].recyclerView != null) {
+            mAH[pos].recyclerView.bindFastScrollbar();
+
+            findViewById(R.id.tab_personal)
+                    .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
+            findViewById(R.id.tab_work)
+                    .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
+
         }
-        final PersonalWorkSlidingTabStrip tabs = findViewById(R.id.tabs);
-        mViewPager.setAdapter(mTabsPagerAdapter = new TabsPagerAdapter());
-        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
-
-            @Override
-            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-                tabs.updateIndicatorPosition(position, positionOffset);
-            }
-
-            @Override
-            public void onPageSelected(int pos) {
-                tabs.updateTabTextColor(pos);
-                mHeader.setMainActive(pos == 0);
-                reset();
-                applyTouchDelegate();
-                if (mAH[pos].recyclerView != null) {
-                    mAH[pos].recyclerView.bindFastScrollbar();
-                }
-                if (pos == AdapterHolder.WORK) {
-                    BottomUserEducationView.showIfNeeded(mLauncher);
-                }
-            }
-
-            @Override
-            public void onPageScrollStateChanged(int state) {
-            }
-        });
-        mAH[AdapterHolder.MAIN].recyclerView.bindFastScrollbar();
-
-        findViewById(R.id.tab_personal)
-                .setOnClickListener((View view) -> mViewPager.setCurrentItem(0));
-        findViewById(R.id.tab_work)
-                .setOnClickListener((View view) -> mViewPager.setCurrentItem(1));
+        if (pos == AdapterHolder.WORK) {
+            BottomUserEducationView.showIfNeeded(mLauncher);
+        }
     }
 
     public AlphabeticalAppsList getApps() {
@@ -519,37 +495,4 @@
                     && verticalFadingEdge);
         }
     }
-
-    private class TabsPagerAdapter extends PagerAdapter {
-        @Override
-        public int getCount() {
-            return 2;
-        }
-
-        @Override
-        public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
-            return view == object;
-        }
-
-        @NonNull
-        @Override
-        public Object instantiateItem(@NonNull ViewGroup container, int position) {
-            if (position == 0) {
-                return mAH[AdapterHolder.MAIN].recyclerView;
-            } else {
-                return mAH[AdapterHolder.WORK].recyclerView;
-            }
-        }
-
-        @Nullable
-        @Override
-        public CharSequence getPageTitle(int position) {
-            if (position == 0) {
-                return getResources().getString(R.string.all_apps_personal_tab);
-            } else {
-                return getResources().getString(R.string.all_apps_work_tab);
-            }
-        }
-    }
-
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
new file mode 100644
index 0000000..86186fd
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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.launcher3.allapps;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+
+public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
+
+    public AllAppsPagedView(Context context) {
+        this(context, null);
+    }
+
+    public AllAppsPagedView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AllAppsPagedView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected String getCurrentPageDescription() {
+        return getResources().getString(
+                getNextPage() == 0 ? R.string.all_apps_personal_tab : R.string.all_apps_work_tab);
+    }
+
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        super.onScrollChanged(l, t, oldl, oldt);
+        mPageIndicator.setScroll(l, mMaxScrollX);
+    }
+}
diff --git a/src/com/android/launcher3/allapps/InterceptingViewPager.java b/src/com/android/launcher3/allapps/InterceptingViewPager.java
deleted file mode 100644
index 3524ca9..0000000
--- a/src/com/android/launcher3/allapps/InterceptingViewPager.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2017 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.launcher3.allapps;
-
-import android.content.Context;
-import android.graphics.PointF;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-
-import com.android.launcher3.touch.SwipeDetector;
-
-import static android.view.MotionEvent.INVALID_POINTER_ID;
-
-
-public class InterceptingViewPager extends ViewPager {
-
-
-    private final PointF mDownPos = new PointF();
-    private final PointF mLastPos = new PointF();
-    private final int mSlop;
-    private int mActivePointerId = INVALID_POINTER_ID;
-
-    public InterceptingViewPager(@NonNull Context context) {
-        super(context);
-        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
-    }
-
-    public InterceptingViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        boolean result = super.onInterceptTouchEvent(ev);
-        if (!result) {
-            switch (ev.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN:
-                    mActivePointerId = ev.getPointerId(0);
-                    mDownPos.set(ev.getX(), ev.getY());
-                    mLastPos.set(mDownPos);
-                    break;
-                case MotionEvent.ACTION_POINTER_UP:
-                    int ptrIdx = ev.getActionIndex();
-                    int ptrId = ev.getPointerId(ptrIdx);
-                    if (ptrId == mActivePointerId) {
-                        final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
-                        mDownPos.set(
-                                ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
-                                ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
-                        mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
-                        mActivePointerId = ev.getPointerId(newPointerIdx);
-                    }
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    int pointerIndex = ev.findPointerIndex(mActivePointerId);
-                    if (pointerIndex == INVALID_POINTER_ID) {
-                        break;
-                    }
-                    float deltaX = ev.getX() - mDownPos.x;
-                    float deltaY = ev.getY() - mDownPos.y;
-                    if (Math.abs(deltaX) > mSlop && Math.abs(deltaX) > Math.abs(deltaY)) {
-                        return true;
-                    }
-                    mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
-                    break;
-                default:
-                    break;
-            }
-        }
-        return result;
-    }
-
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 05cd655..b42d4cd 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -28,13 +28,13 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.util.Themes;
 
 /**
  * Supports two indicator colors, dedicated for personal and work tabs.
  */
-public class PersonalWorkSlidingTabStrip extends LinearLayout {
+public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageIndicator {
     private static final int POSITION_PERSONAL = 0;
     private static final int POSITION_WORK = 1;
 
@@ -51,6 +51,9 @@
     private float mIndicatorOffset;
     private int mSelectedPosition = 0;
 
+    private AllAppsContainerView mContainerView;
+    private int mLastActivePage = 0;
+
     public PersonalWorkSlidingTabStrip(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         setOrientation(HORIZONTAL);
@@ -71,13 +74,13 @@
         mSharedPreferences = Launcher.getLauncher(getContext()).getSharedPrefs();
     }
 
-    public void updateIndicatorPosition(int position, float positionOffset) {
+    private void updateIndicatorPosition(int position, float positionOffset) {
         mIndicatorPosition = position;
         mIndicatorOffset = positionOffset;
         updateIndicatorPosition();
     }
 
-    public void updateTabTextColor(int pos) {
+    private void updateTabTextColor(int pos) {
         mSelectedPosition = pos;
         for (int i = 0; i < getChildCount(); i++) {
             Button tab = (Button) getChildAt(i);
@@ -129,8 +132,6 @@
 
         float y = getHeight() - mDividerPaint.getStrokeWidth();
         canvas.drawLine(getPaddingLeft(), y, getWidth() - getPaddingRight(), y, mDividerPaint);
-
-        final float middleX = getWidth() / 2.0f;
         canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
             mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
     }
@@ -153,4 +154,35 @@
             v.setPressed(false);
         });
     }
+
+    @Override
+    public void setScroll(int currentScroll, int totalScroll) {
+        if (currentScroll == totalScroll) {
+            updateIndicatorPosition(1, 0);
+        } else if (totalScroll > 0) {
+            updateIndicatorPosition(0, ((float) currentScroll) / totalScroll);
+        }
+    }
+
+    @Override
+    public void setActiveMarker(int activePage) {
+        updateTabTextColor(activePage);
+        if (mContainerView != null && mLastActivePage != activePage) {
+            mContainerView.onTabChanged(activePage);
+        }
+        mLastActivePage = activePage;
+    }
+
+    public void setContainerView(AllAppsContainerView containerView) {
+        mContainerView = containerView;
+    }
+
+    @Override
+    public void setMarkersCount(int numMarkers) { }
+
+    @Override
+    public void setPageDescription(CharSequence description) {
+        // We don't want custom page description as the tab-bar already has two tabs with their
+        // own descriptions.
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index f4462aa..a468cb5 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -44,14 +44,14 @@
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
-import com.android.launcher3.pageindicators.PageIndicator;
+import com.android.launcher3.pageindicators.PageIndicatorDots;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Map;
 
-public class FolderPagedView extends PagedView {
+public class FolderPagedView extends PagedView<PageIndicatorDots> {
 
     private static final String TAG = "FolderPagedView";
 
@@ -89,8 +89,6 @@
     private Folder mFolder;
     private PagedFolderKeyEventListener mKeyListener;
 
-    private PageIndicator mPageIndicator;
-
     public FolderPagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
         InvariantDeviceProfile profile = LauncherAppState.getIDP(context);
diff --git a/src/com/android/launcher3/pageindicators/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicator.java
index 5e3d216..3ce7291 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicator.java
@@ -15,41 +15,16 @@
  */
 package com.android.launcher3.pageindicators;
 
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-
 /**
  * Base class for a page indicator.
  */
-public abstract class PageIndicator extends View {
+public interface PageIndicator {
 
-    protected int mNumPages = 1;
+    void setScroll(int currentScroll, int totalScroll);
 
-    public PageIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
+    void setActiveMarker(int activePage);
 
-    public void setScroll(int currentScroll, int totalScroll) {}
+    void setMarkersCount(int numMarkers);
 
-    public void setActiveMarker(int activePage) {}
-
-    public void addMarker() {
-        mNumPages++;
-        onPageCountChanged();
-    }
-
-    public void removeMarker() {
-        mNumPages--;
-        onPageCountChanged();
-    }
-
-    public void setMarkersCount(int numMarkers) {
-        mNumPages = numMarkers;
-        onPageCountChanged();
-    }
-
-    protected void onPageCountChanged() {}
-
-    public void setShouldAutoHide(boolean shouldAutoHide) {}
+    void setPageDescription(CharSequence description);
 }
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 6276c80..524ec3c 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -43,7 +43,7 @@
  * {@link PageIndicator} which shows dots per page. The active page is shown with the current
  * accent color.
  */
-public class PageIndicatorDots extends PageIndicator {
+public class PageIndicatorDots extends View implements PageIndicator {
 
     private static final float SHIFT_PER_ANIMATION = 0.5f;
     private static final float SHIFT_THRESHOLD = 0.1f;
@@ -79,6 +79,7 @@
     private final int mInActiveColor;
     private final boolean mIsRtl;
 
+    private int mNumPages;
     private int mActivePage;
 
     /**
@@ -221,11 +222,17 @@
     }
 
     @Override
-    protected void onPageCountChanged() {
+    public void setMarkersCount(int numMarkers) {
+        mNumPages = numMarkers;
         requestLayout();
     }
 
     @Override
+    public void setPageDescription(CharSequence description) {
+        setContentDescription(description);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // Add extra spacing of mDotRadius on all sides so than entry animation could be run.
         int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ?
diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
index ee4e4ee..4fc7d8a 100644
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@@ -41,7 +41,8 @@
  *
  * The fraction is 1 / number of pages and the position is based on the progress of the page scroll.
  */
-public class WorkspacePageIndicator extends PageIndicator implements Insettable, OnClickListener {
+public class WorkspacePageIndicator extends View
+        implements Insettable, OnClickListener, PageIndicator {
 
     private static final int LINE_ANIMATE_DURATION = ViewConfiguration.getScrollBarFadeDuration();
     private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
@@ -185,14 +186,18 @@
     }
 
     @Override
-    public void setActiveMarker(int activePage) {
+    public void setActiveMarker(int activePage) { }
+
+    @Override
+    public void setMarkersCount(int numMarkers) {
+        if (Float.compare(numMarkers, mNumPagesFloat) != 0) {
+            animateToNumPages(numMarkers);
+        }
     }
 
     @Override
-    protected void onPageCountChanged() {
-        if (Float.compare(mNumPages, mNumPagesFloat) != 0) {
-            animateToNumPages(mNumPages);
-        }
+    public void setPageDescription(CharSequence description) {
+        setContentDescription(description);
     }
 
     public void setShouldAutoHide(boolean shouldAutoHide) {