diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-11-17 16:38:15 -0500 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-11-17 16:38:15 -0500 |
commit | 6a65f2da209bff03cb0eb6da309710ac6ee5026d (patch) | |
tree | 48e2090e716d4178378cb0599fc5d9cffbcf3f63 /android/support | |
parent | 46c77c203439b3b37c99d09e326df4b1fe08c10b (diff) | |
download | android-28-6a65f2da209bff03cb0eb6da309710ac6ee5026d.tar.gz |
Import Android SDK Platform P [4456821]
/google/data/ro/projects/android/fetch_artifact \
--bid 4456821 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4456821.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: I2d206b200d7952f899a5d1647ab532638cc8dd43
Diffstat (limited to 'android/support')
97 files changed, 2110 insertions, 762 deletions
diff --git a/android/support/LibraryGroups.java b/android/support/LibraryGroups.java new file mode 100644 index 00000000..feaefbc6 --- /dev/null +++ b/android/support/LibraryGroups.java @@ -0,0 +1,30 @@ +/* + * Copyright 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 android.support; + +/** + * The list of maven group names of all the libraries in this project. + */ +public class LibraryGroups { + public static final String SUPPORT = "com.android.support"; + public static final String ROOM = "android.arch.persistence.room"; + public static final String PERSISTENCE = "android.arch.persistence"; + public static final String LIFECYCLE = "android.arch.lifecycle"; + public static final String ARCH_CORE = "android.arch.core"; + public static final String PAGING = "android.arch.paging"; + public static final String NAVIGATION = "android.arch.navigation"; +} diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java index 2f5730a2..efa0cbae 100644 --- a/android/support/LibraryVersions.java +++ b/android/support/LibraryVersions.java @@ -28,7 +28,7 @@ public class LibraryVersions { /** * Version code for flatfoot 1.0 projects (room, lifecycles) */ - private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0-rc1"); + private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0"); /** * Version code for Room diff --git a/android/support/SourceJarTaskHelper.java b/android/support/SourceJarTaskHelper.java new file mode 100644 index 00000000..9fbd1dba --- /dev/null +++ b/android/support/SourceJarTaskHelper.java @@ -0,0 +1,60 @@ +/* + * Copyright 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 android.support; + +import com.android.build.gradle.LibraryExtension; +import com.android.builder.core.BuilderConstants; + +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.bundling.Jar; + +/** + * Helper class to handle creation of source jars. + */ +public class SourceJarTaskHelper { + /** + * Sets up a source jar task for an Android library project. + */ + public static void setUpAndroidProject(Project project, LibraryExtension extension) { + // Create sources jar for release builds + extension.getLibraryVariants().all(libraryVariant -> { + if (!libraryVariant.getBuildType().getName().equals(BuilderConstants.RELEASE)) { + return; // Skip non-release builds. + } + + Jar sourceJar = project.getTasks().create("sourceJarRelease", Jar.class); + sourceJar.setPreserveFileTimestamps(false); + sourceJar.setClassifier("sources"); + sourceJar.from(extension.getSourceSets().findByName("main").getJava().getSrcDirs()); + project.getArtifacts().add("archives", sourceJar); + }); + } + + /** + * Sets up a source jar task for a Java library project. + */ + public static void setUpJavaProject(Project project) { + Jar sourceJar = project.getTasks().create("sourceJar", Jar.class); + sourceJar.setPreserveFileTimestamps(false); + sourceJar.setClassifier("sources"); + JavaPluginConvention convention = + project.getConvention().getPlugin(JavaPluginConvention.class); + sourceJar.from(convention.getSourceSets().findByName("main").getAllSource().getSrcDirs()); + project.getArtifacts().add("archives", sourceJar); + } +} diff --git a/android/support/car/drawer/CarDrawerActivity.java b/android/support/car/drawer/CarDrawerActivity.java index 7100218a..f46c652b 100644 --- a/android/support/car/drawer/CarDrawerActivity.java +++ b/android/support/car/drawer/CarDrawerActivity.java @@ -46,7 +46,7 @@ import android.view.ViewGroup; * * <p>The rootAdapter can implement nested-navigation, in its click-handling, by passing the * CarDrawerAdapter for the next level to - * {@link CarDrawerController#switchToAdapter(CarDrawerAdapter)}. + * {@link CarDrawerController#pushAdapter(CarDrawerAdapter)}. * * <p>Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a * derivative. diff --git a/android/support/car/drawer/CarDrawerController.java b/android/support/car/drawer/CarDrawerController.java index 4d9f4e99..7b23714c 100644 --- a/android/support/car/drawer/CarDrawerController.java +++ b/android/support/car/drawer/CarDrawerController.java @@ -19,16 +19,19 @@ package android.support.car.drawer; import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; +import android.support.annotation.AnimRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.car.R; import android.support.car.widget.PagedListView; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.Gravity; import android.view.MenuItem; import android.view.View; +import android.view.animation.AnimationUtils; import android.widget.ProgressBar; import java.util.Stack; @@ -39,13 +42,21 @@ import java.util.Stack; * navigation. */ public class CarDrawerController { + /** An animation for when a user navigates into a submenu. */ + @AnimRes + private static final int DRILL_DOWN_ANIM = R.anim.fade_in_trans_right_layout_anim; + + /** An animation for when a user navigates up (when the back button is pressed). */ + @AnimRes + private static final int NAVIGATE_UP_ANIM = R.anim.fade_in_trans_left_layout_anim; + /** The amount that the drawer has been opened before its color should be switched. */ private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f; /** * A representation of the hierarchy of navigation being displayed in the list. The ordering of * this stack is the order that the user has visited each level. When the user navigates up, - * the adapters are poopped from this list. + * the adapters are popped from this list. */ private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>(); @@ -78,16 +89,14 @@ public class CarDrawerController { ActionBarDrawerToggle drawerToggle) { mToolbar = toolbar; mContext = drawerLayout.getContext(); - + mDrawerToggle = drawerToggle; mDrawerLayout = drawerLayout; mDrawerContent = drawerLayout.findViewById(R.id.drawer_content); mDrawerList = drawerLayout.findViewById(R.id.drawer_list); mDrawerList.setMaxPages(PagedListView.ItemCap.UNLIMITED); - mProgressBar = drawerLayout.findViewById(R.id.drawer_progress); - mDrawerToggle = drawerToggle; setupDrawerToggling(); } @@ -104,7 +113,13 @@ public class CarDrawerController { return; } - mAdapterStack.push(rootAdapter); + // The root adapter is always the last item in the stack. + if (mAdapterStack.size() > 0) { + mAdapterStack.set(0, rootAdapter); + } else { + mAdapterStack.push(rootAdapter); + } + setToolbarTitleFrom(rootAdapter); mDrawerList.setAdapter(rootAdapter); } @@ -120,10 +135,11 @@ public class CarDrawerController { * * @param adapter Adapter for next level of content in the drawer. */ - public final void switchToAdapter(CarDrawerAdapter adapter) { + public final void pushAdapter(CarDrawerAdapter adapter) { mAdapterStack.peek().setTitleChangeListener(null); mAdapterStack.push(adapter); - switchToAdapterInternal(adapter); + setDisplayAdapter(adapter); + runLayoutAnimation(DRILL_DOWN_ANIM); } /** Close the drawer. */ @@ -264,15 +280,15 @@ public class CarDrawerController { } /** - * Sets the navigation drawer's title to be the one supplied by the given adapter and updates - * the navigation drawer list with the adapter's contents. + * Sets the given adapter as the one displaying the current contents of the drawer. + * + * <p>The drawer's title will also be derived from the given adapter. */ - private void switchToAdapterInternal(CarDrawerAdapter adapter) { + private void setDisplayAdapter(CarDrawerAdapter adapter) { setToolbarTitleFrom(adapter); // NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between // car_drawer_list_item_normal, car_drawer_list_item_small and car_list_empty layouts. mDrawerList.getRecyclerView().setAdapter(adapter); - scrollToPosition(0); } /** @@ -290,7 +306,8 @@ public class CarDrawerController { CarDrawerAdapter adapter = mAdapterStack.pop(); adapter.setTitleChangeListener(null); adapter.cleanup(); - switchToAdapterInternal(mAdapterStack.peek()); + setDisplayAdapter(mAdapterStack.peek()); + runLayoutAnimation(NAVIGATE_UP_ANIM); return true; } @@ -301,6 +318,18 @@ public class CarDrawerController { adapter.setTitleChangeListener(null); adapter.cleanup(); } - switchToAdapterInternal(mAdapterStack.peek()); + setDisplayAdapter(mAdapterStack.peek()); + runLayoutAnimation(NAVIGATE_UP_ANIM); + } + + /** + * Runs the given layout animation on the PagedListView. Running this animation will also + * refresh the contents of the list. + */ + private void runLayoutAnimation(@AnimRes int animation) { + RecyclerView recyclerView = mDrawerList.getRecyclerView(); + recyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, animation)); + recyclerView.getAdapter().notifyDataSetChanged(); + recyclerView.scheduleLayoutAnimation(); } } diff --git a/android/support/car/utils/ColumnCalculator.java b/android/support/car/utils/ColumnCalculator.java index 96e081b9..fa5dd432 100644 --- a/android/support/car/utils/ColumnCalculator.java +++ b/android/support/car/utils/ColumnCalculator.java @@ -65,7 +65,7 @@ public class ColumnCalculator { private ColumnCalculator(Context context) { Resources res = context.getResources(); - int marginSize = res.getDimensionPixelSize(R.dimen.car_screen_margin_size); + int marginSize = res.getDimensionPixelSize(R.dimen.car_margin); mGutterSize = res.getDimensionPixelSize(R.dimen.car_screen_gutter_size); mNumOfColumns = res.getInteger(R.integer.car_screen_num_of_columns); diff --git a/android/support/car/widget/CarItemAnimator.java b/android/support/car/widget/CarItemAnimator.java index 4dd32127..ef22c484 100644 --- a/android/support/car/widget/CarItemAnimator.java +++ b/android/support/car/widget/CarItemAnimator.java @@ -22,9 +22,9 @@ import android.support.v7.widget.RecyclerView; /** {@link DefaultItemAnimator} with a few minor changes where it had undesired behavior. */ public class CarItemAnimator extends DefaultItemAnimator { - private final CarLayoutManager mLayoutManager; + private final PagedLayoutManager mLayoutManager; - public CarItemAnimator(CarLayoutManager layoutManager) { + public CarItemAnimator(PagedLayoutManager layoutManager) { mLayoutManager = layoutManager; } diff --git a/android/support/car/widget/CarRecyclerView.java b/android/support/car/widget/CarRecyclerView.java index 2684c58a..bb9cb71a 100644 --- a/android/support/car/widget/CarRecyclerView.java +++ b/android/support/car/widget/CarRecyclerView.java @@ -26,7 +26,7 @@ import android.view.View; import android.view.ViewGroup; /** - * Custom {@link RecyclerView} that helps {@link CarLayoutManager} properly fling and paginate. + * Custom {@link RecyclerView} that helps {@link PagedLayoutManager} properly fling and paginate. * * <p>It also has the ability to fade children as they scroll off screen that can be set with {@link * #setFadeLastItem(boolean)}. @@ -57,7 +57,7 @@ public class CarRecyclerView extends RecyclerView { @Override public boolean fling(int velocityX, int velocityY) { mWasFlingCalledForGesture = true; - return ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY); + return ((PagedLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY); } @Override @@ -69,7 +69,7 @@ public class CarRecyclerView extends RecyclerView { int action = e.getActionMasked(); if (action == MotionEvent.ACTION_UP) { if (!mWasFlingCalledForGesture) { - ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, 0); + ((PagedLayoutManager) getLayoutManager()).settleScrollForFling(this, 0); } mWasFlingCalledForGesture = false; } @@ -102,7 +102,7 @@ public class CarRecyclerView extends RecyclerView { * number of items that fit completely on the screen. */ public void pageUp() { - CarLayoutManager lm = (CarLayoutManager) getLayoutManager(); + PagedLayoutManager lm = (PagedLayoutManager) getLayoutManager(); int pageUpPosition = lm.getPageUpPosition(); if (pageUpPosition == -1) { return; @@ -116,7 +116,7 @@ public class CarRecyclerView extends RecyclerView { * number of items that fit completely on the screen. */ public void pageDown() { - CarLayoutManager lm = (CarLayoutManager) getLayoutManager(); + PagedLayoutManager lm = (PagedLayoutManager) getLayoutManager(); int pageDownPosition = lm.getPageDownPosition(); if (pageDownPosition == -1) { return; diff --git a/android/support/car/widget/CarLayoutManager.java b/android/support/car/widget/PagedLayoutManager.java index d0d3a9e1..c4f469a3 100644 --- a/android/support/car/widget/CarLayoutManager.java +++ b/android/support/car/widget/PagedLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright 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. @@ -18,6 +18,8 @@ package android.support.car.widget; import android.content.Context; import android.graphics.PointF; +import android.os.Parcel; +import android.os.Parcelable; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; @@ -46,9 +48,9 @@ import java.util.ArrayList; * * <ol> * <li>In a normal ListView, when views reach the top of the list, they are clipped. In - * CarLayoutManager, views have the option of flying off of the top of the screen as the next - * row settles in to place. This functionality can be enabled or disabled with {@link - * #setOffsetRows(boolean)}. + * PagedLayoutManager, views have the option of flying off of the top of the screen as the + * next row settles in to place. This functionality can be enabled or disabled with + * {@link #setOffsetRows(boolean)}. * <li>Standard list physics is disabled. Instead, when the user scrolls, it will settle on the * next page. * <li>Items can scroll past the bottom edge of the screen. This helps with pagination so that the @@ -57,8 +59,8 @@ import java.util.ArrayList; * * This LayoutManger should be used with {@link CarRecyclerView}. */ -public class CarLayoutManager extends RecyclerView.LayoutManager { - private static final String TAG = "CarLayoutManager"; +public class PagedLayoutManager extends RecyclerView.LayoutManager { + private static final String TAG = "PagedLayoutManager"; /** * Any fling below the threshold will just scroll to the top fully visible row. The units is @@ -166,7 +168,7 @@ public class CarLayoutManager extends RecyclerView.LayoutManager { /** Set the anchor to the following position on the next layout pass. */ private int mPendingScrollPosition = -1; - public CarLayoutManager(Context context) { + public PagedLayoutManager(Context context) { mContext = context; } @@ -919,6 +921,55 @@ public class CarLayoutManager extends RecyclerView.LayoutManager { return mLowerPageBreakPosition; } + @Override + public Parcelable onSaveInstanceState() { + SavedState savedState = new SavedState(); + savedState.mFirstChildPosition = getFirstFullyVisibleChildPosition(); + return savedState; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (state instanceof SavedState) { + scrollToPosition(((SavedState) state).mFirstChildPosition); + } + } + + /** The state that will be saved across configuration changes. */ + static class SavedState implements Parcelable { + /** The position of the first visible child view in the list. */ + int mFirstChildPosition; + + SavedState() {} + + private SavedState(Parcel in) { + mFirstChildPosition = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mFirstChildPosition); + } + + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + /** * Layout the anchor row. The anchor row is the first fully visible row. * diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java index 46527001..4695c45c 100644 --- a/android/support/car/widget/PagedListView.java +++ b/android/support/car/widget/PagedListView.java @@ -23,7 +23,11 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -33,6 +37,7 @@ import android.support.car.R; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.util.Log; +import android.util.SparseArray; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -41,13 +46,19 @@ import android.widget.FrameLayout; /** * Custom {@link android.support.v7.widget.RecyclerView} that displays a list of items that * resembles a {@link android.widget.ListView} but also has page up and page down arrows on the - * right side. + * left side. */ public class PagedListView extends FrameLayout { /** Default maximum number of clicks allowed on a list */ public static final int DEFAULT_MAX_CLICKS = 6; /** + * Value to pass to {@link #setMaxPages(int)} to indicate there is no restriction on the + * maximum number of pages to show. + */ + public static final int UNLIMITED_PAGES = -1; + + /** * The amount of time after settling to wait before autoscrolling to the next page when the user * holds down a pagination button. */ @@ -57,7 +68,7 @@ public class PagedListView extends FrameLayout { private static final int INVALID_RESOURCE_ID = -1; protected final CarRecyclerView mRecyclerView; - protected final CarLayoutManager mLayoutManager; + protected final PagedLayoutManager mLayoutManager; protected final Handler mHandler = new Handler(); private final boolean mScrollBarEnabled; private final PagedScrollBarView mScrollBarView; @@ -65,8 +76,8 @@ public class PagedListView extends FrameLayout { private int mRowsPerPage = -1; protected RecyclerView.Adapter<? extends RecyclerView.ViewHolder> mAdapter; - /** Maximum number of pages to show. Values < 0 show all pages. */ - private int mMaxPages = -1; + /** Maximum number of pages to show. */ + private int mMaxPages; protected OnScrollListener mOnScrollListener; @@ -115,8 +126,6 @@ public class PagedListView extends FrameLayout { * the item in position 20 instead, for position 1 it will show the item in position 21 instead * and so on. */ - // TODO(b/28003781): ItemPositionOffset and ItemCap interfaces should be merged once - // we enable AlphaJump outside drawer. public interface ItemPositionOffset { /** Sets the position offset for the adapter. */ void setPositionOffset(int positionOffset); @@ -151,7 +160,7 @@ public class PagedListView extends FrameLayout { mMaxPages = getDefaultMaxPages(); - mLayoutManager = new CarLayoutManager(context); + mLayoutManager = new PagedLayoutManager(context); mLayoutManager.setOffsetRows(offsetRows); mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.setOnScrollListener(mRecyclerViewOnScrollListener); @@ -162,7 +171,7 @@ public class PagedListView extends FrameLayout { if (offsetScrollBar) { MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams(); params.setMarginStart(getResources().getDimensionPixelSize( - R.dimen.car_screen_margin_size)); + R.dimen.car_margin)); params.setMarginEnd( a.getDimensionPixelSize(R.styleable.PagedListView_listEndMargin, 0)); mRecyclerView.setLayoutParams(params); @@ -180,6 +189,11 @@ public class PagedListView extends FrameLayout { dividerStartId, dividerEndId)); } + int itemSpacing = a.getDimensionPixelSize(R.styleable.PagedListView_itemSpacing, 0); + if (itemSpacing > 0) { + mRecyclerView.addItemDecoration(new ItemSpacingDecoration(itemSpacing)); + } + // Set this to true so that this view consumes clicks events and views underneath // don't receive this click event. Without this it's possible to click places in the // view that don't capture the event, and as a result, elements visually hidden consume @@ -212,6 +226,16 @@ public class PagedListView extends FrameLayout { } }); + Drawable upButtonIcon = a.getDrawable(R.styleable.PagedListView_upButtonIcon); + if (upButtonIcon != null) { + setUpButtonIcon(upButtonIcon); + } + + Drawable downButtonIcon = a.getDrawable(R.styleable.PagedListView_downButtonIcon); + if (downButtonIcon != null) { + setDownButtonIcon(downButtonIcon); + } + mScrollBarView.setVisibility(mScrollBarEnabled ? VISIBLE : GONE); // Modify the layout the Scroll Bar is not visible. @@ -236,7 +260,7 @@ public class PagedListView extends FrameLayout { if (e.getAction() == MotionEvent.ACTION_DOWN) { // The user has interacted with the list using touch. All movements will now paginate // the list. - mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_PAGE); + mLayoutManager.setRowOffsetMode(PagedLayoutManager.ROW_OFFSET_MODE_PAGE); } return super.onInterceptTouchEvent(e); } @@ -246,7 +270,7 @@ public class PagedListView extends FrameLayout { super.requestChildFocus(child, focused); // The user has interacted with the list using the controller. Movements through the list // will now be one row at a time. - mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_INDIVIDUAL); + mLayoutManager.setRowOffsetMode(PagedLayoutManager.ROW_OFFSET_MODE_INDIVIDUAL); } /** @@ -312,19 +336,25 @@ public class PagedListView extends FrameLayout { mHandler.post(mUpdatePaginationRunnable); } + /** Sets the icon to be used for the up button. */ + public void setUpButtonIcon(Drawable icon) { + mScrollBarView.setUpButtonIcon(icon); + } + + /** Sets the icon to be used for the down button. */ + public void setDownButtonIcon(Drawable icon) { + mScrollBarView.setDownButtonIcon(icon); + } + /** * Sets the adapter for the list. * - * <p>It <em>must</em> implement {@link ItemCap}, otherwise, will throw an {@link - * IllegalArgumentException}. + * <p>The given Adapter can implement {@link ItemCap} if it wishes to control the behavior of + * a max number of items. Otherwise, methods in the PagedListView to limit the content, such as + * {@link #setMaxPages(int)}, will do nothing. */ public void setAdapter( @NonNull RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter) { - if (!(adapter instanceof ItemCap)) { - throw new IllegalArgumentException("ERROR: adapter [" - + adapter.getClass().getCanonicalName() + "] MUST implement ItemCap"); - } - mAdapter = adapter; mRecyclerView.setAdapter(adapter); updateMaxItems(); @@ -333,7 +363,7 @@ public class PagedListView extends FrameLayout { /** @hide */ @RestrictTo(LIBRARY_GROUP) @NonNull - public CarLayoutManager getLayoutManager() { + public PagedLayoutManager getLayoutManager() { return mLayoutManager; } @@ -345,15 +375,19 @@ public class PagedListView extends FrameLayout { /** * Sets the maximum number of the pages that can be shown in the PagedListView. The size of a - * page is defined as the number of items that fit completely on the screen at once. + * page is defined as the number of items that fit completely on the screen at once. + * + * <p>Passing {@link #UNLIMITED_PAGES} will remove any restrictions on a maximum number + * of pages. + * + * <p>Note that for any restriction on maximum pages to work, the adapter passed to this + * PagedListView needs to implement {@link ItemCap}. * - * @param maxPages The maximum number of pages that fit on the screen. Should be positive. + * @param maxPages The maximum number of pages that fit on the screen. Should be positive or + * {@link #UNLIMITED_PAGES}. */ public void setMaxPages(int maxPages) { - if (maxPages < 0) { - return; - } - mMaxPages = maxPages; + mMaxPages = Math.max(UNLIMITED_PAGES, maxPages); updateMaxItems(); } @@ -362,7 +396,8 @@ public class PagedListView extends FrameLayout { * {@link #setMaxPages(int)}. If that method has not been called, then this value should match * the default value. * - * @return The maximum number of pages to be shown. + * @return The maximum number of pages to be shown or {@link #UNLIMITED_PAGES} if there is + * no limit. */ public int getMaxPages() { return mMaxPages; @@ -370,7 +405,7 @@ public class PagedListView extends FrameLayout { /** * Gets the number of rows per page. Default value of mRowsPerPage is -1. If the first child of - * CarLayoutManager is null or the height of the first child is 0, it will return 1. + * PagedLayoutManager is null or the height of the first child is 0, it will return 1. */ public int getRowsPerPage() { return mRowsPerPage; @@ -422,6 +457,32 @@ public class PagedListView extends FrameLayout { } /** + * Sets spacing between each item in the list. The spacing will not be added before the first + * item and after the last. + * + * @param itemSpacing the spacing between each item. + */ + public void setItemSpacing(int itemSpacing) { + ItemSpacingDecoration existing = null; + for (int i = 0, count = mRecyclerView.getItemDecorationCount(); i < count; i++) { + RecyclerView.ItemDecoration itemDecoration = mRecyclerView.getItemDecorationAt(i); + if (itemDecoration instanceof ItemSpacingDecoration) { + existing = (ItemSpacingDecoration) itemDecoration; + break; + } + } + + if (itemSpacing == 0 && existing != null) { + mRecyclerView.removeItemDecoration(existing); + } else if (existing == null) { + mRecyclerView.addItemDecoration(new ItemSpacingDecoration(itemSpacing)); + } else { + existing.setItemSpacing(itemSpacing); + } + mRecyclerView.invalidateItemDecorations(); + } + + /** * Adds an {@link android.support.v7.widget.RecyclerView.OnItemTouchListener} to this * PagedListView. * @@ -520,6 +581,7 @@ public class PagedListView extends FrameLayout { return; } mDefaultMaxPages = newDefault; + resetMaxPages(); } /** Returns the default number of pages the list should have */ @@ -646,8 +708,15 @@ public class PagedListView extends FrameLayout { return; } - final int originalCount = mAdapter.getItemCount(); + // Ensure mRowsPerPage regardless of if the adapter implements ItemCap. updateRowsPerPage(); + + // If the adapter does not implement ItemCap, then the max items on it cannot be updated. + if (!(mAdapter instanceof ItemCap)) { + return; + } + + final int originalCount = mAdapter.getItemCount(); ((ItemCap) mAdapter).setMaxItems(calculateMaxItemCount()); final int newCount = mAdapter.getItemCount(); if (newCount == originalCount) { @@ -683,6 +752,78 @@ public class PagedListView extends FrameLayout { } } + @Override + protected Parcelable onSaveInstanceState() { + SavedState savedState = new SavedState(super.onSaveInstanceState()); + savedState.mLayoutManagerState = mLayoutManager.onSaveInstanceState(); + return savedState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + SavedState savedState = (SavedState) state; + mLayoutManager.onRestoreInstanceState(savedState.mLayoutManagerState); + super.onRestoreInstanceState(savedState.getSuperState()); + } + + @Override + protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { + // There is the possibility of multiple PagedListViews on a page. This means that the ids + // of the child Views of PagedListView are no longer unique, and onSaveInstanceState() + // cannot be used. As a result, PagedListViews needs to manually dispatch the instance + // states. Call dispatchFreezeSelfOnly() so that no child views have onSaveInstanceState() + // called by the system. + dispatchFreezeSelfOnly(container); + } + + @Override + protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { + // Prevent onRestoreInstanceState() from being called on child Views. Instead, PagedListView + // will manually handle passing the state. See the comment in dispatchSaveInstanceState() + // for more information. + dispatchThawSelfOnly(container); + } + + /** The state that will be saved across configuration changes. */ + private static class SavedState extends BaseSavedState { + /** The state of the {@link #mLayoutManager} of this PagedListView. */ + Parcelable mLayoutManagerState; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + mLayoutManagerState = + in.readParcelable(PagedLayoutManager.SavedState.class.getClassLoader()); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeParcelable(mLayoutManagerState, flags); + } + + public static final ClassLoaderCreator<SavedState> CREATOR = + new ClassLoaderCreator<SavedState>() { + @Override + public SavedState createFromParcel(Parcel source, ClassLoader loader) { + return new SavedState(source); + } + + @Override + public SavedState createFromParcel(Parcel source) { + return createFromParcel(source, null /* loader */); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener = new RecyclerView.OnScrollListener() { @Override @@ -766,16 +907,50 @@ public class PagedListView extends FrameLayout { } /** + * A {@link android.support.v7.widget.RecyclerView.ItemDecoration} that will add spacing + * between each item in the RecyclerView that it is added to. + */ + private static class ItemSpacingDecoration extends RecyclerView.ItemDecoration { + + private int mHalfItemSpacing; + + private ItemSpacingDecoration(int itemSpacing) { + mHalfItemSpacing = itemSpacing / 2; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + // Skip top offset for first item and bottom offset for last. + int position = parent.getChildAdapterPosition(view); + if (position > 0) { + outRect.top = mHalfItemSpacing; + } + if (position < state.getItemCount() - 1) { + outRect.bottom = mHalfItemSpacing; + } + } + + /** + * @param itemSpacing sets spacing between each item. + */ + public void setItemSpacing(int itemSpacing) { + mHalfItemSpacing = itemSpacing / 2; + } + } + + /** * A {@link android.support.v7.widget.RecyclerView.ItemDecoration} that will draw a dividing * line between each item in the RecyclerView that it is added to. */ - public static class DividerDecoration extends RecyclerView.ItemDecoration { + private static class DividerDecoration extends RecyclerView.ItemDecoration { private final Context mContext; private final Paint mPaint; private final int mDividerHeight; private final int mDividerStartMargin; @IdRes private final int mDividerStartId; - @IdRes private final int mDvidierEndId; + @IdRes private final int mDividerEndId; /** * @param dividerStartMargin The start offset of the dividing line. This offset will be @@ -792,7 +967,7 @@ public class PagedListView extends FrameLayout { mContext = context; mDividerStartMargin = dividerStartMargin; mDividerStartId = dividerStartId; - mDvidierEndId = dividerEndId; + mDividerEndId = dividerEndId; Resources res = context.getResources(); mPaint = new Paint(); @@ -807,16 +982,20 @@ public class PagedListView extends FrameLayout { @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { - for (int i = 0, childCount = parent.getChildCount(); i < childCount; i++) { + // Draw a divider line between each item. No need to draw the line for the last item. + for (int i = 0, childCount = parent.getChildCount(); i < childCount - 1; i++) { View container = parent.getChildAt(i); + View nextContainer = parent.getChildAt(i + 1); + int spacing = nextContainer.getTop() - container.getBottom(); + View startChild = mDividerStartId != INVALID_RESOURCE_ID ? container.findViewById(mDividerStartId) : container; View endChild = - mDvidierEndId != INVALID_RESOURCE_ID - ? container.findViewById(mDvidierEndId) + mDividerEndId != INVALID_RESOURCE_ID + ? container.findViewById(mDividerEndId) : container; if (startChild == null || endChild == null) { @@ -825,14 +1004,24 @@ public class PagedListView extends FrameLayout { int left = mDividerStartMargin + startChild.getLeft(); int right = endChild.getRight(); - int bottom = container.getBottom(); + int bottom = container.getBottom() + spacing / 2 + mDividerHeight / 2; int top = bottom - mDividerHeight; - // Draw a divider line between each item. No need to draw the line for the last - // item. - if (i != childCount - 1) { - c.drawRect(left, top, right, bottom, mPaint); - } + c.drawRect(left, top, right, bottom, mPaint); + } + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + // Skip top offset for first item and bottom offset for last. + int position = parent.getChildAdapterPosition(view); + if (position > 0) { + outRect.top = mDividerHeight / 2; + } + if (position < state.getItemCount() - 1) { + outRect.bottom = mDividerHeight / 2; } } } diff --git a/android/support/car/widget/PagedScrollBarView.java b/android/support/car/widget/PagedScrollBarView.java index 125b354c..1c46b5d4 100644 --- a/android/support/car/widget/PagedScrollBarView.java +++ b/android/support/car/widget/PagedScrollBarView.java @@ -18,6 +18,7 @@ package android.support.car.widget; import android.content.Context; import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; import android.support.car.R; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; @@ -98,6 +99,16 @@ public class PagedScrollBarView extends FrameLayout return true; } + /** Sets the icon to be used for the up button. */ + public void setUpButtonIcon(Drawable icon) { + mUpButton.setImageDrawable(icon); + } + + /** Sets the icon to be used for the down button. */ + public void setDownButtonIcon(Drawable icon) { + mDownButton.setImageDrawable(icon); + } + /** * Sets the listener that will be notified when the up and down buttons have been pressed. * @@ -119,7 +130,7 @@ public class PagedScrollBarView extends FrameLayout /** Sets the range, offset and extent of the scroll bar. See {@link View}. */ public void setParameters(int range, int offset, int extent, boolean animate) { - // This method is where we take the computed parameters from the CarLayoutManager and + // This method is where we take the computed parameters from the PagedLayoutManager and // render it within the specified constraints ({@link #mMaxThumbLength} and // {@link #mMinThumbLength}). final int size = mFiller.getHeight() - mFiller.getPaddingTop() - mFiller.getPaddingBottom(); diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java deleted file mode 100644 index a18bcf32..00000000 --- a/android/support/mediacompat/testlib/IntentConstants.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 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 android.support.mediacompat.testlib; - -/** - * Constants used for sending intent between client and service apps. - */ -public class IntentConstants { - public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD = - "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD"; - public static final String ACTION_CALL_MEDIA_SESSION_METHOD = - "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD"; - public static final String ACTION_CALL_MEDIA_CONTROLLER_METHOD = - "android.support.mediacompat.client.action.CALL_MEDIA_CONTROLLER_METHOD"; - public static final String ACTION_CALL_TRANSPORT_CONTROLS_METHOD = - "android.support.mediacompat.client.action.CALL_TRANSPORT_CONTROLS_METHOD"; - public static final String KEY_METHOD_ID = "method_id"; - public static final String KEY_ARGUMENT = "argument"; - public static final String KEY_SESSION_TOKEN = "session_token"; -} diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java index 95be1621..cbdccc1b 100644 --- a/android/support/mediacompat/testlib/MediaSessionConstants.java +++ b/android/support/mediacompat/testlib/MediaSessionConstants.java @@ -40,7 +40,6 @@ public class MediaSessionConstants { public static final int SET_RATING_TYPE = 117; public static final String TEST_SESSION_TAG = "test-session-tag"; - public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test"; public static final String TEST_KEY = "test-key"; public static final String TEST_VALUE = "test-val"; public static final String TEST_SESSION_EVENT = "test-session-event"; diff --git a/android/support/mediacompat/testlib/VersionConstants.java b/android/support/mediacompat/testlib/VersionConstants.java new file mode 100644 index 00000000..6533ee17 --- /dev/null +++ b/android/support/mediacompat/testlib/VersionConstants.java @@ -0,0 +1,25 @@ +/* + * Copyright 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 android.support.mediacompat.testlib; + +/** + * Constants for getting support library version information. + */ +public class VersionConstants { + public static final String KEY_CLIENT_VERSION = "client_version"; + public static final String KEY_SERVICE_VERSION = "service_version"; +} diff --git a/android/support/mediacompat/testlib/util/IntentUtil.java b/android/support/mediacompat/testlib/util/IntentUtil.java new file mode 100644 index 00000000..bbf97524 --- /dev/null +++ b/android/support/mediacompat/testlib/util/IntentUtil.java @@ -0,0 +1,131 @@ +/* + * Copyright 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 android.support.mediacompat.testlib.util; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcelable; + +import java.util.ArrayList; + +/** + * Methods and constants used for sending intent between client and service apps. + */ +public class IntentUtil { + + public static final ComponentName SERVICE_RECEIVER_COMPONENT_NAME = new ComponentName( + "android.support.mediacompat.service.test", + "android.support.mediacompat.service.ServiceBroadcastReceiver"); + public static final ComponentName CLIENT_RECEIVER_COMPONENT_NAME = new ComponentName( + "android.support.mediacompat.client.test", + "android.support.mediacompat.client.ClientBroadcastReceiver"); + + public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD = + "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD"; + public static final String ACTION_CALL_MEDIA_SESSION_METHOD = + "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD"; + public static final String ACTION_CALL_MEDIA_CONTROLLER_METHOD = + "android.support.mediacompat.client.action.CALL_MEDIA_CONTROLLER_METHOD"; + public static final String ACTION_CALL_TRANSPORT_CONTROLS_METHOD = + "android.support.mediacompat.client.action.CALL_TRANSPORT_CONTROLS_METHOD"; + + public static final String KEY_METHOD_ID = "method_id"; + public static final String KEY_ARGUMENT = "argument"; + public static final String KEY_SESSION_TOKEN = "session_token"; + + /** + * Calls a method of MediaBrowserService. Used by client app. + */ + public static void callMediaBrowserServiceMethod(int methodId, Object arg, Context context) { + Intent intent = createIntent(SERVICE_RECEIVER_COMPONENT_NAME, methodId, arg); + intent.setAction(ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD); + if (Build.VERSION.SDK_INT >= 16) { + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + } + context.sendBroadcast(intent); + } + + /** + * Calls a method of MediaSession. Used by client app. + */ + public static void callMediaSessionMethod(int methodId, Object arg, Context context) { + Intent intent = createIntent(SERVICE_RECEIVER_COMPONENT_NAME, methodId, arg); + intent.setAction(ACTION_CALL_MEDIA_SESSION_METHOD); + if (Build.VERSION.SDK_INT >= 16) { + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + } + context.sendBroadcast(intent); + } + + /** + * Calls a method of MediaController. Used by service app. + */ + public static void callMediaControllerMethod( + int methodId, Object arg, Context context, Parcelable token) { + Intent intent = createIntent(CLIENT_RECEIVER_COMPONENT_NAME, methodId, arg); + intent.setAction(ACTION_CALL_MEDIA_CONTROLLER_METHOD); + intent.putExtra(KEY_SESSION_TOKEN, token); + if (Build.VERSION.SDK_INT >= 16) { + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + } + context.sendBroadcast(intent); + } + + /** + * Calls a method of TransportControls. Used by service app. + */ + public static void callTransportControlsMethod( + int methodId, Object arg, Context context, Parcelable token) { + Intent intent = createIntent(CLIENT_RECEIVER_COMPONENT_NAME, methodId, arg); + intent.setAction(ACTION_CALL_TRANSPORT_CONTROLS_METHOD); + intent.putExtra(KEY_SESSION_TOKEN, token); + if (Build.VERSION.SDK_INT >= 16) { + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + } + context.sendBroadcast(intent); + } + + private static Intent createIntent(ComponentName componentName, int methodId, Object arg) { + Intent intent = new Intent(); + intent.setComponent(componentName); + intent.putExtra(KEY_METHOD_ID, methodId); + + if (arg instanceof String) { + intent.putExtra(KEY_ARGUMENT, (String) arg); + } else if (arg instanceof Integer) { + intent.putExtra(KEY_ARGUMENT, (int) arg); + } else if (arg instanceof Long) { + intent.putExtra(KEY_ARGUMENT, (long) arg); + } else if (arg instanceof Boolean) { + intent.putExtra(KEY_ARGUMENT, (boolean) arg); + } else if (arg instanceof Parcelable) { + intent.putExtra(KEY_ARGUMENT, (Parcelable) arg); + } else if (arg instanceof ArrayList<?>) { + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(KEY_ARGUMENT, (ArrayList<? extends Parcelable>) arg); + intent.putExtras(bundle); + } else if (arg instanceof Bundle) { + Bundle bundle = new Bundle(); + bundle.putBundle(KEY_ARGUMENT, (Bundle) arg); + intent.putExtras(bundle); + } + return intent; + } +} diff --git a/android/support/mediacompat/testlib/util/PollingCheck.java b/android/support/mediacompat/testlib/util/PollingCheck.java new file mode 100644 index 00000000..3412da02 --- /dev/null +++ b/android/support/mediacompat/testlib/util/PollingCheck.java @@ -0,0 +1,98 @@ +/* + * Copyright 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 android.support.mediacompat.testlib.util; + +import junit.framework.Assert; + +/** + * Utility used for testing that allows to poll for a certain condition to happen within a timeout. + * (Copied from testutils/src/main/java/android/support/testutils/PollingCheck.java.) + */ +public abstract class PollingCheck { + private static final long DEFAULT_TIMEOUT = 3000; + private static final long TIME_SLICE = 50; + private final long mTimeout; + + /** + * The condition that the PollingCheck should use to proceed successfully. + */ + public interface PollingCheckCondition { + /** + * @return Whether the polling condition has been met. + */ + boolean canProceed(); + } + + public PollingCheck(long timeout) { + mTimeout = timeout; + } + + protected abstract boolean check(); + + /** + * Start running the polling check. + */ + public void run() { + if (check()) { + return; + } + + long timeout = mTimeout; + while (timeout > 0) { + try { + Thread.sleep(TIME_SLICE); + } catch (InterruptedException e) { + Assert.fail("unexpected InterruptedException"); + } + + if (check()) { + return; + } + + timeout -= TIME_SLICE; + } + + Assert.fail("unexpected timeout"); + } + + /** + * Instantiate and start polling for a given condition with a default 3000ms timeout. + * @param condition The condition to check for success. + */ + public static void waitFor(final PollingCheckCondition condition) { + new PollingCheck(DEFAULT_TIMEOUT) { + @Override + protected boolean check() { + return condition.canProceed(); + } + }.run(); + } + + /** + * Instantiate and start polling for a given condition. + * @param timeout Time out in ms + * @param condition The condition to check for success. + */ + public static void waitFor(long timeout, final PollingCheckCondition condition) { + new PollingCheck(timeout) { + @Override + protected boolean check() { + return condition.canProceed(); + } + }.run(); + } +} diff --git a/android/support/mediacompat/testlib/util/TestUtil.java b/android/support/mediacompat/testlib/util/TestUtil.java new file mode 100644 index 00000000..d105510c --- /dev/null +++ b/android/support/mediacompat/testlib/util/TestUtil.java @@ -0,0 +1,41 @@ +/* + * Copyright 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 android.support.mediacompat.testlib.util; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertSame; + +import android.os.Bundle; + +/** + * Utility methods used for testing. + */ +public final class TestUtil { + + /** + * Asserts that two Bundles are equal. + */ + public static void assertBundleEquals(Bundle expected, Bundle observed) { + if (expected == null || observed == null) { + assertSame(expected, observed); + } + assertEquals(expected.size(), observed.size()); + for (String key : expected.keySet()) { + assertEquals(expected.get(key), observed.get(key)); + } + } +} diff --git a/android/support/text/emoji/widget/EmojiAppCompatEditText.java b/android/support/text/emoji/widget/EmojiAppCompatEditText.java index 87c17c20..0ae4ea04 100644 --- a/android/support/text/emoji/widget/EmojiAppCompatEditText.java +++ b/android/support/text/emoji/widget/EmojiAppCompatEditText.java @@ -21,6 +21,7 @@ import android.support.annotation.IntRange; import android.support.annotation.Nullable; import android.support.text.emoji.EmojiCompat; import android.support.v7.widget.AppCompatEditText; +import android.text.method.KeyListener; import android.util.AttributeSet; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -67,8 +68,11 @@ public class EmojiAppCompatEditText extends AppCompatEditText { } @Override - public void setKeyListener(android.text.method.KeyListener input) { - super.setKeyListener(getEmojiEditTextHelper().getKeyListener(input)); + public void setKeyListener(@Nullable KeyListener keyListener) { + if (keyListener != null) { + keyListener = getEmojiEditTextHelper().getKeyListener(keyListener); + } + super.setKeyListener(keyListener); } @Override diff --git a/android/support/text/emoji/widget/EmojiEditText.java b/android/support/text/emoji/widget/EmojiEditText.java index a0e8a69e..70ca7a66 100644 --- a/android/support/text/emoji/widget/EmojiEditText.java +++ b/android/support/text/emoji/widget/EmojiEditText.java @@ -21,6 +21,7 @@ import android.os.Build; import android.support.annotation.IntRange; import android.support.annotation.Nullable; import android.support.text.emoji.EmojiCompat; +import android.text.method.KeyListener; import android.util.AttributeSet; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -73,8 +74,11 @@ public class EmojiEditText extends EditText { } @Override - public void setKeyListener(android.text.method.KeyListener keyListener) { - super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener)); + public void setKeyListener(@Nullable KeyListener keyListener) { + if (keyListener != null) { + keyListener = getEmojiEditTextHelper().getKeyListener(keyListener); + } + super.setKeyListener(keyListener); } @Override diff --git a/android/support/text/emoji/widget/EmojiExtractEditText.java b/android/support/text/emoji/widget/EmojiExtractEditText.java index ca1868e2..2e4d3caa 100644 --- a/android/support/text/emoji/widget/EmojiExtractEditText.java +++ b/android/support/text/emoji/widget/EmojiExtractEditText.java @@ -27,6 +27,7 @@ import android.support.annotation.Nullable; import android.support.annotation.RestrictTo; import android.support.text.emoji.EmojiCompat; import android.support.text.emoji.EmojiSpan; +import android.text.method.KeyListener; import android.util.AttributeSet; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -81,8 +82,11 @@ public class EmojiExtractEditText extends ExtractEditText { } @Override - public void setKeyListener(android.text.method.KeyListener keyListener) { - super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener)); + public void setKeyListener(@Nullable KeyListener keyListener) { + if (keyListener != null) { + keyListener = getEmojiEditTextHelper().getKeyListener(keyListener); + } + super.setKeyListener(keyListener); } @Override diff --git a/android/support/transition/Transition.java b/android/support/transition/Transition.java index 04cc57bd..9c198a94 100644 --- a/android/support/transition/Transition.java +++ b/android/support/transition/Transition.java @@ -1017,7 +1017,7 @@ public abstract class Transition implements Cloneable { */ @NonNull public Transition addTarget(@IdRes int targetId) { - if (targetId > 0) { + if (targetId != 0) { mTargetIds.add(targetId); } return this; @@ -1107,7 +1107,7 @@ public abstract class Transition implements Cloneable { */ @NonNull public Transition removeTarget(@IdRes int targetId) { - if (targetId > 0) { + if (targetId != 0) { mTargetIds.remove((Integer) targetId); } return this; diff --git a/android/support/v17/leanback/app/BaseFragment.java b/android/support/v17/leanback/app/BaseFragment.java index bdb213f2..ea460111 100644 --- a/android/support/v17/leanback/app/BaseFragment.java +++ b/android/support/v17/leanback/app/BaseFragment.java @@ -31,7 +31,9 @@ import android.view.ViewTreeObserver; /** * Base class for leanback Fragments. This class is not intended to be subclassed by apps. + * @deprecated use {@link BaseSupportFragment} */ +@Deprecated @SuppressWarnings("FragmentNotInstantiable") public class BaseFragment extends BrandedFragment { diff --git a/android/support/v17/leanback/app/BaseRowFragment.java b/android/support/v17/leanback/app/BaseRowFragment.java index 2d79f3e1..97a5b848 100644 --- a/android/support/v17/leanback/app/BaseRowFragment.java +++ b/android/support/v17/leanback/app/BaseRowFragment.java @@ -34,7 +34,9 @@ import android.view.ViewGroup; /** * An internal base class for a fragment containing a list of rows. + * @deprecated use {@link BaseRowSupportFragment} */ +@Deprecated abstract class BaseRowFragment extends Fragment { private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition"; private ObjectAdapter mAdapter; @@ -164,8 +166,10 @@ abstract class BaseRowFragment extends Fragment { * Set the presenter selector used to create and bind views. */ public final void setPresenterSelector(PresenterSelector presenterSelector) { - mPresenterSelector = presenterSelector; - updateAdapter(); + if (mPresenterSelector != presenterSelector) { + mPresenterSelector = presenterSelector; + updateAdapter(); + } } /** @@ -180,8 +184,10 @@ abstract class BaseRowFragment extends Fragment { * @param rowsAdapter Adapter that represents list of rows. */ public final void setAdapter(ObjectAdapter rowsAdapter) { - mAdapter = rowsAdapter; - updateAdapter(); + if (mAdapter != rowsAdapter) { + mAdapter = rowsAdapter; + updateAdapter(); + } } /** diff --git a/android/support/v17/leanback/app/BaseRowSupportFragment.java b/android/support/v17/leanback/app/BaseRowSupportFragment.java index dba78daf..6a477ab0 100644 --- a/android/support/v17/leanback/app/BaseRowSupportFragment.java +++ b/android/support/v17/leanback/app/BaseRowSupportFragment.java @@ -161,8 +161,10 @@ abstract class BaseRowSupportFragment extends Fragment { * Set the presenter selector used to create and bind views. */ public final void setPresenterSelector(PresenterSelector presenterSelector) { - mPresenterSelector = presenterSelector; - updateAdapter(); + if (mPresenterSelector != presenterSelector) { + mPresenterSelector = presenterSelector; + updateAdapter(); + } } /** @@ -177,8 +179,10 @@ abstract class BaseRowSupportFragment extends Fragment { * @param rowsAdapter Adapter that represents list of rows. */ public final void setAdapter(ObjectAdapter rowsAdapter) { - mAdapter = rowsAdapter; - updateAdapter(); + if (mAdapter != rowsAdapter) { + mAdapter = rowsAdapter; + updateAdapter(); + } } /** diff --git a/android/support/v17/leanback/app/BrandedFragment.java b/android/support/v17/leanback/app/BrandedFragment.java index 1f6ad299..415c13e0 100644 --- a/android/support/v17/leanback/app/BrandedFragment.java +++ b/android/support/v17/leanback/app/BrandedFragment.java @@ -33,7 +33,9 @@ import android.view.ViewGroup; /** * Fragment class for managing search and branding using a view that implements * {@link TitleViewAdapter.Provider}. + * @deprecated use {@link BrandedSupportFragment} */ +@Deprecated public class BrandedFragment extends Fragment { // BUNDLE attribute for title is showing diff --git a/android/support/v17/leanback/app/BrowseFragment.java b/android/support/v17/leanback/app/BrowseFragment.java index ae31c4fb..c561ea99 100644 --- a/android/support/v17/leanback/app/BrowseFragment.java +++ b/android/support/v17/leanback/app/BrowseFragment.java @@ -81,7 +81,9 @@ import java.util.Map; * The recommended theme to use with a BrowseFragment is * {@link android.support.v17.leanback.R.style#Theme_Leanback_Browse}. * </p> + * @deprecated use {@link BrowseSupportFragment} */ +@Deprecated public class BrowseFragment extends BaseFragment { // BUNDLE attribute for saving header show/hide status when backstack is used: @@ -203,7 +205,9 @@ public class BrowseFragment extends BaseFragment { /** * Listener for transitions between browse headers and rows. + * @deprecated use {@link BrowseSupportFragment} */ + @Deprecated public static class BrowseTransitionListener { /** * Callback when headers transition starts. @@ -267,7 +271,9 @@ public class BrowseFragment extends BaseFragment { /** * Possible set of actions that {@link BrowseFragment} exposes to clients. Custom * fragments can interact with {@link BrowseFragment} using this interface. + * @deprecated use {@link BrowseSupportFragment} */ + @Deprecated public interface FragmentHost { /** * Fragments are required to invoke this callback once their view is created @@ -376,7 +382,9 @@ public class BrowseFragment extends BaseFragment { * and provide that through {@link MainFragmentAdapterRegistry}. * {@link MainFragmentAdapter} implementation can supply any fragment and override * just those interactions that makes sense. + * @deprecated use {@link BrowseSupportFragment} */ + @Deprecated public static class MainFragmentAdapter<T extends Fragment> { private boolean mScalingEnabled; private final T mFragment; @@ -466,7 +474,9 @@ public class BrowseFragment extends BaseFragment { * Interface to be implemented by all fragments for providing an instance of * {@link MainFragmentAdapter}. Both {@link RowsFragment} and custom fragment provided * against {@link PageRow} will need to implement this interface. + * @deprecated use {@link BrowseSupportFragment} */ + @Deprecated public interface MainFragmentAdapterProvider { /** * Returns an instance of {@link MainFragmentAdapter} that {@link BrowseFragment} @@ -478,7 +488,9 @@ public class BrowseFragment extends BaseFragment { /** * Interface to be implemented by {@link RowsFragment} and its subclasses for providing * an instance of {@link MainFragmentRowsAdapter}. + * @deprecated use {@link BrowseSupportFragment} */ + @Deprecated public interface MainFragmentRowsAdapterProvider { /** * Returns an instance of {@link MainFragmentRowsAdapter} that {@link BrowseFragment} @@ -491,7 +503,9 @@ public class BrowseFragment extends BaseFragment { * This is used to pass information to {@link RowsFragment} or its subclasses. * {@link BrowseFragment} uses this interface to pass row based interaction events to * the target fragment. + * @deprecated use {@link BrowseSupportFragment} */ + @Deprecated public static class MainFragmentRowsAdapter<T extends Fragment> { private final T mFragment; @@ -570,14 +584,27 @@ public class BrowseFragment extends BaseFragment { } boolean oldIsPageRow = mIsPageRow; + Object oldPageRow = mPageRow; mIsPageRow = mCanShowHeaders && item instanceof PageRow; + mPageRow = mIsPageRow ? item : null; boolean swap; if (mMainFragment == null) { swap = true; } else { if (oldIsPageRow) { - swap = true; + if (mIsPageRow) { + if (oldPageRow == null) { + // fragment is restored, page row object not yet set, so just set the + // mPageRow object and there is no need to replace the fragment + swap = false; + } else { + // swap if page row object changes + swap = oldPageRow != mPageRow; + } + } else { + swap = true; + } } else { swap = mIsPageRow; } @@ -590,37 +617,45 @@ public class BrowseFragment extends BaseFragment { "Fragment must implement MainFragmentAdapterProvider"); } - mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment) - .getMainFragmentAdapter(); - mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl()); - if (!mIsPageRow) { - if (mMainFragment instanceof MainFragmentRowsAdapterProvider) { - mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment) - .getMainFragmentRowsAdapter(); - } else { - mMainFragmentRowsAdapter = null; - } - mIsPageRow = mMainFragmentRowsAdapter == null; - } else { - mMainFragmentRowsAdapter = null; - } + setMainFragmentAdapter(); } return swap; } + void setMainFragmentAdapter() { + mMainFragmentAdapter = ((MainFragmentAdapterProvider) mMainFragment) + .getMainFragmentAdapter(); + mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl()); + if (!mIsPageRow) { + if (mMainFragment instanceof MainFragmentRowsAdapterProvider) { + setMainFragmentRowsAdapter(((MainFragmentRowsAdapterProvider) mMainFragment) + .getMainFragmentRowsAdapter()); + } else { + setMainFragmentRowsAdapter(null); + } + mIsPageRow = mMainFragmentRowsAdapter == null; + } else { + setMainFragmentRowsAdapter(null); + } + } + /** * Factory class responsible for creating fragment given the current item. {@link ListRow} * should return {@link RowsFragment} or its subclass whereas {@link PageRow} * can return any fragment class. + * @deprecated use {@link BrowseSupportFragment} */ + @Deprecated public abstract static class FragmentFactory<T extends Fragment> { public abstract T createFragment(Object row); } /** * FragmentFactory implementation for {@link ListRow}. + * @deprecated use {@link BrowseSupportFragment} */ + @Deprecated public static class ListRowFragmentFactory extends FragmentFactory<RowsFragment> { @Override public RowsFragment createFragment(Object row) { @@ -634,7 +669,9 @@ public class BrowseFragment extends BaseFragment { * handling {@link ListRow}. Developers can override that and also if they want to * use custom fragment, they can register a custom {@link FragmentFactory} * against {@link PageRow}. + * @deprecated use {@link BrowseSupportFragment} */ + @Deprecated public final static class MainFragmentAdapterRegistry { private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap<>(); private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory(); @@ -678,7 +715,8 @@ public class BrowseFragment extends BaseFragment { MainFragmentAdapter mMainFragmentAdapter; Fragment mMainFragment; HeadersFragment mHeadersFragment; - private MainFragmentRowsAdapter mMainFragmentRowsAdapter; + MainFragmentRowsAdapter mMainFragmentRowsAdapter; + ListRowDataAdapter mMainFragmentListRowDataAdapter; private ObjectAdapter mAdapter; private PresenterSelector mAdapterPresenter; @@ -701,6 +739,7 @@ public class BrowseFragment extends BaseFragment { private int mSelectedPosition = -1; private float mScaleFactor; boolean mIsPageRow; + Object mPageRow; private PresenterSelector mHeaderPresenterSelector; private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable(); @@ -820,11 +859,45 @@ public class BrowseFragment extends BaseFragment { return; } + updateMainFragmentRowsAdapter(); + mHeadersFragment.setAdapter(mAdapter); + } + + void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) { + if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) { + return; + } + // first clear previous mMainFragmentRowsAdapter and set a new mMainFragmentRowsAdapter if (mMainFragmentRowsAdapter != null) { - mMainFragmentRowsAdapter.setAdapter( - adapter == null ? null : new ListRowDataAdapter(adapter)); + // RowsFragment cannot change click/select listeners after view created. + // The main fragment and adapter should be GCed as long as there is no reference from + // BrowseFragment to it. + mMainFragmentRowsAdapter.setAdapter(null); + } + mMainFragmentRowsAdapter = mainFragmentRowsAdapter; + if (mMainFragmentRowsAdapter != null) { + mMainFragmentRowsAdapter.setOnItemViewSelectedListener( + new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter)); + mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener); + } + // second update mMainFragmentListRowDataAdapter set on mMainFragmentRowsAdapter + updateMainFragmentRowsAdapter(); + } + + /** + * Update mMainFragmentListRowDataAdapter and set it on mMainFragmentRowsAdapter. + * It also clears old mMainFragmentListRowDataAdapter. + */ + void updateMainFragmentRowsAdapter() { + if (mMainFragmentListRowDataAdapter != null) { + mMainFragmentListRowDataAdapter.detach(); + mMainFragmentListRowDataAdapter = null; + } + if (mMainFragmentRowsAdapter != null) { + mMainFragmentListRowDataAdapter = mAdapter == null + ? null : new ListRowDataAdapter(mAdapter); + mMainFragmentRowsAdapter.setAdapter(mMainFragmentListRowDataAdapter); } - mHeadersFragment.setAdapter(adapter); } public final MainFragmentAdapterRegistry getMainFragmentRegistry() { @@ -1144,7 +1217,8 @@ public class BrowseFragment extends BaseFragment { @Override public void onDestroyView() { - mMainFragmentRowsAdapter = null; + setMainFragmentRowsAdapter(null); + mPageRow = null; mMainFragmentAdapter = null; mMainFragment = null; mHeadersFragment = null; @@ -1198,26 +1272,17 @@ public class BrowseFragment extends BaseFragment { mHeadersFragment = (HeadersFragment) getChildFragmentManager() .findFragmentById(R.id.browse_headers_dock); mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame); - mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment) - .getMainFragmentAdapter(); - mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl()); mIsPageRow = savedInstanceState != null && savedInstanceState.getBoolean(IS_PAGE_ROW, false); + // mPageRow object is unable to restore, if its null and mIsPageRow is true, this is + // the case for restoring, later if setSelection() triggers a createMainFragment(), + // should not create fragment. mSelectedPosition = savedInstanceState != null ? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0; - if (!mIsPageRow) { - if (mMainFragment instanceof MainFragmentRowsAdapterProvider) { - mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment) - .getMainFragmentRowsAdapter(); - } else { - mMainFragmentRowsAdapter = null; - } - } else { - mMainFragmentRowsAdapter = null; - } + setMainFragmentAdapter(); } mHeadersFragment.setHeadersGone(!mCanShowHeaders); @@ -1242,8 +1307,6 @@ public class BrowseFragment extends BaseFragment { mScaleFrameLayout.setPivotX(0); mScaleFrameLayout.setPivotY(mContainerListAlignTop); - setupMainFragment(); - if (mBrandColorSet) { mHeadersFragment.setBackgroundColor(mBrandColor); } @@ -1270,17 +1333,6 @@ public class BrowseFragment extends BaseFragment { return root; } - private void setupMainFragment() { - if (mMainFragmentRowsAdapter != null) { - if (mAdapter != null) { - mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(mAdapter)); - } - mMainFragmentRowsAdapter.setOnItemViewSelectedListener( - new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter)); - mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener); - } - } - void createHeadersTransition() { mHeadersTransition = TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this), mShowingHeaders @@ -1470,10 +1522,10 @@ public class BrowseFragment extends BaseFragment { }; void onRowSelected(int position) { - if (position != mSelectedPosition) { - mSetSelectionRunnable.post( - position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true); - } + // even position is same, it could be data changed, always post selection runnable + // to possibly swap main fragment. + mSetSelectionRunnable.post( + position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true); } void setSelection(int position, boolean smooth) { @@ -1500,7 +1552,6 @@ public class BrowseFragment extends BaseFragment { if (createMainFragment(mAdapter, position)) { swapToMainFragment(); expandMainFragment(!(mCanShowHeaders && mShowingHeaders)); - setupMainFragment(); } } diff --git a/android/support/v17/leanback/app/BrowseSupportFragment.java b/android/support/v17/leanback/app/BrowseSupportFragment.java index 4a2502a8..c28064ca 100644 --- a/android/support/v17/leanback/app/BrowseSupportFragment.java +++ b/android/support/v17/leanback/app/BrowseSupportFragment.java @@ -567,14 +567,27 @@ public class BrowseSupportFragment extends BaseSupportFragment { } boolean oldIsPageRow = mIsPageRow; + Object oldPageRow = mPageRow; mIsPageRow = mCanShowHeaders && item instanceof PageRow; + mPageRow = mIsPageRow ? item : null; boolean swap; if (mMainFragment == null) { swap = true; } else { if (oldIsPageRow) { - swap = true; + if (mIsPageRow) { + if (oldPageRow == null) { + // fragment is restored, page row object not yet set, so just set the + // mPageRow object and there is no need to replace the fragment + swap = false; + } else { + // swap if page row object changes + swap = oldPageRow != mPageRow; + } + } else { + swap = true; + } } else { swap = mIsPageRow; } @@ -587,25 +600,29 @@ public class BrowseSupportFragment extends BaseSupportFragment { "Fragment must implement MainFragmentAdapterProvider"); } - mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment) - .getMainFragmentAdapter(); - mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl()); - if (!mIsPageRow) { - if (mMainFragment instanceof MainFragmentRowsAdapterProvider) { - mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment) - .getMainFragmentRowsAdapter(); - } else { - mMainFragmentRowsAdapter = null; - } - mIsPageRow = mMainFragmentRowsAdapter == null; - } else { - mMainFragmentRowsAdapter = null; - } + setMainFragmentAdapter(); } return swap; } + void setMainFragmentAdapter() { + mMainFragmentAdapter = ((MainFragmentAdapterProvider) mMainFragment) + .getMainFragmentAdapter(); + mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl()); + if (!mIsPageRow) { + if (mMainFragment instanceof MainFragmentRowsAdapterProvider) { + setMainFragmentRowsAdapter(((MainFragmentRowsAdapterProvider) mMainFragment) + .getMainFragmentRowsAdapter()); + } else { + setMainFragmentRowsAdapter(null); + } + mIsPageRow = mMainFragmentRowsAdapter == null; + } else { + setMainFragmentRowsAdapter(null); + } + } + /** * Factory class responsible for creating fragment given the current item. {@link ListRow} * should return {@link RowsSupportFragment} or its subclass whereas {@link PageRow} @@ -675,7 +692,8 @@ public class BrowseSupportFragment extends BaseSupportFragment { MainFragmentAdapter mMainFragmentAdapter; Fragment mMainFragment; HeadersSupportFragment mHeadersSupportFragment; - private MainFragmentRowsAdapter mMainFragmentRowsAdapter; + MainFragmentRowsAdapter mMainFragmentRowsAdapter; + ListRowDataAdapter mMainFragmentListRowDataAdapter; private ObjectAdapter mAdapter; private PresenterSelector mAdapterPresenter; @@ -698,6 +716,7 @@ public class BrowseSupportFragment extends BaseSupportFragment { private int mSelectedPosition = -1; private float mScaleFactor; boolean mIsPageRow; + Object mPageRow; private PresenterSelector mHeaderPresenterSelector; private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable(); @@ -817,11 +836,45 @@ public class BrowseSupportFragment extends BaseSupportFragment { return; } + updateMainFragmentRowsAdapter(); + mHeadersSupportFragment.setAdapter(mAdapter); + } + + void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) { + if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) { + return; + } + // first clear previous mMainFragmentRowsAdapter and set a new mMainFragmentRowsAdapter + if (mMainFragmentRowsAdapter != null) { + // RowsFragment cannot change click/select listeners after view created. + // The main fragment and adapter should be GCed as long as there is no reference from + // BrowseSupportFragment to it. + mMainFragmentRowsAdapter.setAdapter(null); + } + mMainFragmentRowsAdapter = mainFragmentRowsAdapter; + if (mMainFragmentRowsAdapter != null) { + mMainFragmentRowsAdapter.setOnItemViewSelectedListener( + new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter)); + mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener); + } + // second update mMainFragmentListRowDataAdapter set on mMainFragmentRowsAdapter + updateMainFragmentRowsAdapter(); + } + + /** + * Update mMainFragmentListRowDataAdapter and set it on mMainFragmentRowsAdapter. + * It also clears old mMainFragmentListRowDataAdapter. + */ + void updateMainFragmentRowsAdapter() { + if (mMainFragmentListRowDataAdapter != null) { + mMainFragmentListRowDataAdapter.detach(); + mMainFragmentListRowDataAdapter = null; + } if (mMainFragmentRowsAdapter != null) { - mMainFragmentRowsAdapter.setAdapter( - adapter == null ? null : new ListRowDataAdapter(adapter)); + mMainFragmentListRowDataAdapter = mAdapter == null + ? null : new ListRowDataAdapter(mAdapter); + mMainFragmentRowsAdapter.setAdapter(mMainFragmentListRowDataAdapter); } - mHeadersSupportFragment.setAdapter(adapter); } public final MainFragmentAdapterRegistry getMainFragmentRegistry() { @@ -1141,7 +1194,8 @@ public class BrowseSupportFragment extends BaseSupportFragment { @Override public void onDestroyView() { - mMainFragmentRowsAdapter = null; + setMainFragmentRowsAdapter(null); + mPageRow = null; mMainFragmentAdapter = null; mMainFragment = null; mHeadersSupportFragment = null; @@ -1195,26 +1249,17 @@ public class BrowseSupportFragment extends BaseSupportFragment { mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager() .findFragmentById(R.id.browse_headers_dock); mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame); - mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment) - .getMainFragmentAdapter(); - mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl()); mIsPageRow = savedInstanceState != null && savedInstanceState.getBoolean(IS_PAGE_ROW, false); + // mPageRow object is unable to restore, if its null and mIsPageRow is true, this is + // the case for restoring, later if setSelection() triggers a createMainFragment(), + // should not create fragment. mSelectedPosition = savedInstanceState != null ? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0; - if (!mIsPageRow) { - if (mMainFragment instanceof MainFragmentRowsAdapterProvider) { - mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment) - .getMainFragmentRowsAdapter(); - } else { - mMainFragmentRowsAdapter = null; - } - } else { - mMainFragmentRowsAdapter = null; - } + setMainFragmentAdapter(); } mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders); @@ -1239,8 +1284,6 @@ public class BrowseSupportFragment extends BaseSupportFragment { mScaleFrameLayout.setPivotX(0); mScaleFrameLayout.setPivotY(mContainerListAlignTop); - setupMainFragment(); - if (mBrandColorSet) { mHeadersSupportFragment.setBackgroundColor(mBrandColor); } @@ -1267,17 +1310,6 @@ public class BrowseSupportFragment extends BaseSupportFragment { return root; } - private void setupMainFragment() { - if (mMainFragmentRowsAdapter != null) { - if (mAdapter != null) { - mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(mAdapter)); - } - mMainFragmentRowsAdapter.setOnItemViewSelectedListener( - new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter)); - mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener); - } - } - void createHeadersTransition() { mHeadersTransition = TransitionHelper.loadTransition(getContext(), mShowingHeaders @@ -1467,10 +1499,10 @@ public class BrowseSupportFragment extends BaseSupportFragment { }; void onRowSelected(int position) { - if (position != mSelectedPosition) { - mSetSelectionRunnable.post( - position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true); - } + // even position is same, it could be data changed, always post selection runnable + // to possibly swap main fragment. + mSetSelectionRunnable.post( + position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true); } void setSelection(int position, boolean smooth) { @@ -1497,7 +1529,6 @@ public class BrowseSupportFragment extends BaseSupportFragment { if (createMainFragment(mAdapter, position)) { swapToMainFragment(); expandMainFragment(!(mCanShowHeaders && mShowingHeaders)); - setupMainFragment(); } } diff --git a/android/support/v17/leanback/app/DetailsFragment.java b/android/support/v17/leanback/app/DetailsFragment.java index 36559637..18934f45 100644 --- a/android/support/v17/leanback/app/DetailsFragment.java +++ b/android/support/v17/leanback/app/DetailsFragment.java @@ -91,7 +91,9 @@ import java.lang.ref.WeakReference; * DetailsFragment can use {@link DetailsFragmentBackgroundController} to add a parallax drawable * background and embedded video playing fragment. * </p> + * @deprecated use {@link DetailsSupportFragment} */ +@Deprecated public class DetailsFragment extends BaseFragment { static final String TAG = "DetailsFragment"; static boolean DEBUG = false; diff --git a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java index 223b8ef2..25ed723e 100644 --- a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java +++ b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java @@ -107,7 +107,9 @@ import android.app.Fragment; * {@link #onCreateGlueHost()}. * </p> * + * @deprecated use {@link DetailsSupportFragmentBackgroundController} */ +@Deprecated public class DetailsFragmentBackgroundController { final DetailsFragment mFragment; diff --git a/android/support/v17/leanback/app/ErrorFragment.java b/android/support/v17/leanback/app/ErrorFragment.java index 2896d0f4..eda0de16 100644 --- a/android/support/v17/leanback/app/ErrorFragment.java +++ b/android/support/v17/leanback/app/ErrorFragment.java @@ -32,7 +32,9 @@ import android.widget.TextView; /** * A fragment for displaying an error indication. + * @deprecated use {@link ErrorSupportFragment} */ +@Deprecated public class ErrorFragment extends BrandedFragment { private ViewGroup mErrorFrame; diff --git a/android/support/v17/leanback/app/GuidedStepFragment.java b/android/support/v17/leanback/app/GuidedStepFragment.java index 2b7f2d0d..9be350d8 100644 --- a/android/support/v17/leanback/app/GuidedStepFragment.java +++ b/android/support/v17/leanback/app/GuidedStepFragment.java @@ -27,6 +27,7 @@ import android.support.annotation.NonNull; import android.support.annotation.RestrictTo; import android.support.v17.leanback.R; import android.support.v17.leanback.transition.TransitionHelper; +import android.support.v17.leanback.widget.DiffCallback; import android.support.v17.leanback.widget.GuidanceStylist; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; @@ -140,7 +141,9 @@ import java.util.List; * @see GuidanceStylist.Guidance * @see GuidedAction * @see GuidedActionsStylist + * @deprecated use {@link GuidedStepSupportFragment} */ +@Deprecated public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.FocusListener { private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment"; @@ -806,6 +809,8 @@ public class GuidedStepFragment extends Fragment implements GuidedActionAdapter. /** * Sets the list of GuidedActions that the user may take in this fragment. + * Uses DiffCallback set by {@link #setActionsDiffCallback(DiffCallback)}. + * * @param actions The list of GuidedActions for this fragment. */ public void setActions(List<GuidedAction> actions) { @@ -816,6 +821,18 @@ public class GuidedStepFragment extends Fragment implements GuidedActionAdapter. } /** + * Sets the RecyclerView DiffCallback used when {@link #setActions(List)} is called. By default + * GuidedStepFragment uses + * {@link android.support.v17.leanback.widget.GuidedActionDiffCallback}. + * Sets it to null if app wants to refresh the whole list. + * + * @param diffCallback DiffCallback used in {@link #setActions(List)}. + */ + public void setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback) { + mAdapter.setDiffCallback(diffCallback); + } + + /** * Notify an action has changed and update its UI. * @param position Position of the GuidedAction in array. */ diff --git a/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/android/support/v17/leanback/app/GuidedStepSupportFragment.java index aeb2d334..e276d076 100644 --- a/android/support/v17/leanback/app/GuidedStepSupportFragment.java +++ b/android/support/v17/leanback/app/GuidedStepSupportFragment.java @@ -24,6 +24,7 @@ import android.support.annotation.NonNull; import android.support.annotation.RestrictTo; import android.support.v17.leanback.R; import android.support.v17.leanback.transition.TransitionHelper; +import android.support.v17.leanback.widget.DiffCallback; import android.support.v17.leanback.widget.GuidanceStylist; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; @@ -803,6 +804,8 @@ public class GuidedStepSupportFragment extends Fragment implements GuidedActionA /** * Sets the list of GuidedActions that the user may take in this fragment. + * Uses DiffCallback set by {@link #setActionsDiffCallback(DiffCallback)}. + * * @param actions The list of GuidedActions for this fragment. */ public void setActions(List<GuidedAction> actions) { @@ -813,6 +816,18 @@ public class GuidedStepSupportFragment extends Fragment implements GuidedActionA } /** + * Sets the RecyclerView DiffCallback used when {@link #setActions(List)} is called. By default + * GuidedStepSupportFragment uses + * {@link android.support.v17.leanback.widget.GuidedActionDiffCallback}. + * Sets it to null if app wants to refresh the whole list. + * + * @param diffCallback DiffCallback used in {@link #setActions(List)}. + */ + public void setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback) { + mAdapter.setDiffCallback(diffCallback); + } + + /** * Notify an action has changed and update its UI. * @param position Position of the GuidedAction in array. */ diff --git a/android/support/v17/leanback/app/HeadersFragment.java b/android/support/v17/leanback/app/HeadersFragment.java index dd037d2f..08780a50 100644 --- a/android/support/v17/leanback/app/HeadersFragment.java +++ b/android/support/v17/leanback/app/HeadersFragment.java @@ -52,12 +52,16 @@ import android.widget.FrameLayout; * </ul> * Use {@link #setPresenterSelector(PresenterSelector)} in subclass constructor to customize * Presenters. App may override {@link BrowseFragment#onCreateHeadersFragment()}. + * @deprecated use {@link HeadersSupportFragment} */ +@Deprecated public class HeadersFragment extends BaseRowFragment { /** * Interface definition for a callback to be invoked when a header item is clicked. + * @deprecated use {@link HeadersSupportFragment} */ + @Deprecated public interface OnHeaderClickedListener { /** * Called when a header item has been clicked. @@ -70,7 +74,9 @@ public class HeadersFragment extends BaseRowFragment { /** * Interface definition for a callback to be invoked when a header item is selected. + * @deprecated use {@link HeadersSupportFragment} */ + @Deprecated public interface OnHeaderViewSelectedListener { /** * Called when a header item has been selected. diff --git a/android/support/v17/leanback/app/ListRowDataAdapter.java b/android/support/v17/leanback/app/ListRowDataAdapter.java index f9af12f3..03d948be 100644 --- a/android/support/v17/leanback/app/ListRowDataAdapter.java +++ b/android/support/v17/leanback/app/ListRowDataAdapter.java @@ -13,6 +13,7 @@ import android.support.v17.leanback.widget.Row; * thinks there are items even though they're invisible. This class takes care of filtering out * the invisible rows at the end. In case the data inside the adapter changes, it adjusts the * bounds to reflect the latest data. + * {@link #detach()} must be called to release DataObserver from Adapter. */ class ListRowDataAdapter extends ObjectAdapter { public static final int ON_ITEM_RANGE_CHANGED = 2; @@ -22,6 +23,7 @@ class ListRowDataAdapter extends ObjectAdapter { private final ObjectAdapter mAdapter; int mLastVisibleRowIndex; + final DataObserver mDataObserver; public ListRowDataAdapter(ObjectAdapter adapter) { super(adapter.getPresenterSelector()); @@ -34,10 +36,20 @@ class ListRowDataAdapter extends ObjectAdapter { // operation. To handle this case, we use QueueBasedDataObserver which forces // recyclerview to do a full data refresh after each update operation. if (adapter.isImmediateNotifySupported()) { - mAdapter.registerObserver(new SimpleDataObserver()); + mDataObserver = new SimpleDataObserver(); } else { - mAdapter.registerObserver(new QueueBasedDataObserver()); + mDataObserver = new QueueBasedDataObserver(); } + attach(); + } + + void detach() { + mAdapter.unregisterObserver(mDataObserver); + } + + void attach() { + initialize(); + mAdapter.registerObserver(mDataObserver); } void initialize() { diff --git a/android/support/v17/leanback/app/OnboardingFragment.java b/android/support/v17/leanback/app/OnboardingFragment.java index b69d5a72..f352c413 100644 --- a/android/support/v17/leanback/app/OnboardingFragment.java +++ b/android/support/v17/leanback/app/OnboardingFragment.java @@ -154,7 +154,9 @@ import java.util.List; * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingPageIndicatorStyle * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle + * @deprecated use {@link OnboardingSupportFragment} */ +@Deprecated abstract public class OnboardingFragment extends Fragment { private static final String TAG = "OnboardingF"; private static final boolean DEBUG = false; diff --git a/android/support/v17/leanback/app/PlaybackFragment.java b/android/support/v17/leanback/app/PlaybackFragment.java index 33e787c3..e2e6be48 100644 --- a/android/support/v17/leanback/app/PlaybackFragment.java +++ b/android/support/v17/leanback/app/PlaybackFragment.java @@ -81,7 +81,9 @@ import android.view.animation.AccelerateInterpolator; * {@link #setControlsOverlayAutoHideEnabled(boolean)} upon play/pause. The auto hiding timer will * be cancelled upon {@link #tickle()} triggered by input event. * </p> + * @deprecated use {@link PlaybackSupportFragment} */ +@Deprecated public class PlaybackFragment extends Fragment { static final String BUNDLE_CONTROL_VISIBLE_ON_CREATEVIEW = "controlvisible_oncreateview"; @@ -181,7 +183,9 @@ public class PlaybackFragment extends Fragment { * Listener allowing the application to receive notification of fade in and/or fade out * completion events. * @hide + * @deprecated use {@link PlaybackSupportFragment} */ + @Deprecated public static class OnFadeCompleteListener { public void onFadeInComplete() { } diff --git a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java index 4a9d10f8..9e342fdb 100644 --- a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java +++ b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java @@ -30,7 +30,9 @@ import android.view.View; /** * {@link PlaybackGlueHost} implementation * the interaction between this class and {@link PlaybackFragment}. + * @deprecated use {@link PlaybackSupportFragmentGlueHost} */ +@Deprecated public class PlaybackFragmentGlueHost extends PlaybackGlueHost implements PlaybackSeekUi { private final PlaybackFragment mFragment; diff --git a/android/support/v17/leanback/app/RowsFragment.java b/android/support/v17/leanback/app/RowsFragment.java index a008ad60..aa346bd9 100644 --- a/android/support/v17/leanback/app/RowsFragment.java +++ b/android/support/v17/leanback/app/RowsFragment.java @@ -53,7 +53,9 @@ import java.util.ArrayList; * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses * of {@link RowPresenter}. * </p> + * @deprecated use {@link RowsSupportFragment} */ +@Deprecated public class RowsFragment extends BaseRowFragment implements BrowseFragment.MainFragmentRowsAdapterProvider, BrowseFragment.MainFragmentAdapterProvider { @@ -634,7 +636,9 @@ public class RowsFragment extends BaseRowFragment implements * The adapter that RowsFragment implements * BrowseFragment.MainFragmentRowsAdapter. * @see #getMainFragmentRowsAdapter(). + * @deprecated use {@link RowsSupportFragment} */ + @Deprecated public static class MainFragmentRowsAdapter extends BrowseFragment.MainFragmentRowsAdapter<RowsFragment> { diff --git a/android/support/v17/leanback/app/SearchFragment.java b/android/support/v17/leanback/app/SearchFragment.java index 2154ff28..00f2cca8 100644 --- a/android/support/v17/leanback/app/SearchFragment.java +++ b/android/support/v17/leanback/app/SearchFragment.java @@ -66,7 +66,9 @@ import java.util.List; * not when fragment is restored from an instance state. Activity may manually * call {@link #startRecognition()}, typically in onNewIntent(). * </p> + * @deprecated use {@link SearchSupportFragment} */ +@Deprecated public class SearchFragment extends Fragment { static final String TAG = SearchFragment.class.getSimpleName(); static final boolean DEBUG = false; diff --git a/android/support/v17/leanback/app/VerticalGridFragment.java b/android/support/v17/leanback/app/VerticalGridFragment.java index 5bc52ff5..bff3dbab 100644 --- a/android/support/v17/leanback/app/VerticalGridFragment.java +++ b/android/support/v17/leanback/app/VerticalGridFragment.java @@ -39,7 +39,9 @@ import android.view.ViewGroup; * * <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and * an {@link ObjectAdapter}. + * @deprecated use {@link VerticalGridSupportFragment} */ +@Deprecated public class VerticalGridFragment extends BaseFragment { static final String TAG = "VerticalGF"; static boolean DEBUG = false; diff --git a/android/support/v17/leanback/app/VideoFragment.java b/android/support/v17/leanback/app/VideoFragment.java index 1b2b8d07..e4d75f30 100644 --- a/android/support/v17/leanback/app/VideoFragment.java +++ b/android/support/v17/leanback/app/VideoFragment.java @@ -27,7 +27,9 @@ import android.view.ViewGroup; /** * Subclass of {@link PlaybackFragment} that is responsible for providing a {@link SurfaceView} * and rendering video. + * @deprecated use {@link VideoSupportFragment} */ +@Deprecated public class VideoFragment extends PlaybackFragment { static final int SURFACE_NOT_CREATED = 0; static final int SURFACE_CREATED = 1; diff --git a/android/support/v17/leanback/app/VideoFragmentGlueHost.java b/android/support/v17/leanback/app/VideoFragmentGlueHost.java index d123676f..546e581c 100644 --- a/android/support/v17/leanback/app/VideoFragmentGlueHost.java +++ b/android/support/v17/leanback/app/VideoFragmentGlueHost.java @@ -24,7 +24,9 @@ import android.view.SurfaceHolder; /** * {@link PlaybackGlueHost} implementation * the interaction between {@link PlaybackGlue} and {@link VideoFragment}. + * @deprecated use {@link VideoSupportFragmentGlueHost} */ +@Deprecated public class VideoFragmentGlueHost extends PlaybackFragmentGlueHost implements SurfaceHolderGlueHost { private final VideoFragment mFragment; diff --git a/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/android/support/v17/leanback/widget/ArrayObjectAdapter.java index 00bc073d..2dcf51f7 100644 --- a/android/support/v17/leanback/widget/ArrayObjectAdapter.java +++ b/android/support/v17/leanback/widget/ArrayObjectAdapter.java @@ -225,6 +225,8 @@ public class ArrayObjectAdapter extends ObjectAdapter { return true; } + ListUpdateCallback mListUpdateCallback; + /** * Set a new item list to adapter. The DiffUtil will compute the difference and dispatch it to * specified position. @@ -280,39 +282,43 @@ public class ArrayObjectAdapter extends ObjectAdapter { mItems.addAll(itemList); // dispatch diff result - diffResult.dispatchUpdatesTo(new ListUpdateCallback() { - - @Override - public void onInserted(int position, int count) { - if (DEBUG) { - Log.d(TAG, "onInserted"); + if (mListUpdateCallback == null) { + mListUpdateCallback = new ListUpdateCallback() { + + @Override + public void onInserted(int position, int count) { + if (DEBUG) { + Log.d(TAG, "onInserted"); + } + notifyItemRangeInserted(position, count); } - notifyItemRangeInserted(position, count); - } - @Override - public void onRemoved(int position, int count) { - if (DEBUG) { - Log.d(TAG, "onRemoved"); + @Override + public void onRemoved(int position, int count) { + if (DEBUG) { + Log.d(TAG, "onRemoved"); + } + notifyItemRangeRemoved(position, count); } - notifyItemRangeRemoved(position, count); - } - @Override - public void onMoved(int fromPosition, int toPosition) { - if (DEBUG) { - Log.d(TAG, "onMoved"); + @Override + public void onMoved(int fromPosition, int toPosition) { + if (DEBUG) { + Log.d(TAG, "onMoved"); + } + notifyItemMoved(fromPosition, toPosition); } - notifyItemMoved(fromPosition, toPosition); - } - @Override - public void onChanged(int position, int count, Object payload) { - if (DEBUG) { - Log.d(TAG, "onChanged"); + @Override + public void onChanged(int position, int count, Object payload) { + if (DEBUG) { + Log.d(TAG, "onChanged"); + } + notifyItemRangeChanged(position, count, payload); } - notifyItemRangeChanged(position, count, payload); - } - }); + }; + } + diffResult.dispatchUpdatesTo(mListUpdateCallback); + mOldItems.clear(); } } diff --git a/android/support/v17/leanback/widget/BaseGridView.java b/android/support/v17/leanback/widget/BaseGridView.java index f4e01c0b..2ebec47e 100644 --- a/android/support/v17/leanback/widget/BaseGridView.java +++ b/android/support/v17/leanback/widget/BaseGridView.java @@ -1134,7 +1134,7 @@ public abstract class BaseGridView extends RecyclerView { @Override public void scrollToPosition(int position) { // dont abort the animateOut() animation, just record the position - if (mLayoutManager.mIsSlidingChildViews) { + if (mLayoutManager.isSlidingChildViews()) { mLayoutManager.setSelectionWithSub(position, 0, 0); return; } @@ -1144,7 +1144,7 @@ public abstract class BaseGridView extends RecyclerView { @Override public void smoothScrollToPosition(int position) { // dont abort the animateOut() animation, just record the position - if (mLayoutManager.mIsSlidingChildViews) { + if (mLayoutManager.isSlidingChildViews()) { mLayoutManager.setSelectionWithSub(position, 0, 0); return; } diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java index dded0715..d7020e91 100644 --- a/android/support/v17/leanback/widget/GridLayoutManager.java +++ b/android/support/v17/leanback/widget/GridLayoutManager.java @@ -217,9 +217,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mFocusPosition = getTargetPosition(); } if (hasFocus()) { - mInSelection = true; + mFlag |= PF_IN_SELECTION; targetView.requestFocus(); - mInSelection = false; + mFlag &= ~PF_IN_SELECTION; } dispatchChildSelected(); dispatchChildSelectedAndPositioned(); @@ -320,9 +320,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } } if (newSelected != null && hasFocus()) { - mInSelection = true; + mFlag |= PF_IN_SELECTION; newSelected.requestFocus(); - mInSelection = false; + mFlag &= ~PF_IN_SELECTION; } } @@ -355,7 +355,8 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { if (mPendingMoves == 0) { return null; } - int direction = (mReverseFlowPrimary ? mPendingMoves > 0 : mPendingMoves < 0) + int direction = ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 + ? mPendingMoves > 0 : mPendingMoves < 0) ? -1 : 1; if (mOrientation == HORIZONTAL) { return new PointF(direction, 0); @@ -386,10 +387,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { // effect smooth scrolling too over to bind an item view then drag the item view back. final static int MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN = 30; - // Represents whether child views are temporarily sliding out - boolean mIsSlidingChildViews; - boolean mLayoutEatenInSliding; - String getTag() { return TAG + ":" + mBaseGridView.getId(); } @@ -444,15 +441,101 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { private static final Rect sTempRect = new Rect(); - boolean mInLayout; - private boolean mInScroll; - boolean mInFastRelayout; + // 2 bits mask is for 3 STAGEs: 0, PF_STAGE_LAYOUT or PF_STAGE_SCROLL. + static final int PF_STAGE_MASK = 0x3; + static final int PF_STAGE_LAYOUT = 0x1; + static final int PF_STAGE_SCROLL = 0x2; + + // Flag for "in fast relayout", determined by layoutInit() result. + static final int PF_FAST_RELAYOUT = 1 << 2; + + // Flag for the selected item being updated in fast relayout. + static final int PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION = 1 << 3; /** * During full layout pass, when GridView had focus: onLayoutChildren will * skip non-focusable child and adjust mFocusPosition. */ - boolean mInLayoutSearchFocus; - boolean mInSelection = false; + static final int PF_IN_LAYOUT_SEARCH_FOCUS = 1 << 4; + + // flag to prevent reentry if it's already processing selection request. + static final int PF_IN_SELECTION = 1 << 5; + + // Represents whether child views are temporarily sliding out + static final int PF_SLIDING = 1 << 6; + static final int PF_LAYOUT_EATEN_IN_SLIDING = 1 << 7; + + /** + * Force a full layout under certain situations. E.g. Rows change, jump to invisible child. + */ + static final int PF_FORCE_FULL_LAYOUT = 1 << 8; + + /** + * True if layout is enabled. + */ + static final int PF_LAYOUT_ENABLED = 1 << 9; + + /** + * Flag controlling whether the current/next layout should + * be updating the secondary size of rows. + */ + static final int PF_ROW_SECONDARY_SIZE_REFRESH = 1 << 10; + + /** + * Allow DPAD key to navigate out at the front of the View (where position = 0), + * default is false. + */ + static final int PF_FOCUS_OUT_FRONT = 1 << 11; + + /** + * Allow DPAD key to navigate out at the end of the view, default is false. + */ + static final int PF_FOCUS_OUT_END = 1 << 12; + + static final int PF_FOCUS_OUT_MASKS = PF_FOCUS_OUT_FRONT | PF_FOCUS_OUT_END; + + /** + * Allow DPAD key to navigate out of second axis. + * default is true. + */ + static final int PF_FOCUS_OUT_SIDE_START = 1 << 13; + + /** + * Allow DPAD key to navigate out of second axis. + */ + static final int PF_FOCUS_OUT_SIDE_END = 1 << 14; + + static final int PF_FOCUS_OUT_SIDE_MASKS = PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END; + + /** + * True if focus search is disabled. + */ + static final int PF_FOCUS_SEARCH_DISABLED = 1 << 15; + + /** + * True if prune child, might be disabled during transition. + */ + static final int PF_PRUNE_CHILD = 1 << 16; + + /** + * True if scroll content, might be disabled during transition. + */ + static final int PF_SCROLL_ENABLED = 1 << 17; + + /** + * Set to true for RTL layout in horizontal orientation + */ + static final int PF_REVERSE_FLOW_PRIMARY = 1 << 18; + + /** + * Set to true for RTL layout in vertical orientation + */ + static final int PF_REVERSE_FLOW_SECONDARY = 1 << 19; + + static final int PF_REVERSE_FLOW_MASK = PF_REVERSE_FLOW_PRIMARY | PF_REVERSE_FLOW_SECONDARY; + + int mFlag = PF_LAYOUT_ENABLED + | PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END + | PF_PRUNE_CHILD | PF_SCROLL_ENABLED; private OnChildSelectedListener mChildSelectedListener = null; @@ -493,16 +576,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { private int mPrimaryScrollExtra; /** - * Force a full layout under certain situations. E.g. Rows change, jump to invisible child. - */ - private boolean mForceFullLayout; - - /** - * True if layout is enabled. - */ - private boolean mLayoutEnabled = true; - - /** * override child visibility */ @Visibility @@ -535,12 +608,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { private int[] mRowSizeSecondary; /** - * Flag controlling whether the current/next layout should - * be updating the secondary size of rows. - */ - private boolean mRowSecondarySizeRefresh; - - /** * The maximum measured size of the view. */ private int mMaxSizeSecondary; @@ -605,58 +672,11 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { private int mExtraLayoutSpace; /** - * Allow DPAD key to navigate out at the front of the View (where position = 0), - * default is false. - */ - private boolean mFocusOutFront; - - /** - * Allow DPAD key to navigate out at the end of the view, default is false. - */ - private boolean mFocusOutEnd; - - /** - * Allow DPAD key to navigate out of second axis. - * default is true. - */ - private boolean mFocusOutSideStart = true; - - /** - * Allow DPAD key to navigate out of second axis. - */ - private boolean mFocusOutSideEnd = true; - - /** - * True if focus search is disabled. - */ - private boolean mFocusSearchDisabled; - - /** - * True if prune child, might be disabled during transition. - */ - private boolean mPruneChild = true; - - /** - * True if scroll content, might be disabled during transition. - */ - private boolean mScrollEnabled = true; - - /** * Temporary variable: an int array of length=2. */ static int[] sTwoInts = new int[2]; /** - * Set to true for RTL layout in horizontal orientation - */ - boolean mReverseFlowPrimary = false; - - /** - * Set to true for RTL layout in vertical orientation - */ - private boolean mReverseFlowSecondary = false; - - /** * Temporaries used for measuring. */ private int[] mMeasuredDimension = new int[2]; @@ -685,24 +705,21 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation); mWindowAlignment.setOrientation(orientation); mItemAlignment.setOrientation(orientation); - mForceFullLayout = true; + mFlag |= PF_FORCE_FULL_LAYOUT; } public void onRtlPropertiesChanged(int layoutDirection) { - boolean reversePrimary, reverseSecondary; + final int flags; if (mOrientation == HORIZONTAL) { - reversePrimary = layoutDirection == View.LAYOUT_DIRECTION_RTL; - reverseSecondary = false; + flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_PRIMARY : 0; } else { - reverseSecondary = layoutDirection == View.LAYOUT_DIRECTION_RTL; - reversePrimary = false; + flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_SECONDARY : 0; } - if (mReverseFlowPrimary == reversePrimary && mReverseFlowSecondary == reverseSecondary) { + if ((mFlag & PF_REVERSE_FLOW_MASK) == flags) { return; } - mReverseFlowPrimary = reversePrimary; - mReverseFlowSecondary = reverseSecondary; - mForceFullLayout = true; + mFlag = (mFlag & ~PF_REVERSE_FLOW_MASK) | flags; + mFlag |= PF_FORCE_FULL_LAYOUT; mWindowAlignment.horizontal.setReversedFlow(layoutDirection == View.LAYOUT_DIRECTION_RTL); } @@ -775,13 +792,15 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } public void setFocusOutAllowed(boolean throughFront, boolean throughEnd) { - mFocusOutFront = throughFront; - mFocusOutEnd = throughEnd; + mFlag = (mFlag & ~PF_FOCUS_OUT_MASKS) + | (throughFront ? PF_FOCUS_OUT_FRONT : 0) + | (throughEnd ? PF_FOCUS_OUT_END : 0); } public void setFocusOutSideAllowed(boolean throughStart, boolean throughEnd) { - mFocusOutSideStart = throughStart; - mFocusOutSideEnd = throughEnd; + mFlag = (mFlag & ~PF_FOCUS_OUT_SIDE_MASKS) + | (throughStart ? PF_FOCUS_OUT_SIDE_START : 0) + | (throughEnd ? PF_FOCUS_OUT_SIDE_END : 0); } public void setNumRows(int numRows) { @@ -971,7 +990,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { // layout warning. // If not in layout, we may be scrolling in which case the child layout request will be // eaten by recyclerview. Post a requestLayout. - if (!mInLayout && !mBaseGridView.isLayoutRequested()) { + if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && !mBaseGridView.isLayoutRequested()) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { if (getChildAt(i).isLayoutRequested()) { @@ -1177,19 +1196,19 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mSubFocusPosition = 0; } if (!mState.didStructureChange() && mGrid != null && mGrid.getFirstVisibleIndex() >= 0 - && !mForceFullLayout && mGrid.getNumRows() == mNumRows) { + && (mFlag & PF_FORCE_FULL_LAYOUT) == 0 && mGrid.getNumRows() == mNumRows) { updateScrollController(); updateSecondaryScrollLimits(); mGrid.setSpacing(mSpacingPrimary); return true; } else { - mForceFullLayout = false; + mFlag &= ~PF_FORCE_FULL_LAYOUT; if (mGrid == null || mNumRows != mGrid.getNumRows() - || mReverseFlowPrimary != mGrid.isReversedFlow()) { + || ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) != mGrid.isReversedFlow()) { mGrid = Grid.createGrid(mNumRows); mGrid.setProvider(mGridProvider); - mGrid.setReversedFlow(mReverseFlowPrimary); + mGrid.setReversedFlow((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0); } initScrollController(); updateSecondaryScrollLimits(); @@ -1216,7 +1235,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { int start = 0; // Iterate from left to right, which is a different index traversal // in RTL flow - if (mReverseFlowSecondary) { + if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) { for (int i = mNumRows-1; i > rowIndex; i--) { start += getRowSizeSecondary(i) + mSpacingSecondary; } @@ -1229,7 +1248,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } private int getSizeSecondary() { - int rightmostIndex = mReverseFlowSecondary ? 0 : mNumRows - 1; + int rightmostIndex = (mFlag & PF_REVERSE_FLOW_SECONDARY) != 0 ? 0 : mNumRows - 1; return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex); } @@ -1366,8 +1385,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { * Checks if we need to update row secondary sizes. */ private void updateRowSecondarySizeRefresh() { - mRowSecondarySizeRefresh = processRowSizeSecondary(false); - if (mRowSecondarySizeRefresh) { + mFlag = (mFlag & ~PF_ROW_SECONDARY_SIZE_REFRESH) + | (processRowSizeSecondary(false) ? PF_ROW_SECONDARY_SIZE_REFRESH : 0); + if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) { if (DEBUG) Log.v(getTag(), "mRowSecondarySizeRefresh now set"); forceRequestLayout(); } @@ -1599,7 +1619,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mPendingMoveSmoothScroller.consumePendingMovesBeforeLayout(); } int subindex = getSubPositionByView(v, v.findFocus()); - if (!mInLayout) { + if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) { // when we are appending item during scroll pass and the item's position // matches the mFocusPosition, we should signal a childSelected event. // However if we are still running PendingMoveSmoothScroller, we defer and @@ -1610,20 +1630,20 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { && mPendingMoveSmoothScroller == null) { dispatchChildSelected(); } - } else if (!mInFastRelayout) { + } else if ((mFlag & PF_FAST_RELAYOUT) == 0) { // fastRelayout will dispatch event at end of onLayoutChildren(). // For full layout, two situations here: // 1. mInLayoutSearchFocus is false, dispatchChildSelected() at mFocusPosition. // 2. mInLayoutSearchFocus is true: dispatchChildSelected() on first child // equal to or after mFocusPosition that can take focus. - if (!mInLayoutSearchFocus && index == mFocusPosition + if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) == 0 && index == mFocusPosition && subindex == mSubFocusPosition) { dispatchChildSelected(); - } else if (mInLayoutSearchFocus && index >= mFocusPosition + } else if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) != 0 && index >= mFocusPosition && v.hasFocusable()) { mFocusPosition = index; mSubFocusPosition = subindex; - mInLayoutSearchFocus = false; + mFlag &= ~PF_IN_LAYOUT_SEARCH_FOCUS; dispatchChildSelected(); } } @@ -1663,7 +1683,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { if (!mState.isPreLayout()) { updateScrollLimits(); } - if (!mInLayout && mPendingMoveSmoothScroller != null) { + if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && mPendingMoveSmoothScroller != null) { mPendingMoveSmoothScroller.consumePendingMovesAfterLayout(); } if (mChildLaidOutListener != null) { @@ -1677,7 +1697,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { public void removeItem(int index) { if (TRACE) TraceCompat.beginSection("removeItem"); View v = findViewByPosition(index - mPositionDeltaInPreLayout); - if (mInLayout) { + if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) { detachAndScrapView(v, mRecycler); } else { removeAndRecycleView(v, mRecycler); @@ -1688,7 +1708,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { @Override public int getEdge(int index) { View v = findViewByPosition(index - mPositionDeltaInPreLayout); - return mReverseFlowPrimary ? getViewMax(v) : getViewMin(v); + return (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? getViewMax(v) : getViewMin(v); } @Override @@ -1705,7 +1725,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary); } final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; - final int horizontalGravity = (mReverseFlowPrimary || mReverseFlowSecondary) + final int horizontalGravity = (mFlag & PF_REVERSE_FLOW_MASK) != 0 ? Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK, View.LAYOUT_DIRECTION_RTL) : mGravity & Gravity.HORIZONTAL_GRAVITY_MASK; @@ -1781,16 +1801,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } private void removeInvisibleViewsAtEnd() { - if (mPruneChild && !mIsSlidingChildViews) { - mGrid.removeInvisibleItemsAtEnd(mFocusPosition, - mReverseFlowPrimary ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace); + if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) { + mGrid.removeInvisibleItemsAtEnd(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 + ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace); } } private void removeInvisibleViewsAtFront() { - if (mPruneChild && !mIsSlidingChildViews) { - mGrid.removeInvisibleItemsAtFront(mFocusPosition, - mReverseFlowPrimary ? mSizePrimary + mExtraLayoutSpace: -mExtraLayoutSpace); + if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) { + mGrid.removeInvisibleItemsAtFront(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 + ? mSizePrimary + mExtraLayoutSpace : -mExtraLayoutSpace); } } @@ -1799,16 +1819,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } void slideIn() { - if (mIsSlidingChildViews) { - mIsSlidingChildViews = false; + if ((mFlag & PF_SLIDING) != 0) { + mFlag &= ~PF_SLIDING; if (mFocusPosition >= 0) { scrollToSelection(mFocusPosition, mSubFocusPosition, true, mPrimaryScrollExtra); } else { - mLayoutEatenInSliding = false; + mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING; requestLayout(); } - if (mLayoutEatenInSliding) { - mLayoutEatenInSliding = false; + if ((mFlag & PF_LAYOUT_EATEN_IN_SLIDING) != 0) { + mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING; if (mBaseGridView.getScrollState() != SCROLL_STATE_IDLE || isSmoothScrolling()) { mBaseGridView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override @@ -1838,7 +1858,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } } } else { - if (mReverseFlowPrimary) { + if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) { distance = getWidth(); if (getChildCount() > 0) { int start = getChildAt(0).getRight(); @@ -1861,14 +1881,18 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { return distance; } + boolean isSlidingChildViews() { + return (mFlag & PF_SLIDING) != 0; + } + /** * Temporarily slide out child and block layout and scroll requests. */ void slideOut() { - if (mIsSlidingChildViews) { + if ((mFlag & PF_SLIDING) != 0) { return; } - mIsSlidingChildViews = true; + mFlag |= PF_SLIDING; if (getChildCount() == 0) { return; } @@ -1886,13 +1910,13 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } private void appendVisibleItems() { - mGrid.appendVisibleItems(mReverseFlowPrimary + mGrid.appendVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout : mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout); } private void prependVisibleItems() { - mGrid.prependVisibleItems(mReverseFlowPrimary + mGrid.prependVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout : -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout); } @@ -1907,6 +1931,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { final int childCount = getChildCount(); int position = mGrid.getFirstVisibleIndex(); int index = 0; + mFlag &= ~PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION; for (; index < childCount; index++, position++) { View view = getChildAt(index); // We don't hit fastRelayout() if State.didStructure() is true, but prelayout may add @@ -1932,6 +1957,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { LayoutParams lp = (LayoutParams) view.getLayoutParams(); if (lp.viewNeedsUpdate()) { + mFlag |= PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION; detachAndScrapView(view, mRecycler); view = getViewForPosition(position); addView(view, index); @@ -1960,7 +1986,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { detachAndScrapView(v, mRecycler); } mGrid.invalidateItemsAfter(position); - if (mPruneChild) { + if ((mFlag & PF_PRUNE_CHILD) != 0) { // in regular prune child mode, we just append items up to edge limit appendVisibleItems(); if (mFocusPosition >= 0 && mFocusPosition <= savedLastPos) { @@ -2108,7 +2134,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { Log.v(getTag(), "layoutChildren start numRows " + mNumRows + " inPreLayout " + state.isPreLayout() + " didStructureChange " + state.didStructureChange() - + " mForceFullLayout " + mForceFullLayout); + + " mForceFullLayout " + ((mFlag & PF_FORCE_FULL_LAYOUT) != 0)); Log.v(getTag(), "width " + getWidth() + " height " + getHeight()); } @@ -2121,20 +2147,20 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { return; } - if (mIsSlidingChildViews) { + if ((mFlag & PF_SLIDING) != 0) { // if there is already children, delay the layout process until slideIn(), if it's // first time layout children: scroll them offscreen at end of onLayoutChildren() if (getChildCount() > 0) { - mLayoutEatenInSliding = true; + mFlag |= PF_LAYOUT_EATEN_IN_SLIDING; return; } } - if (!mLayoutEnabled) { + if ((mFlag & PF_LAYOUT_ENABLED) == 0) { discardLayoutInfo(); removeAndRecycleAllViews(recycler); return; } - mInLayout = true; + mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_LAYOUT; saveContext(recycler, state); if (state.isPreLayout()) { @@ -2172,7 +2198,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { appendVisibleItems(); prependVisibleItems(); } - mInLayout = false; + mFlag &= ~PF_STAGE_MASK; leaveContext(); if (DEBUG) Log.v(getTag(), "layoutChildren end"); return; @@ -2206,13 +2232,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { deltaSecondary = state.getRemainingScrollHorizontal(); deltaPrimary = state.getRemainingScrollVertical(); } - if (mInFastRelayout = layoutInit()) { + if (layoutInit()) { + mFlag |= PF_FAST_RELAYOUT; // If grid view is empty, we will start from mFocusPosition mGrid.setStart(mFocusPosition); fastRelayout(); } else { + mFlag &= ~PF_FAST_RELAYOUT; // layoutInit() has detached all views, so start from scratch - mInLayoutSearchFocus = hadFocus; + mFlag = (mFlag & ~PF_IN_LAYOUT_SEARCH_FOCUS) + | (hadFocus ? PF_IN_LAYOUT_SEARCH_FOCUS : 0); int startFromPosition, endPos; if (scrollToFocus && (firstVisibleIndex < 0 || mFocusPosition > lastVisibleIndex || mFocusPosition < firstVisibleIndex)) { @@ -2270,27 +2299,30 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { Log.d(getTag(), sw.toString()); } - if (mRowSecondarySizeRefresh) { - mRowSecondarySizeRefresh = false; + if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) { + mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH; } else { updateRowSecondarySizeRefresh(); } - // For fastRelayout, only dispatch event when focus position changes. - if (mInFastRelayout && (mFocusPosition != savedFocusPos || mSubFocusPosition - != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView)) { + // For fastRelayout, only dispatch event when focus position changes or selected item + // being updated. + if ((mFlag & PF_FAST_RELAYOUT) != 0 && (mFocusPosition != savedFocusPos || mSubFocusPosition + != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView + || (mFlag & PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION) != 0)) { dispatchChildSelected(); - } else if (!mInFastRelayout && mInLayoutSearchFocus) { + } else if ((mFlag & (PF_FAST_RELAYOUT | PF_IN_LAYOUT_SEARCH_FOCUS)) + == PF_IN_LAYOUT_SEARCH_FOCUS) { // For full layout we dispatchChildSelected() in createItem() unless searched all // children and found none is focusable then dispatchChildSelected() here. dispatchChildSelected(); } dispatchChildSelectedAndPositioned(); - if (mIsSlidingChildViews) { + if ((mFlag & PF_SLIDING) != 0) { scrollDirectionPrimary(getSlideOutDistance()); } - mInLayout = false; + mFlag &= ~PF_STAGE_MASK; leaveContext(); if (DEBUG) Log.v(getTag(), "layoutChildren end"); } @@ -2324,11 +2356,11 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { @Override public int scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state) { if (DEBUG) Log.v(getTag(), "scrollHorizontallyBy " + dx); - if (!mLayoutEnabled || !hasDoneFirstLayout()) { + if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) { return 0; } saveContext(recycler, state); - mInScroll = true; + mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL; int result; if (mOrientation == HORIZONTAL) { result = scrollDirectionPrimary(dx); @@ -2336,17 +2368,17 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { result = scrollDirectionSecondary(dx); } leaveContext(); - mInScroll = false; + mFlag &= ~PF_STAGE_MASK; return result; } @Override public int scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state) { if (DEBUG) Log.v(getTag(), "scrollVerticallyBy " + dy); - if (!mLayoutEnabled || !hasDoneFirstLayout()) { + if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) { return 0; } - mInScroll = true; + mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL; saveContext(recycler, state); int result; if (mOrientation == VERTICAL) { @@ -2355,7 +2387,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { result = scrollDirectionSecondary(dy); } leaveContext(); - mInScroll = false; + mFlag &= ~PF_STAGE_MASK; return result; } @@ -2367,7 +2399,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { // 2. During onLayoutChildren(), it may compensate the remaining scroll delta, // we should honor the request regardless if it goes over minScroll / maxScroll. // (see b/64931938 testScrollAndRemove and testScrollAndRemoveSample1) - if (!mIsSlidingChildViews && !mInLayout) { + if ((mFlag & PF_SLIDING) == 0 && (mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) { if (da > 0) { if (!mWindowAlignment.mainAxis().isMaxUnknown()) { int maxScroll = mWindowAlignment.mainAxis().getMaxScroll(); @@ -2389,7 +2421,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { return 0; } offsetChildrenPrimary(-da); - if (mInLayout) { + if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) { updateScrollLimits(); if (TRACE) TraceCompat.endSection(); return da; @@ -2398,7 +2430,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { int childCount = getChildCount(); boolean updated; - if (mReverseFlowPrimary ? da > 0 : da < 0) { + if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) { prependVisibleItems(); } else { appendVisibleItems(); @@ -2407,7 +2439,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { childCount = getChildCount(); if (TRACE) TraceCompat.beginSection("remove"); - if (mReverseFlowPrimary ? da > 0 : da < 0) { + if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) { removeInvisibleViewsAtEnd(); } else { removeInvisibleViewsAtFront(); @@ -2476,7 +2508,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } int highVisiblePos, lowVisiblePos; int highMaxPos, lowMinPos; - if (!mReverseFlowPrimary) { + if ((mFlag & PF_REVERSE_FLOW_PRIMARY) == 0) { highVisiblePos = mGrid.getLastVisibleIndex(); highMaxPos = mState.getItemCount() - 1; lowVisiblePos = mGrid.getFirstVisibleIndex(); @@ -2614,14 +2646,14 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { // scrollToView() is based on Adapter position. Only call scrollToView() when item // is still valid. if (view != null && getAdapterPositionByView(view) == position) { - mInSelection = true; + mFlag |= PF_IN_SELECTION; scrollToView(view, smooth); - mInSelection = false; + mFlag &= ~PF_IN_SELECTION; } else { mFocusPosition = position; mSubFocusPosition = subposition; mFocusPositionOffset = Integer.MIN_VALUE; - if (!mLayoutEnabled || mIsSlidingChildViews) { + if ((mFlag & PF_LAYOUT_ENABLED) == 0 || (mFlag & PF_SLIDING) != 0) { return; } if (smooth) { @@ -2637,7 +2669,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mSubFocusPosition = 0; } } else { - mForceFullLayout = true; + mFlag |= PF_FORCE_FULL_LAYOUT; requestLayout(); } } @@ -2654,7 +2686,8 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { final int firstChildPos = getPosition(getChildAt(0)); // TODO We should be able to deduce direction from bounds of current and target // focus, rather than making assumptions about positions and directionality - final boolean isStart = mReverseFlowPrimary ? targetPosition > firstChildPos + final boolean isStart = (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 + ? targetPosition > firstChildPos : targetPosition < firstChildPos; final int direction = isStart ? -1 : 1; if (mOrientation == HORIZONTAL) { @@ -2788,14 +2821,14 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { @Override public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) { - if (mFocusSearchDisabled) { + if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) { return true; } if (getAdapterPositionByView(child) == NO_POSITION) { // This is could be the last view in DISAPPEARING animation. return true; } - if (!mInLayout && !mInSelection && !mInScroll) { + if ((mFlag & (PF_STAGE_MASK | PF_IN_SELECTION)) == 0) { scrollToView(child, focused, true); } return true; @@ -2865,7 +2898,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { */ private void scrollToView(View view, View childView, boolean smooth, int extraDelta, int extraDeltaSecondary) { - if (mIsSlidingChildViews) { + if ((mFlag & PF_SLIDING) != 0) { return; } int newFocusPosition = getAdapterPositionByView(view); @@ -2874,7 +2907,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mFocusPosition = newFocusPosition; mSubFocusPosition = newSubFocusPosition; mFocusPositionOffset = 0; - if (!mInLayout) { + if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) { dispatchChildSelected(); } if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) { @@ -2889,7 +2922,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { // by setSelection()) view.requestFocus(); } - if (!mScrollEnabled && smooth) { + if ((mFlag & PF_SCROLL_ENABLED) == 0 && smooth) { return; } if (getScrollPosition(view, childView, sTwoInts) @@ -3007,7 +3040,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) { - if (mInLayout) { + if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) { scrollDirectionPrimary(scrollPrimary); scrollDirectionSecondary(scrollSecondary); } else { @@ -3030,22 +3063,23 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } public void setPruneChild(boolean pruneChild) { - if (mPruneChild != pruneChild) { - mPruneChild = pruneChild; - if (mPruneChild) { + if (((mFlag & PF_PRUNE_CHILD) != 0) != pruneChild) { + mFlag = (mFlag & ~PF_PRUNE_CHILD) | (pruneChild ? PF_PRUNE_CHILD : 0); + if (pruneChild) { requestLayout(); } } } public boolean getPruneChild() { - return mPruneChild; + return (mFlag & PF_PRUNE_CHILD) != 0; } public void setScrollEnabled(boolean scrollEnabled) { - if (mScrollEnabled != scrollEnabled) { - mScrollEnabled = scrollEnabled; - if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED + if (((mFlag & PF_SCROLL_ENABLED) != 0) != scrollEnabled) { + mFlag = (mFlag & ~PF_SCROLL_ENABLED) | (scrollEnabled ? PF_SCROLL_ENABLED : 0); + if (((mFlag & PF_SCROLL_ENABLED) != 0) + && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED && mFocusPosition != NO_POSITION) { scrollToSelection(mFocusPosition, mSubFocusPosition, true, mPrimaryScrollExtra); @@ -3054,7 +3088,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } public boolean isScrollEnabled() { - return mScrollEnabled; + return (mFlag & PF_SCROLL_ENABLED) != 0; } private int findImmediateChildIndex(View view) { @@ -3088,16 +3122,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } void setFocusSearchDisabled(boolean disabled) { - mFocusSearchDisabled = disabled; + mFlag = (mFlag & ~PF_FOCUS_SEARCH_DISABLED) | (disabled ? PF_FOCUS_SEARCH_DISABLED : 0); } boolean isFocusSearchDisabled() { - return mFocusSearchDisabled; + return (mFlag & PF_FOCUS_SEARCH_DISABLED) != 0; } @Override public View onInterceptFocusSearch(View focused, int direction) { - if (mFocusSearchDisabled) { + if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) { return focused; } @@ -3132,27 +3166,27 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { int movement = getMovement(direction); final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE; if (movement == NEXT_ITEM) { - if (isScroll || !mFocusOutEnd) { + if (isScroll || (mFlag & PF_FOCUS_OUT_END) == 0) { result = focused; } - if (mScrollEnabled && !hasCreatedLastItem()) { + if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedLastItem()) { processPendingMovement(true); result = focused; } } else if (movement == PREV_ITEM) { - if (isScroll || !mFocusOutFront) { + if (isScroll || (mFlag & PF_FOCUS_OUT_FRONT) == 0) { result = focused; } - if (mScrollEnabled && !hasCreatedFirstItem()) { + if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedFirstItem()) { processPendingMovement(false); result = focused; } } else if (movement == NEXT_ROW) { - if (isScroll || !mFocusOutSideEnd) { + if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_END) == 0) { result = focused; } } else if (movement == PREV_ROW) { - if (isScroll || !mFocusOutSideStart) { + if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_START) == 0) { result = focused; } } @@ -3191,7 +3225,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { @Override public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views, int direction, int focusableMode) { - if (mFocusSearchDisabled) { + if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) { return true; } // If this viewgroup or one of its children currently has focus then we @@ -3423,10 +3457,10 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { if (mOrientation == HORIZONTAL) { switch(direction) { case View.FOCUS_LEFT: - movement = (!mReverseFlowPrimary) ? PREV_ITEM : NEXT_ITEM; + movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? PREV_ITEM : NEXT_ITEM; break; case View.FOCUS_RIGHT: - movement = (!mReverseFlowPrimary) ? NEXT_ITEM : PREV_ITEM; + movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? NEXT_ITEM : PREV_ITEM; break; case View.FOCUS_UP: movement = PREV_ROW; @@ -3438,10 +3472,10 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } else if (mOrientation == VERTICAL) { switch(direction) { case View.FOCUS_LEFT: - movement = (!mReverseFlowSecondary) ? PREV_ROW : NEXT_ROW; + movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? PREV_ROW : NEXT_ROW; break; case View.FOCUS_RIGHT: - movement = (!mReverseFlowSecondary) ? NEXT_ROW : PREV_ROW; + movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? NEXT_ROW : PREV_ROW; break; case View.FOCUS_UP: movement = PREV_ITEM; @@ -3497,12 +3531,12 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { private void discardLayoutInfo() { mGrid = null; mRowSizeSecondary = null; - mRowSecondarySizeRefresh = false; + mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH; } public void setLayoutEnabled(boolean layoutEnabled) { - if (mLayoutEnabled != layoutEnabled) { - mLayoutEnabled = layoutEnabled; + if (((mFlag & PF_LAYOUT_ENABLED) != 0) != layoutEnabled) { + mFlag = (mFlag & ~PF_LAYOUT_ENABLED) | (layoutEnabled ? PF_LAYOUT_ENABLED : 0); requestLayout(); } } @@ -3592,7 +3626,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mFocusPosition = loadingState.index; mFocusPositionOffset = 0; mChildrenStates.loadFromBundle(loadingState.childStates); - mForceFullLayout = true; + mFlag |= PF_FORCE_FULL_LAYOUT; requestLayout(); if (DEBUG) Log.v(getTag(), "onRestoreInstanceState mFocusPosition " + mFocusPosition); } @@ -3699,9 +3733,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { if (newSelected != null) { if (preventScroll) { if (hasFocus()) { - mInSelection = true; + mFlag |= PF_IN_SELECTION; newSelected.requestFocus(); - mInSelection = false; + mFlag &= ~PF_IN_SELECTION; } mFocusPosition = focusPosition; mSubFocusPosition = 0; @@ -3717,11 +3751,11 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { AccessibilityNodeInfoCompat info) { saveContext(recycler, state); int count = state.getItemCount(); - if (mScrollEnabled && count > 1 && !isItemFullyVisible(0)) { + if ((mFlag & PF_SCROLL_ENABLED) != 0 && count > 1 && !isItemFullyVisible(0)) { info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); info.setScrollable(true); } - if (mScrollEnabled && count > 1 && !isItemFullyVisible(count - 1)) { + if ((mFlag & PF_SCROLL_ENABLED) != 0 && count > 1 && !isItemFullyVisible(count - 1)) { info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); info.setScrollable(true); } diff --git a/android/support/v17/leanback/widget/GuidedActionAdapter.java b/android/support/v17/leanback/widget/GuidedActionAdapter.java index 5b755f5e..51b29e21 100644 --- a/android/support/v17/leanback/widget/GuidedActionAdapter.java +++ b/android/support/v17/leanback/widget/GuidedActionAdapter.java @@ -15,7 +15,9 @@ package android.support.v17.leanback.widget; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; +import android.support.annotation.Nullable; import android.support.annotation.RestrictTo; +import android.support.v7.util.DiffUtil; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.util.Log; @@ -103,6 +105,7 @@ public class GuidedActionAdapter extends RecyclerView.Adapter { private ClickListener mClickListener; final GuidedActionsStylist mStylist; GuidedActionAdapterGroup mGroup; + DiffCallback<GuidedAction> mDiffCallback; private final View.OnClickListener mOnClickListener = new View.OnClickListener() { @Override @@ -145,20 +148,78 @@ public class GuidedActionAdapter extends RecyclerView.Adapter { mActionOnFocusListener = new ActionOnFocusListener(focusListener); mActionEditListener = new ActionEditListener(); mIsSubAdapter = isSubAdapter; + if (!isSubAdapter) { + mDiffCallback = GuidedActionDiffCallback.getInstance(); + } + } + + /** + * Change DiffCallback used in {@link #setActions(List)}. Set to null for firing a + * general {@link #notifyDataSetChanged()}. + * + * @param diffCallback + */ + public void setDiffCallback(DiffCallback<GuidedAction> diffCallback) { + mDiffCallback = diffCallback; } /** - * Sets the list of actions managed by this adapter. + * Sets the list of actions managed by this adapter. Use {@link #setDiffCallback(DiffCallback)} + * to change DiffCallback. * @param actions The list of actions to be managed. */ - public void setActions(List<GuidedAction> actions) { + public void setActions(final List<GuidedAction> actions) { if (!mIsSubAdapter) { mStylist.collapseAction(false); } mActionOnFocusListener.unFocus(); - mActions.clear(); - mActions.addAll(actions); - notifyDataSetChanged(); + if (mDiffCallback != null) { + // temporary variable used for DiffCallback + final List<GuidedAction> oldActions = new ArrayList(); + oldActions.addAll(mActions); + + // update items. + mActions.clear(); + mActions.addAll(actions); + + DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return oldActions.size(); + } + + @Override + public int getNewListSize() { + return mActions.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return mDiffCallback.areItemsTheSame(oldActions.get(oldItemPosition), + mActions.get(newItemPosition)); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return mDiffCallback.areContentsTheSame(oldActions.get(oldItemPosition), + mActions.get(newItemPosition)); + } + + @Nullable + @Override + public Object getChangePayload(int oldItemPosition, int newItemPosition) { + return mDiffCallback.getChangePayload(oldActions.get(oldItemPosition), + mActions.get(newItemPosition)); + } + }); + + // dispatch diff result + diffResult.dispatchUpdatesTo(this); + } else { + mActions.clear(); + mActions.addAll(actions); + notifyDataSetChanged(); + } } /** diff --git a/android/support/v17/leanback/widget/GuidedActionDiffCallback.java b/android/support/v17/leanback/widget/GuidedActionDiffCallback.java new file mode 100644 index 00000000..d4d4d77a --- /dev/null +++ b/android/support/v17/leanback/widget/GuidedActionDiffCallback.java @@ -0,0 +1,65 @@ +/* + * Copyright 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 android.support.v17.leanback.widget; + +import android.support.annotation.NonNull; +import android.text.TextUtils; + +/** + * DiffCallback used for GuidedActions, see {@link + * android.support.v17.leanback.app.GuidedStepSupportFragment#setActionsDiffCallback(DiffCallback)}. + */ +public class GuidedActionDiffCallback extends DiffCallback<GuidedAction> { + + static final GuidedActionDiffCallback sInstance = new GuidedActionDiffCallback(); + + /** + * Returns the singleton GuidedActionDiffCallback. + * @return The singleton GuidedActionDiffCallback. + */ + public static final GuidedActionDiffCallback getInstance() { + return sInstance; + } + + @Override + public boolean areItemsTheSame(@NonNull GuidedAction oldItem, @NonNull GuidedAction newItem) { + if (oldItem == null) { + return newItem == null; + } else if (newItem == null) { + return false; + } + return oldItem.getId() == newItem.getId(); + } + + @Override + public boolean areContentsTheSame(@NonNull GuidedAction oldItem, + @NonNull GuidedAction newItem) { + if (oldItem == null) { + return newItem == null; + } else if (newItem == null) { + return false; + } + return oldItem.getCheckSetId() == newItem.getCheckSetId() + && oldItem.mActionFlags == newItem.mActionFlags + && TextUtils.equals(oldItem.getTitle(), newItem.getTitle()) + && TextUtils.equals(oldItem.getDescription(), newItem.getDescription()) + && oldItem.getInputType() == newItem.getInputType() + && TextUtils.equals(oldItem.getEditTitle(), newItem.getEditTitle()) + && TextUtils.equals(oldItem.getEditDescription(), newItem.getEditDescription()) + && oldItem.getEditInputType() == newItem.getEditInputType() + && oldItem.getDescriptionEditInputType() == newItem.getDescriptionEditInputType(); + } +} diff --git a/android/support/v17/leanback/widget/ObjectAdapter.java b/android/support/v17/leanback/widget/ObjectAdapter.java index 535f81b4..d411f9e7 100644 --- a/android/support/v17/leanback/widget/ObjectAdapter.java +++ b/android/support/v17/leanback/widget/ObjectAdapter.java @@ -13,7 +13,10 @@ */ package android.support.v17.leanback.widget; +import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; + import android.database.Observable; +import android.support.annotation.RestrictTo; /** * Base class adapter to be used in leanback activities. Provides access to a data model and is @@ -132,6 +135,10 @@ public abstract class ObjectAdapter { mObservers.get(i).onItemMoved(positionStart, toPosition); } } + + boolean hasObserver() { + return mObservers.size() > 0; + } } private final DataObservable mObservable = new DataObservable(); @@ -207,6 +214,14 @@ public abstract class ObjectAdapter { } /** + * @hide + */ + @RestrictTo(LIBRARY_GROUP) + public final boolean hasObserver() { + return mObservable.hasObserver(); + } + + /** * Unregisters all DataObservers for this ObjectAdapter. */ public final void unregisterAllObservers() { diff --git a/android/support/v4/app/FragmentActivity.java b/android/support/v4/app/FragmentActivity.java index 614ff351..78161a87 100644 --- a/android/support/v4/app/FragmentActivity.java +++ b/android/support/v4/app/FragmentActivity.java @@ -536,7 +536,7 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - markState(getSupportFragmentManager(), Lifecycle.State.CREATED); + markFragmentsCreated(); Parcelable p = mFragments.saveAllState(); if (p != null) { outState.putParcelable(FRAGMENTS_TAG, p); @@ -591,7 +591,7 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements super.onStop(); mStopped = true; - markState(getSupportFragmentManager(), Lifecycle.State.CREATED); + markFragmentsCreated(); mHandler.sendEmptyMessage(MSG_REALLY_STOPPED); mFragments.dispatchStop(); @@ -970,18 +970,30 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements } } - private static void markState(FragmentManager manager, Lifecycle.State state) { + private void markFragmentsCreated() { + boolean reiterate; + do { + reiterate = markState(getSupportFragmentManager(), Lifecycle.State.CREATED); + } while (reiterate); + } + + private static boolean markState(FragmentManager manager, Lifecycle.State state) { + boolean hadNotMarked = false; Collection<Fragment> fragments = manager.getFragments(); for (Fragment fragment : fragments) { if (fragment == null) { continue; } - fragment.mLifecycleRegistry.markState(state); + if (fragment.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { + fragment.mLifecycleRegistry.markState(state); + hadNotMarked = true; + } FragmentManager childFragmentManager = fragment.peekChildFragmentManager(); if (childFragmentManager != null) { - markState(childFragmentManager, state); + hadNotMarked |= markState(childFragmentManager, state); } } + return hadNotMarked; } } diff --git a/android/support/v4/graphics/TypefaceCompat.java b/android/support/v4/graphics/TypefaceCompat.java index 3c55df62..734f1837 100644 --- a/android/support/v4/graphics/TypefaceCompat.java +++ b/android/support/v4/graphics/TypefaceCompat.java @@ -35,7 +35,6 @@ import android.support.v4.content.res.ResourcesCompat; import android.support.v4.provider.FontsContractCompat; import android.support.v4.provider.FontsContractCompat.FontInfo; import android.support.v4.util.LruCache; - /** * Helper for accessing features in {@link Typeface}. * @hide @@ -46,7 +45,9 @@ public class TypefaceCompat { private static final TypefaceCompatImpl sTypefaceCompatImpl; static { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + sTypefaceCompatImpl = new TypefaceCompatApi28Impl(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { sTypefaceCompatImpl = new TypefaceCompatApi26Impl(); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && TypefaceCompatApi24Impl.isUsable()) { diff --git a/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/android/support/v4/graphics/TypefaceCompatApi26Impl.java index 1b55a2e0..00e31a1a 100644 --- a/android/support/v4/graphics/TypefaceCompatApi26Impl.java +++ b/android/support/v4/graphics/TypefaceCompatApi26Impl.java @@ -60,76 +60,69 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl { "createFromFamiliesWithDefault"; private static final String FREEZE_METHOD = "freeze"; private static final String ABORT_CREATION_METHOD = "abortCreation"; - private static final Class sFontFamily; - private static final Constructor sFontFamilyCtor; - private static final Method sAddFontFromAssetManager; - private static final Method sAddFontFromBuffer; - private static final Method sFreeze; - private static final Method sAbortCreation; - private static final Method sCreateFromFamiliesWithDefault; private static final int RESOLVE_BY_FONT_TABLE = -1; - static { - Class fontFamilyClass; + protected final Class mFontFamily; + protected final Constructor mFontFamilyCtor; + protected final Method mAddFontFromAssetManager; + protected final Method mAddFontFromBuffer; + protected final Method mFreeze; + protected final Method mAbortCreation; + protected final Method mCreateFromFamiliesWithDefault; + + public TypefaceCompatApi26Impl() { + Class fontFamily; Constructor fontFamilyCtor; - Method addFontMethod; - Method addFromBufferMethod; - Method freezeMethod; - Method abortCreationMethod; - Method createFromFamiliesWithDefaultMethod; + Method addFontFromAssetManager; + Method addFontFromBuffer; + Method freeze; + Method abortCreation; + Method createFromFamiliesWithDefault; try { - fontFamilyClass = Class.forName(FONT_FAMILY_CLASS); - fontFamilyCtor = fontFamilyClass.getConstructor(); - addFontMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD, - AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE, - Integer.TYPE, Integer.TYPE, FontVariationAxis[].class); - addFromBufferMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_BUFFER_METHOD, - ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE, - Integer.TYPE); - freezeMethod = fontFamilyClass.getMethod(FREEZE_METHOD); - abortCreationMethod = fontFamilyClass.getMethod(ABORT_CREATION_METHOD); - Object familyArray = Array.newInstance(fontFamilyClass, 1); - createFromFamiliesWithDefaultMethod = - Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD, - familyArray.getClass(), Integer.TYPE, Integer.TYPE); - createFromFamiliesWithDefaultMethod.setAccessible(true); + fontFamily = obtainFontFamily(); + fontFamilyCtor = obtainFontFamilyCtor(fontFamily); + addFontFromAssetManager = obtainAddFontFromAssetManagerMethod(fontFamily); + addFontFromBuffer = obtainAddFontFromBufferMethod(fontFamily); + freeze = obtainFreezeMethod(fontFamily); + abortCreation = obtainAbortCreationMethod(fontFamily); + createFromFamiliesWithDefault = obtainCreateFromFamiliesWithDefaultMethod(fontFamily); } catch (ClassNotFoundException | NoSuchMethodException e) { Log.e(TAG, "Unable to collect necessary methods for class " + e.getClass().getName(), e); - fontFamilyClass = null; + fontFamily = null; fontFamilyCtor = null; - addFontMethod = null; - addFromBufferMethod = null; - freezeMethod = null; - abortCreationMethod = null; - createFromFamiliesWithDefaultMethod = null; + addFontFromAssetManager = null; + addFontFromBuffer = null; + freeze = null; + abortCreation = null; + createFromFamiliesWithDefault = null; } - sFontFamilyCtor = fontFamilyCtor; - sFontFamily = fontFamilyClass; - sAddFontFromAssetManager = addFontMethod; - sAddFontFromBuffer = addFromBufferMethod; - sFreeze = freezeMethod; - sAbortCreation = abortCreationMethod; - sCreateFromFamiliesWithDefault = createFromFamiliesWithDefaultMethod; + mFontFamily = fontFamily; + mFontFamilyCtor = fontFamilyCtor; + mAddFontFromAssetManager = addFontFromAssetManager; + mAddFontFromBuffer = addFontFromBuffer; + mFreeze = freeze; + mAbortCreation = abortCreation; + mCreateFromFamiliesWithDefault = createFromFamiliesWithDefault; } /** - * Returns true if API26 implementation is usable. + * Returns true if all the necessary methods were found. */ - private static boolean isFontFamilyPrivateAPIAvailable() { - if (sAddFontFromAssetManager == null) { + private boolean isFontFamilyPrivateAPIAvailable() { + if (mAddFontFromAssetManager == null) { Log.w(TAG, "Unable to collect necessary private methods. " + "Fallback to legacy implementation."); } - return sAddFontFromAssetManager != null; + return mAddFontFromAssetManager != null; } /** * Create a new FontFamily instance */ - private static Object newFamily() { + private Object newFamily() { try { - return sFontFamilyCtor.newInstance(); + return mFontFamilyCtor.newInstance(); } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { throw new RuntimeException(e); } @@ -139,10 +132,10 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl { * Call FontFamily#addFontFromAssetManager(AssetManager mgr, String path, int cookie, * boolean isAsset, int ttcIndex, int weight, int isItalic, FontVariationAxis[] axes) */ - private static boolean addFontFromAssetManager(Context context, Object family, String fileName, + private boolean addFontFromAssetManager(Context context, Object family, String fileName, int ttcIndex, int weight, int style) { try { - final Boolean result = (Boolean) sAddFontFromAssetManager.invoke(family, + final Boolean result = (Boolean) mAddFontFromAssetManager.invoke(family, context.getAssets(), fileName, 0 /* cookie */, false /* isAsset */, ttcIndex, weight, style, null /* axes */); return result.booleanValue(); @@ -155,10 +148,10 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl { * Call FontFamily#addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes, * int weight, int italic) */ - private static boolean addFontFromBuffer(Object family, ByteBuffer buffer, + private boolean addFontFromBuffer(Object family, ByteBuffer buffer, int ttcIndex, int weight, int style) { try { - final Boolean result = (Boolean) sAddFontFromBuffer.invoke(family, + final Boolean result = (Boolean) mAddFontFromBuffer.invoke(family, buffer, ttcIndex, null /* axes */, weight, style); return result.booleanValue(); } catch (IllegalAccessException | InvocationTargetException e) { @@ -167,14 +160,14 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl { } /** - * Call static method Typeface#createFromFamiliesWithDefault( + * Call method Typeface#createFromFamiliesWithDefault( * FontFamily[] families, int weight, int italic) */ - private static Typeface createFromFamiliesWithDefault(Object family) { + protected Typeface createFromFamiliesWithDefault(Object family) { try { - Object familyArray = Array.newInstance(sFontFamily, 1); + Object familyArray = Array.newInstance(mFontFamily, 1); Array.set(familyArray, 0, family); - return (Typeface) sCreateFromFamiliesWithDefault.invoke(null /* static method */, + return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */, familyArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); @@ -184,9 +177,9 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl { /** * Call FontFamily#freeze() */ - private static boolean freeze(Object family) { + private boolean freeze(Object family) { try { - Boolean result = (Boolean) sFreeze.invoke(family); + Boolean result = (Boolean) mFreeze.invoke(family); return result.booleanValue(); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); @@ -196,9 +189,9 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl { /** * Call FontFamily#abortCreation() */ - private static boolean abortCreation(Object family) { + private boolean abortCreation(Object family) { try { - Boolean result = (Boolean) sAbortCreation.invoke(family); + Boolean result = (Boolean) mAbortCreation.invoke(family); return result.booleanValue(); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); @@ -299,4 +292,47 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl { } return createFromFamiliesWithDefault(fontFamily); } + + // The following getters retrieve by reflection the Typeface methods, belonging to the + // framework code, which will be invoked. Since the definitions of these methods can change + // across different API versions, inheriting classes should override these getters in order to + // reflect the method definitions in the API versions they represent. + //=========================================================================================== + protected Class obtainFontFamily() throws ClassNotFoundException { + return Class.forName(FONT_FAMILY_CLASS); + } + + protected Constructor obtainFontFamilyCtor(Class fontFamily) throws NoSuchMethodException { + return fontFamily.getConstructor(); + } + + protected Method obtainAddFontFromAssetManagerMethod(Class fontFamily) + throws NoSuchMethodException { + return fontFamily.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD, + AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE, + Integer.TYPE, Integer.TYPE, FontVariationAxis[].class); + } + + protected Method obtainAddFontFromBufferMethod(Class fontFamily) throws NoSuchMethodException { + return fontFamily.getMethod(ADD_FONT_FROM_BUFFER_METHOD, + ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE, + Integer.TYPE); + } + + protected Method obtainFreezeMethod(Class fontFamily) throws NoSuchMethodException { + return fontFamily.getMethod(FREEZE_METHOD); + } + + protected Method obtainAbortCreationMethod(Class fontFamily) throws NoSuchMethodException { + return fontFamily.getMethod(ABORT_CREATION_METHOD); + } + + protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily) + throws NoSuchMethodException { + Object familyArray = Array.newInstance(fontFamily, 1); + Method m = Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD, + familyArray.getClass(), Integer.TYPE, Integer.TYPE); + m.setAccessible(true); + return m; + } } diff --git a/android/support/v4/graphics/TypefaceCompatApi28Impl.java b/android/support/v4/graphics/TypefaceCompatApi28Impl.java new file mode 100644 index 00000000..baa2ce67 --- /dev/null +++ b/android/support/v4/graphics/TypefaceCompatApi28Impl.java @@ -0,0 +1,68 @@ +/* + * Copyright 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 android.support.v4.graphics; + +import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +import android.graphics.Typeface; +import android.support.annotation.RequiresApi; +import android.support.annotation.RestrictTo; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Implementation of the Typeface compat methods for API 28 and above. + * @hide + */ +@RestrictTo(LIBRARY_GROUP) +@RequiresApi(28) +public class TypefaceCompatApi28Impl extends TypefaceCompatApi26Impl { + private static final String TAG = "TypefaceCompatApi28Impl"; + + private static final String CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD = + "createFromFamiliesWithDefault"; + private static final int RESOLVE_BY_FONT_TABLE = -1; + private static final String DEFAULT_FAMILY = "sans-serif"; + + /** + * Call method Typeface#createFromFamiliesWithDefault( + * FontFamily[] families, String fallbackName, int weight, int italic) + */ + @Override + protected Typeface createFromFamiliesWithDefault(Object family) { + try { + Object familyArray = Array.newInstance(mFontFamily, 1); + Array.set(familyArray, 0, family); + return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */, + familyArray, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + @Override + protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily) + throws NoSuchMethodException { + Object familyArray = Array.newInstance(fontFamily, 1); + Method m = Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD, + familyArray.getClass(), String.class, Integer.TYPE, Integer.TYPE); + m.setAccessible(true); + return m; + } +} diff --git a/android/support/v4/media/MediaBrowserCompat.java b/android/support/v4/media/MediaBrowserCompat.java index 85f5a512..7adf7d78 100644 --- a/android/support/v4/media/MediaBrowserCompat.java +++ b/android/support/v4/media/MediaBrowserCompat.java @@ -676,17 +676,15 @@ public final class MediaBrowserCompat { WeakReference<Subscription> mSubscriptionRef; public SubscriptionCallback() { + mToken = new Binder(); if (Build.VERSION.SDK_INT >= 26) { mSubscriptionCallbackObj = MediaBrowserCompatApi26.createSubscriptionCallback(new StubApi26()); - mToken = null; } else if (Build.VERSION.SDK_INT >= 21) { mSubscriptionCallbackObj = MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21()); - mToken = new Binder(); } else { mSubscriptionCallbackObj = null; - mToken = new Binder(); } } @@ -1958,22 +1956,30 @@ public final class MediaBrowserCompat { @Override public void subscribe(@NonNull String parentId, @Nullable Bundle options, @NonNull SubscriptionCallback callback) { - if (options == null) { - MediaBrowserCompatApi21.subscribe( - mBrowserObj, parentId, callback.mSubscriptionCallbackObj); + if (mServiceBinderWrapper == null) { + if (options == null) { + MediaBrowserCompatApi21.subscribe( + mBrowserObj, parentId, callback.mSubscriptionCallbackObj); + } else { + MediaBrowserCompatApi26.subscribe( + mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj); + } } else { - MediaBrowserCompatApi26.subscribe( - mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj); + super.subscribe(parentId, options, callback); } } @Override public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) { - if (callback == null) { - MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId); + if (mServiceBinderWrapper == null) { + if (callback == null) { + MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId); + } else { + MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId, + callback.mSubscriptionCallbackObj); + } } else { - MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId, - callback.mSubscriptionCallbackObj); + super.unsubscribe(parentId, callback); } } } diff --git a/android/support/v4/media/MediaBrowserServiceCompat.java b/android/support/v4/media/MediaBrowserServiceCompat.java index 53b111ab..debc66e8 100644 --- a/android/support/v4/media/MediaBrowserServiceCompat.java +++ b/android/support/v4/media/MediaBrowserServiceCompat.java @@ -422,11 +422,15 @@ public abstract class MediaBrowserServiceCompat extends Service { @Override public void notifyChildrenChanged(final String parentId, final Bundle options) { - if (options == null) { - MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId); + if (mMessenger == null) { + if (options == null) { + MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId); + } else { + MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId, + options); + } } else { - MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId, - options); + super.notifyChildrenChanged(parentId, options); } } diff --git a/android/support/v4/media/MediaMetadataCompat.java b/android/support/v4/media/MediaMetadataCompat.java index 3ddf255c..00f16cb3 100644 --- a/android/support/v4/media/MediaMetadataCompat.java +++ b/android/support/v4/media/MediaMetadataCompat.java @@ -365,10 +365,12 @@ public final class MediaMetadataCompat implements Parcelable { MediaMetadataCompat(Bundle bundle) { mBundle = new Bundle(bundle); + mBundle.setClassLoader(MediaMetadataCompat.class.getClassLoader()); } MediaMetadataCompat(Parcel in) { mBundle = in.readBundle(); + mBundle.setClassLoader(MediaMetadataCompat.class.getClassLoader()); } /** diff --git a/android/support/v4/view/ViewCompat.java b/android/support/v4/view/ViewCompat.java index 34a198a1..204a1218 100644 --- a/android/support/v4/view/ViewCompat.java +++ b/android/support/v4/view/ViewCompat.java @@ -1356,7 +1356,7 @@ public class ViewCompat { // after applying the tint Drawable background = view.getBackground(); boolean hasTint = (view.getBackgroundTintList() != null) - && (view.getBackgroundTintMode() != null); + || (view.getBackgroundTintMode() != null); if ((background != null) && hasTint) { if (background.isStateful()) { background.setState(view.getDrawableState()); @@ -1375,7 +1375,7 @@ public class ViewCompat { // after applying the tint Drawable background = view.getBackground(); boolean hasTint = (view.getBackgroundTintList() != null) - && (view.getBackgroundTintMode() != null); + || (view.getBackgroundTintMode() != null); if ((background != null) && hasTint) { if (background.isStateful()) { background.setState(view.getDrawableState()); diff --git a/android/support/v7/app/AppCompatDelegateImplV9.java b/android/support/v7/app/AppCompatDelegateImplV9.java index 056e33e3..5b53401c 100644 --- a/android/support/v7/app/AppCompatDelegateImplV9.java +++ b/android/support/v7/app/AppCompatDelegateImplV9.java @@ -1001,7 +1001,26 @@ class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase public View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (mAppCompatViewInflater == null) { - mAppCompatViewInflater = new AppCompatViewInflater(); + TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); + String viewInflaterClassName = + a.getString(R.styleable.AppCompatTheme_viewInflaterClass); + if ((viewInflaterClassName == null) + || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) { + // Either default class name or set explicitly to null. In both cases + // create the base inflater (no reflection) + mAppCompatViewInflater = new AppCompatViewInflater(); + } else { + try { + Class viewInflaterClass = Class.forName(viewInflaterClassName); + mAppCompatViewInflater = + (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor() + .newInstance(); + } catch (Throwable t) { + Log.i(TAG, "Failed to instantiate custom view inflater " + + viewInflaterClassName + ". Falling back to default.", t); + mAppCompatViewInflater = new AppCompatViewInflater(); + } + } } boolean inheritContext = false; diff --git a/android/support/v7/app/AppCompatViewInflater.java b/android/support/v7/app/AppCompatViewInflater.java index 54d01bce..87a1a3c7 100644 --- a/android/support/v7/app/AppCompatViewInflater.java +++ b/android/support/v7/app/AppCompatViewInflater.java @@ -51,14 +51,12 @@ import java.lang.reflect.Method; import java.util.Map; /** - * This class is responsible for manually inflating our tinted widgets which are used on devices - * running {@link android.os.Build.VERSION_CODES#KITKAT KITKAT} or below. As such, this class - * should only be used when running on those devices. + * This class is responsible for manually inflating our tinted widgets. * <p>This class two main responsibilities: the first is to 'inject' our tinted views in place of * the framework versions in layout inflation; the second is backport the {@code android:theme} * functionality for any inflated widgets. This include theme inheritance from its parent. */ -class AppCompatViewInflater { +public class AppCompatViewInflater { private static final Class<?>[] sConstructorSignature = new Class[]{ Context.class, AttributeSet.class}; @@ -77,7 +75,7 @@ class AppCompatViewInflater { private final Object[] mConstructorArgs = new Object[2]; - public final View createView(View parent, final String name, @NonNull Context context, + final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) { final Context originalContext = context; @@ -100,44 +98,63 @@ class AppCompatViewInflater { // We need to 'inject' our tint aware Views in place of the standard framework versions switch (name) { case "TextView": - view = new AppCompatTextView(context, attrs); + view = createTextView(context, attrs); + verifyNotNull(view, name); break; case "ImageView": - view = new AppCompatImageView(context, attrs); + view = createImageView(context, attrs); + verifyNotNull(view, name); break; case "Button": - view = new AppCompatButton(context, attrs); + view = createButton(context, attrs); + verifyNotNull(view, name); break; case "EditText": - view = new AppCompatEditText(context, attrs); + view = createEditText(context, attrs); + verifyNotNull(view, name); break; case "Spinner": - view = new AppCompatSpinner(context, attrs); + view = createSpinner(context, attrs); + verifyNotNull(view, name); break; case "ImageButton": - view = new AppCompatImageButton(context, attrs); + view = createImageButton(context, attrs); + verifyNotNull(view, name); break; case "CheckBox": - view = new AppCompatCheckBox(context, attrs); + view = createCheckBox(context, attrs); + verifyNotNull(view, name); break; case "RadioButton": - view = new AppCompatRadioButton(context, attrs); + view = createRadioButton(context, attrs); + verifyNotNull(view, name); break; case "CheckedTextView": - view = new AppCompatCheckedTextView(context, attrs); + view = createCheckedTextView(context, attrs); + verifyNotNull(view, name); break; case "AutoCompleteTextView": - view = new AppCompatAutoCompleteTextView(context, attrs); + view = createAutoCompleteTextView(context, attrs); + verifyNotNull(view, name); break; case "MultiAutoCompleteTextView": - view = new AppCompatMultiAutoCompleteTextView(context, attrs); + view = createMultiAutoCompleteTextView(context, attrs); + verifyNotNull(view, name); break; case "RatingBar": - view = new AppCompatRatingBar(context, attrs); + view = createRatingBar(context, attrs); + verifyNotNull(view, name); break; case "SeekBar": - view = new AppCompatSeekBar(context, attrs); + view = createSeekBar(context, attrs); + verifyNotNull(view, name); break; + default: + // The fallback that allows extending class to take over view inflation + // for other tags. Note that we don't check that the result is not-null. + // That allows the custom inflater path to fall back on the default one + // later in this method. + view = createView(context, name, attrs); } if (view == null && originalContext != context) { @@ -154,6 +171,85 @@ class AppCompatViewInflater { return view; } + @NonNull + protected AppCompatTextView createTextView(Context context, AttributeSet attrs) { + return new AppCompatTextView(context, attrs); + } + + @NonNull + protected AppCompatImageView createImageView(Context context, AttributeSet attrs) { + return new AppCompatImageView(context, attrs); + } + + @NonNull + protected AppCompatButton createButton(Context context, AttributeSet attrs) { + return new AppCompatButton(context, attrs); + } + + @NonNull + protected AppCompatEditText createEditText(Context context, AttributeSet attrs) { + return new AppCompatEditText(context, attrs); + } + + @NonNull + protected AppCompatSpinner createSpinner(Context context, AttributeSet attrs) { + return new AppCompatSpinner(context, attrs); + } + + @NonNull + protected AppCompatImageButton createImageButton(Context context, AttributeSet attrs) { + return new AppCompatImageButton(context, attrs); + } + + @NonNull + protected AppCompatCheckBox createCheckBox(Context context, AttributeSet attrs) { + return new AppCompatCheckBox(context, attrs); + } + + @NonNull + protected AppCompatRadioButton createRadioButton(Context context, AttributeSet attrs) { + return new AppCompatRadioButton(context, attrs); + } + + @NonNull + protected AppCompatCheckedTextView createCheckedTextView(Context context, AttributeSet attrs) { + return new AppCompatCheckedTextView(context, attrs); + } + + @NonNull + protected AppCompatAutoCompleteTextView createAutoCompleteTextView(Context context, + AttributeSet attrs) { + return new AppCompatAutoCompleteTextView(context, attrs); + } + + @NonNull + protected AppCompatMultiAutoCompleteTextView createMultiAutoCompleteTextView(Context context, + AttributeSet attrs) { + return new AppCompatMultiAutoCompleteTextView(context, attrs); + } + + @NonNull + protected AppCompatRatingBar createRatingBar(Context context, AttributeSet attrs) { + return new AppCompatRatingBar(context, attrs); + } + + @NonNull + protected AppCompatSeekBar createSeekBar(Context context, AttributeSet attrs) { + return new AppCompatSeekBar(context, attrs); + } + + private void verifyNotNull(View view, String name) { + if (view == null) { + throw new IllegalStateException(this.getClass().getName() + + " asked to inflate view for <" + name + ">, but returned null"); + } + } + + @Nullable + protected View createView(Context context, String name, AttributeSet attrs) { + return null; + } + private View createViewFromTag(Context context, String name, AttributeSet attrs) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); @@ -165,14 +261,14 @@ class AppCompatViewInflater { if (-1 == name.indexOf('.')) { for (int i = 0; i < sClassPrefixList.length; i++) { - final View view = createView(context, name, sClassPrefixList[i]); + final View view = createViewByPrefix(context, name, sClassPrefixList[i]); if (view != null) { return view; } } return null; } else { - return createView(context, name, null); + return createViewByPrefix(context, name, null); } } catch (Exception e) { // We do not want to catch these, lets return null and let the actual LayoutInflater @@ -209,7 +305,7 @@ class AppCompatViewInflater { a.recycle(); } - private View createView(Context context, String name, String prefix) + private View createViewByPrefix(Context context, String name, String prefix) throws ClassNotFoundException, InflateException { Constructor<? extends View> constructor = sConstructorMap.get(name); diff --git a/android/support/v7/util/SortedList.java b/android/support/v7/util/SortedList.java index c62d0ce8..af000a1e 100644 --- a/android/support/v7/util/SortedList.java +++ b/android/support/v7/util/SortedList.java @@ -16,6 +16,8 @@ package android.support.v7.util; +import android.support.annotation.Nullable; + import java.lang.reflect.Array; import java.util.Arrays; import java.util.Collection; @@ -315,7 +317,8 @@ public class SortedList<T> { newDataStart++; mOldDataStart++; if (!mCallback.areContentsTheSame(oldItem, newItem)) { - mCallback.onChanged(mMergedSize - 1, 1); + mCallback.onChanged(mMergedSize - 1, 1, + mCallback.getChangePayload(oldItem, newItem)); } } else { // Old item is lower than or equal to (but not the same as the new). Output it. @@ -401,7 +404,7 @@ public class SortedList<T> { return index; } else { mData[index] = item; - mCallback.onChanged(index, 1); + mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item)); return index; } } @@ -488,13 +491,13 @@ public class SortedList<T> { if (cmp == 0) { mData[index] = item; if (contentsChanged) { - mCallback.onChanged(index, 1); + mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item)); } return; } } if (contentsChanged) { - mCallback.onChanged(index, 1); + mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item)); } // TODO this done in 1 pass to avoid shifting twice. removeItemAtIndex(index, false); @@ -741,6 +744,28 @@ public class SortedList<T> { * @return True if the two items represent the same object or false if they are different. */ abstract public boolean areItemsTheSame(T2 item1, T2 item2); + + /** + * When {@link #areItemsTheSame(T2, T2)} returns {@code true} for two items and + * {@link #areContentsTheSame(T2, T2)} returns false for them, {@link Callback} calls this + * method to get a payload about the change. + * <p> + * For example, if you are using {@link Callback} with + * {@link android.support.v7.widget.RecyclerView}, you can return the particular field that + * changed in the item and your + * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that + * information to run the correct animation. + * <p> + * Default implementation returns {@code null}. + * + * @param item1 The first item to check. + * @param item2 The second item to check. + * @return A payload object that represents the changes between the two items. + */ + @Nullable + public Object getChangePayload(T2 item1, T2 item2) { + return null; + } } /** @@ -801,6 +826,11 @@ public class SortedList<T> { } @Override + public void onChanged(int position, int count, Object payload) { + mBatchingListUpdateCallback.onChanged(position, count, payload); + } + + @Override public boolean areContentsTheSame(T2 oldItem, T2 newItem) { return mWrappedCallback.areContentsTheSame(oldItem, newItem); } @@ -810,6 +840,12 @@ public class SortedList<T> { return mWrappedCallback.areItemsTheSame(item1, item2); } + @Nullable + @Override + public Object getChangePayload(T2 item1, T2 item2) { + return mWrappedCallback.getChangePayload(item1, item2); + } + /** * This method dispatches any pending event notifications to the wrapped Callback. * You <b>must</b> always call this method after you are done with editing the SortedList. diff --git a/android/support/v7/util/SortedListBatchedCallbackTest.java b/android/support/v7/util/SortedListBatchedCallbackTest.java index 3ace2178..bc50415d 100644 --- a/android/support/v7/util/SortedListBatchedCallbackTest.java +++ b/android/support/v7/util/SortedListBatchedCallbackTest.java @@ -50,6 +50,16 @@ public class SortedListBatchedCallbackTest { } @Test + public void onChangeWithPayload() { + final Object payload = 7; + mBatchedCallback.onChanged(1, 2, payload); + verifyZeroInteractions(mMockCallback); + mBatchedCallback.dispatchLastEvent(); + verify(mMockCallback).onChanged(1, 2, payload); + verifyNoMoreInteractions(mMockCallback); + } + + @Test public void onRemoved() { mBatchedCallback.onRemoved(2, 3); verifyZeroInteractions(mMockCallback); diff --git a/android/support/v7/util/SortedListTest.java b/android/support/v7/util/SortedListTest.java index da3c9572..47d2ac0f 100644 --- a/android/support/v7/util/SortedListTest.java +++ b/android/support/v7/util/SortedListTest.java @@ -16,6 +16,7 @@ package android.support.v7.util; +import android.support.annotation.Nullable; import android.support.test.filters.SmallTest; import junit.framework.TestCase; @@ -41,6 +42,8 @@ public class SortedListTest extends TestCase { List<Pair> mRemovals = new ArrayList<Pair>(); List<Pair> mMoves = new ArrayList<Pair>(); List<Pair> mUpdates = new ArrayList<Pair>(); + private boolean mPayloadChanges = false; + List<PayloadChange> mPayloadUpdates = new ArrayList<>(); private SortedList.Callback<Item> mCallback; InsertedCallback<Item> mInsertedCallback; ChangedCallback<Item> mChangedCallback; @@ -97,6 +100,15 @@ public class SortedListTest extends TestCase { } @Override + public void onChanged(int position, int count, Object payload) { + if (mPayloadChanges) { + mPayloadUpdates.add(new PayloadChange(position, count, payload)); + } else { + onChanged(position, count); + } + } + + @Override public boolean areContentsTheSame(Item oldItem, Item newItem) { return oldItem.cmpField == newItem.cmpField && oldItem.data == newItem.data; } @@ -105,6 +117,15 @@ public class SortedListTest extends TestCase { public boolean areItemsTheSame(Item item1, Item item2) { return item1.id == item2.id; } + + @Nullable + @Override + public Object getChangePayload(Item item1, Item item2) { + if (mPayloadChanges) { + return item2.data; + } + return null; + } }; mInsertedCallback = null; mChangedCallback = null; @@ -705,6 +726,76 @@ public class SortedListTest extends TestCase { assertTrue(mAdditions.contains(new Pair(0, 6))); } + @Test + public void testAddExistingItemCallsChangeWithPayload() { + mList.addAll( + new Item(1, 10), + new Item(2, 20), + new Item(3, 30) + ); + mPayloadChanges = true; + + // add an item with the same id but a new data field i.e. send an update + final Item twoUpdate = new Item(2, 20); + twoUpdate.data = 1337; + mList.add(twoUpdate); + assertEquals(1, mPayloadUpdates.size()); + final PayloadChange update = mPayloadUpdates.get(0); + assertEquals(1, update.position); + assertEquals(1, update.count); + assertEquals(1337, update.payload); + assertEquals(3, size()); + } + + @Test + public void testUpdateItemCallsChangeWithPayload() { + mList.addAll( + new Item(1, 10), + new Item(2, 20), + new Item(3, 30) + ); + mPayloadChanges = true; + + // add an item with the same id but a new data field i.e. send an update + final Item twoUpdate = new Item(2, 20); + twoUpdate.data = 1337; + mList.updateItemAt(1, twoUpdate); + assertEquals(1, mPayloadUpdates.size()); + final PayloadChange update = mPayloadUpdates.get(0); + assertEquals(1, update.position); + assertEquals(1, update.count); + assertEquals(1337, update.payload); + assertEquals(3, size()); + assertEquals(1337, mList.get(1).data); + } + + @Test + public void testAddMultipleExistingItemCallsChangeWithPayload() { + mList.addAll( + new Item(1, 10), + new Item(2, 20), + new Item(3, 30) + ); + mPayloadChanges = true; + + // add two items with the same ids but a new data fields i.e. send two updates + final Item twoUpdate = new Item(2, 20); + twoUpdate.data = 222; + final Item threeUpdate = new Item(3, 30); + threeUpdate.data = 333; + mList.addAll(twoUpdate, threeUpdate); + assertEquals(2, mPayloadUpdates.size()); + final PayloadChange update1 = mPayloadUpdates.get(0); + assertEquals(1, update1.position); + assertEquals(1, update1.count); + assertEquals(222, update1.payload); + final PayloadChange update2 = mPayloadUpdates.get(1); + assertEquals(2, update2.position); + assertEquals(1, update2.count); + assertEquals(333, update2.payload); + assertEquals(3, size()); + } + private int size() { return mList.size(); } @@ -821,4 +912,37 @@ public class SortedListTest extends TestCase { return result; } } + + private static final class PayloadChange { + public final int position; + public final int count; + public final Object payload; + + PayloadChange(int position, int count, Object payload) { + this.position = position; + this.count = count; + this.payload = payload; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PayloadChange payloadChange = (PayloadChange) o; + + if (position != payloadChange.position) return false; + if (count != payloadChange.count) return false; + return payload != null ? payload.equals(payloadChange.payload) + : payloadChange.payload == null; + } + + @Override + public int hashCode() { + int result = position; + result = 31 * result + count; + result = 31 * result + (payload != null ? payload.hashCode() : 0); + return result; + } + } }
\ No newline at end of file diff --git a/android/support/v7/widget/AppCompatTextHelper.java b/android/support/v7/widget/AppCompatTextHelper.java index fa6196f5..b8ce82a4 100644 --- a/android/support/v7/widget/AppCompatTextHelper.java +++ b/android/support/v7/widget/AppCompatTextHelper.java @@ -213,9 +213,9 @@ class AppCompatTextHelper { if (a.hasValue(R.styleable.TextAppearance_android_fontFamily) || a.hasValue(R.styleable.TextAppearance_fontFamily)) { mFontTypeface = null; - int fontFamilyId = a.hasValue(R.styleable.TextAppearance_android_fontFamily) - ? R.styleable.TextAppearance_android_fontFamily - : R.styleable.TextAppearance_fontFamily; + int fontFamilyId = a.hasValue(R.styleable.TextAppearance_fontFamily) + ? R.styleable.TextAppearance_fontFamily + : R.styleable.TextAppearance_android_fontFamily; if (!context.isRestricted()) { final WeakReference<TextView> textViewWeak = new WeakReference<>(mView); ResourcesCompat.FontCallback replyCallback = new ResourcesCompat.FontCallback() { diff --git a/android/support/v7/widget/TooltipCompatHandler.java b/android/support/v7/widget/TooltipCompatHandler.java index 5ce1f8b3..63a61982 100644 --- a/android/support/v7/widget/TooltipCompatHandler.java +++ b/android/support/v7/widget/TooltipCompatHandler.java @@ -66,6 +66,10 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList private TooltipPopup mPopup; private boolean mFromTouch; + // The handler currently scheduled to show a tooltip, triggered by a hover + // (there can be only one). + private static TooltipCompatHandler sPendingHandler; + // The handler currently showing a tooltip (there can be only one). private static TooltipCompatHandler sActiveHandler; @@ -76,6 +80,15 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList * @param tooltipText the tooltip text */ public static void setTooltipText(View view, CharSequence tooltipText) { + // The code below is not attempting to update the tooltip text + // for a pending or currently active tooltip, because it may lead + // to updating the wrong tooltip in in some rare cases (e.g. when + // action menu item views are recycled). Instead, the tooltip is + // canceled/hidden. This might still be the wrong tooltip, + // but hiding a wrong tooltip is less disruptive UX. + if (sPendingHandler != null && sPendingHandler.mAnchor == view) { + setPendingHandler(null); + } if (TextUtils.isEmpty(tooltipText)) { if (sActiveHandler != null && sActiveHandler.mAnchor == view) { sActiveHandler.hide(); @@ -119,8 +132,7 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList if (mAnchor.isEnabled() && mPopup == null) { mAnchorX = (int) event.getX(); mAnchorY = (int) event.getY(); - mAnchor.removeCallbacks(mShowRunnable); - mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout()); + setPendingHandler(this); } break; case MotionEvent.ACTION_HOVER_EXIT: @@ -145,6 +157,7 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList if (!ViewCompat.isAttachedToWindow(mAnchor)) { return; } + setPendingHandler(null); if (sActiveHandler != null) { sActiveHandler.hide(); } @@ -180,7 +193,27 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList Log.e(TAG, "sActiveHandler.mPopup == null"); } } - mAnchor.removeCallbacks(mShowRunnable); + if (sPendingHandler == this) { + setPendingHandler(null); + } mAnchor.removeCallbacks(mHideRunnable); } + + private static void setPendingHandler(TooltipCompatHandler handler) { + if (sPendingHandler != null) { + sPendingHandler.cancelPendingShow(); + } + sPendingHandler = handler; + if (sPendingHandler != null) { + sPendingHandler.scheduleShow(); + } + } + + private void scheduleShow() { + mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout()); + } + + private void cancelPendingShow() { + mAnchor.removeCallbacks(mShowRunnable); + } } diff --git a/android/support/v7/widget/util/SortedListAdapterCallback.java b/android/support/v7/widget/util/SortedListAdapterCallback.java index 4921541b..a1203a69 100644 --- a/android/support/v7/widget/util/SortedListAdapterCallback.java +++ b/android/support/v7/widget/util/SortedListAdapterCallback.java @@ -56,4 +56,9 @@ public abstract class SortedListAdapterCallback<T2> extends SortedList.Callback< public void onChanged(int position, int count) { mAdapter.notifyItemRangeChanged(position, count); } + + @Override + public void onChanged(int position, int count, Object payload) { + mAdapter.notifyItemRangeChanged(position, count, payload); + } } diff --git a/android/support/wear/ambient/AmbientDelegate.java b/android/support/wear/ambient/AmbientDelegate.java index 49012908..8e96a020 100644 --- a/android/support/wear/ambient/AmbientDelegate.java +++ b/android/support/wear/ambient/AmbientDelegate.java @@ -19,14 +19,12 @@ import android.app.Activity; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.Log; import com.google.android.wearable.compat.WearableActivityController; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; -import java.lang.reflect.Method; /** * Provides compatibility for ambient mode. @@ -146,19 +144,6 @@ final class AmbientDelegate { } /** - * Sets whether this activity's task should be moved to the front when the system exits ambient - * mode. If true, the activity's task may be moved to the front if it was the last activity to - * be running when ambient started, depending on how much time the system spent in ambient mode. - */ - void setAutoResumeEnabled(boolean enabled) { - if (mWearableController != null) { - if (hasSetAutoResumeEnabledMethod()) { - mWearableController.setAutoResumeEnabled(enabled); - } - } - } - - /** * @return {@code true} if the activity is currently in ambient. */ boolean isAmbient() { @@ -177,31 +162,4 @@ final class AmbientDelegate { mWearableController.dump(prefix, fd, writer, args); } } - - private boolean hasSetAutoResumeEnabledMethod() { - if (!sInitAutoResumeEnabledMethod) { - sInitAutoResumeEnabledMethod = true; - try { - Method method = - WearableActivityController.class - .getDeclaredMethod("setAutoResumeEnabled", boolean.class); - // Proguard is sneaky -- it will actually rewrite strings it finds in addition to - // function names. Therefore add a "." prefix to the method name check to ensure the - // function was not renamed by proguard. - if (!(".setAutoResumeEnabled".equals("." + method.getName()))) { - throw new NoSuchMethodException(); - } - sHasAutoResumeEnabledMethod = true; - } catch (NoSuchMethodException e) { - Log.w( - "WearableActivity", - "Could not find a required method for auto-resume " - + "support, likely due to proguard optimization. Please add " - + "com.google.android.wearable:wearable jar to the list of library " - + "jars for your project"); - sHasAutoResumeEnabledMethod = false; - } - } - return sHasAutoResumeEnabledMethod; - } } diff --git a/android/support/wear/ambient/AmbientMode.java b/android/support/wear/ambient/AmbientMode.java index db53dfc1..5db93830 100644 --- a/android/support/wear/ambient/AmbientMode.java +++ b/android/support/wear/ambient/AmbientMode.java @@ -38,13 +38,13 @@ import java.io.PrintWriter; * It should be called with an {@link Activity} as an argument and that {@link Activity} will then * be able to receive ambient lifecycle events through an {@link AmbientCallback}. The * {@link Activity} will also receive a {@link AmbientController} object from the attachment which - * can be used to query the current status of the ambient mode, or toggle simple settings. + * can be used to query the current status of the ambient mode. * An example of how to attach {@link AmbientMode} to your {@link Activity} and use * the {@link AmbientController} can be found below: * <p> * <pre class="prettyprint">{@code * AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this); - * controller.setAutoResumeEnabled(true); + * boolean isAmbient = controller.isAmbient(); * }</pre> */ public final class AmbientMode extends Fragment { @@ -117,7 +117,7 @@ public final class AmbientMode extends Fragment { * Called when the system is updating the display for ambient mode. Activities may use this * opportunity to update or invalidate views. */ - public void onUpdateAmbient() {}; + public void onUpdateAmbient() {} /** * Called when an activity should exit ambient mode. This event is sent while an activity is @@ -126,7 +126,7 @@ public final class AmbientMode extends Fragment { * <p><em>Derived classes must call through to the super class's implementation of this * method. If they do not, an exception will be thrown.</em> */ - public void onExitAmbient() {}; + public void onExitAmbient() {} } private final AmbientDelegate.AmbientCallback mCallback = @@ -220,7 +220,7 @@ public final class AmbientMode extends Fragment { * @param activity the activity to attach ambient support to. This activity has to also * implement {@link AmbientCallbackProvider} * @return the associated {@link AmbientController} which can be used to query the state of - * ambient mode and toggle simple settings related to it. + * ambient mode. */ public static <T extends Activity & AmbientCallbackProvider> AmbientController attachAmbientSupport(T activity) { @@ -251,9 +251,8 @@ public final class AmbientMode extends Fragment { /** * A class for interacting with the ambient mode on a wearable device. This class can be used to - * query the current state of ambient mode and to enable or disable certain settings. - * An instance of this class is returned to the user when they attach their {@link Activity} - * to {@link AmbientMode}. + * query the current state of ambient mode. An instance of this class is returned to the user + * when they attach their {@link Activity} to {@link AmbientMode}. */ public final class AmbientController { private static final String TAG = "AmbientController"; diff --git a/android/support/wear/ambient/SharedLibraryVersion.java b/android/support/wear/ambient/SharedLibraryVersion.java index cd90a3b7..9421d9ee 100644 --- a/android/support/wear/ambient/SharedLibraryVersion.java +++ b/android/support/wear/ambient/SharedLibraryVersion.java @@ -16,7 +16,6 @@ package android.support.wear.ambient; import android.os.Build; -import android.support.annotation.RestrictTo; import android.support.annotation.VisibleForTesting; import com.google.android.wearable.WearableSharedLib; @@ -24,10 +23,7 @@ import com.google.android.wearable.WearableSharedLib; /** * Internal class which can be used to determine the version of the wearable shared library that is * available on the current device. - * - * @hide */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) final class SharedLibraryVersion { private SharedLibraryVersion() { diff --git a/android/support/wear/ambient/WearableControllerProvider.java b/android/support/wear/ambient/WearableControllerProvider.java index 1682dc0e..4b6ae8ee 100644 --- a/android/support/wear/ambient/WearableControllerProvider.java +++ b/android/support/wear/ambient/WearableControllerProvider.java @@ -28,7 +28,7 @@ import java.lang.reflect.Method; * * @hide */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +@RestrictTo(RestrictTo.Scope.LIBRARY) public class WearableControllerProvider { private static final String TAG = "WearableControllerProvider"; diff --git a/android/support/wear/internal/widget/ResourcesUtil.java b/android/support/wear/internal/widget/ResourcesUtil.java index f23a6889..8ba3adf0 100644 --- a/android/support/wear/internal/widget/ResourcesUtil.java +++ b/android/support/wear/internal/widget/ResourcesUtil.java @@ -26,7 +26,7 @@ import android.support.annotation.RestrictTo.Scope; * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) public final class ResourcesUtil { /** diff --git a/android/support/wear/internal/widget/drawer/MultiPagePresenter.java b/android/support/wear/internal/widget/drawer/MultiPagePresenter.java index ad56048b..4a7ce667 100644 --- a/android/support/wear/internal/widget/drawer/MultiPagePresenter.java +++ b/android/support/wear/internal/widget/drawer/MultiPagePresenter.java @@ -28,7 +28,7 @@ import android.support.wear.widget.drawer.WearableNavigationDrawerView.WearableN * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) public class MultiPagePresenter extends WearableNavigationDrawerPresenter { private final Ui mUi; diff --git a/android/support/wear/internal/widget/drawer/MultiPageUi.java b/android/support/wear/internal/widget/drawer/MultiPageUi.java index 90568451..0ba2f5d1 100644 --- a/android/support/wear/internal/widget/drawer/MultiPageUi.java +++ b/android/support/wear/internal/widget/drawer/MultiPageUi.java @@ -16,6 +16,7 @@ package android.support.wear.internal.widget.drawer; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RestrictTo; import android.support.annotation.RestrictTo.Scope; @@ -37,7 +38,7 @@ import android.widget.TextView; * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) public class MultiPageUi implements MultiPagePresenter.Ui { private static final String TAG = "MultiPageUi"; @@ -62,13 +63,8 @@ public class MultiPageUi implements MultiPagePresenter.Ui { final View content = inflater.inflate(R.layout.ws_navigation_drawer_view, drawer, false /* attachToRoot */); - mNavigationPager = - (ViewPager) content - .findViewById(R.id.ws_navigation_drawer_view_pager); - mPageIndicatorView = - (PageIndicatorView) - content.findViewById( - R.id.ws_navigation_drawer_page_indicator); + mNavigationPager = content.findViewById(R.id.ws_navigation_drawer_view_pager); + mPageIndicatorView = content.findViewById(R.id.ws_navigation_drawer_page_indicator); drawer.setDrawerContent(content); } @@ -132,8 +128,9 @@ public class MultiPageUi implements MultiPagePresenter.Ui { mAdapter = adapter; } + @NonNull @Override - public Object instantiateItem(ViewGroup container, int position) { + public Object instantiateItem(@NonNull ViewGroup container, int position) { // Do not attach to root in the inflate method. The view needs to returned at the end // of this method. Attaching to root will cause view to point to container instead. final View view = @@ -141,17 +138,17 @@ public class MultiPageUi implements MultiPagePresenter.Ui { .inflate(R.layout.ws_navigation_drawer_item_view, container, false); container.addView(view); final ImageView iconView = - (ImageView) view - .findViewById(R.id.ws_navigation_drawer_item_icon); + view.findViewById(R.id.ws_navigation_drawer_item_icon); final TextView textView = - (TextView) view.findViewById(R.id.ws_navigation_drawer_item_text); + view.findViewById(R.id.ws_navigation_drawer_item_text); iconView.setImageDrawable(mAdapter.getItemDrawable(position)); textView.setText(mAdapter.getItemText(position)); return view; } @Override - public void destroyItem(ViewGroup container, int position, Object object) { + public void destroyItem(@NonNull ViewGroup container, int position, + @NonNull Object object) { container.removeView((View) object); } @@ -161,12 +158,12 @@ public class MultiPageUi implements MultiPagePresenter.Ui { } @Override - public int getItemPosition(Object object) { + public int getItemPosition(@NonNull Object object) { return POSITION_NONE; } @Override - public boolean isViewFromObject(View view, Object object) { + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { return view == object; } } diff --git a/android/support/wear/internal/widget/drawer/SinglePagePresenter.java b/android/support/wear/internal/widget/drawer/SinglePagePresenter.java index d90b5891..42cc7d06 100644 --- a/android/support/wear/internal/widget/drawer/SinglePagePresenter.java +++ b/android/support/wear/internal/widget/drawer/SinglePagePresenter.java @@ -29,7 +29,7 @@ import android.support.wear.widget.drawer.WearableNavigationDrawerView.WearableN * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) public class SinglePagePresenter extends WearableNavigationDrawerPresenter { private static final long DRAWER_CLOSE_DELAY_MS = 500; diff --git a/android/support/wear/internal/widget/drawer/SinglePageUi.java b/android/support/wear/internal/widget/drawer/SinglePageUi.java index f3a42904..ffc966f0 100644 --- a/android/support/wear/internal/widget/drawer/SinglePageUi.java +++ b/android/support/wear/internal/widget/drawer/SinglePageUi.java @@ -38,7 +38,7 @@ import android.widget.Toast; * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) public class SinglePageUi implements SinglePagePresenter.Ui { @IdRes @@ -111,11 +111,10 @@ public class SinglePageUi implements SinglePagePresenter.Ui { R.layout.ws_single_page_nav_drawer_peek_view, mDrawer, false /* attachToRoot */); - mTextView = (TextView) content.findViewById(R.id.ws_nav_drawer_text); + mTextView = content.findViewById(R.id.ws_nav_drawer_text); mSinglePageImageViews = new CircledImageView[count]; for (int i = 0; i < count; i++) { - mSinglePageImageViews[i] = (CircledImageView) content - .findViewById(SINGLE_PAGE_BUTTON_IDS[i]); + mSinglePageImageViews[i] = content.findViewById(SINGLE_PAGE_BUTTON_IDS[i]); mSinglePageImageViews[i].setOnClickListener(new OnSelectedClickHandler(i, mPresenter)); mSinglePageImageViews[i].setCircleHidden(true); } diff --git a/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java b/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java index 1c8c4fb7..df108aac 100644 --- a/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java +++ b/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java @@ -30,7 +30,7 @@ import java.util.Set; * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) public abstract class WearableNavigationDrawerPresenter { private final Set<OnItemSelectedListener> mOnItemSelectedListeners = new HashSet<>(); diff --git a/android/support/wear/utils/MetadataConstants.java b/android/support/wear/utils/MetadataConstants.java index 5be9c523..c7335c2d 100644 --- a/android/support/wear/utils/MetadataConstants.java +++ b/android/support/wear/utils/MetadataConstants.java @@ -15,16 +15,13 @@ */ package android.support.wear.utils; -import android.annotation.TargetApi; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.os.Build; /** * Constants for android wear apps which are related to manifest meta-data. */ -@TargetApi(Build.VERSION_CODES.N) public class MetadataConstants { // Constants for standalone apps. // diff --git a/android/support/wear/widget/BezierSCurveInterpolator.java b/android/support/wear/widget/BezierSCurveInterpolator.java index 131bae84..9c56a83d 100644 --- a/android/support/wear/widget/BezierSCurveInterpolator.java +++ b/android/support/wear/widget/BezierSCurveInterpolator.java @@ -17,8 +17,6 @@ package android.support.wear.widget; import android.animation.TimeInterpolator; -import android.annotation.TargetApi; -import android.os.Build; import android.support.annotation.RestrictTo; import android.support.annotation.RestrictTo.Scope; @@ -27,8 +25,7 @@ import android.support.annotation.RestrictTo.Scope; * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) -@TargetApi(Build.VERSION_CODES.KITKAT_WATCH) +@RestrictTo(Scope.LIBRARY) class BezierSCurveInterpolator implements TimeInterpolator { /** diff --git a/android/support/wear/widget/BoxInsetLayout.java b/android/support/wear/widget/BoxInsetLayout.java index ba35f2c9..a8b13814 100644 --- a/android/support/wear/widget/BoxInsetLayout.java +++ b/android/support/wear/widget/BoxInsetLayout.java @@ -20,7 +20,6 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.Build; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -111,21 +110,6 @@ public class BoxInsetLayout extends ViewGroup { } @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - insets = super.onApplyWindowInsets(insets); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - final boolean round = insets.isRound(); - if (round != mIsRound) { - mIsRound = round; - requestLayout(); - } - mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), - insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); - } - return insets; - } - - @Override public void setForeground(Drawable drawable) { super.setForeground(drawable); mForegroundDrawable = drawable; @@ -145,14 +129,10 @@ public class BoxInsetLayout extends ViewGroup { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - requestApplyInsets(); - } else { - mIsRound = getResources().getConfiguration().isScreenRound(); - WindowInsets insets = getRootWindowInsets(); - mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), - insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); - } + mIsRound = getResources().getConfiguration().isScreenRound(); + WindowInsets insets = getRootWindowInsets(); + mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), + insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); } @Override @@ -413,7 +393,7 @@ public class BoxInsetLayout extends ViewGroup { public static class LayoutParams extends FrameLayout.LayoutParams { /** @hide */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @RestrictTo(RestrictTo.Scope.LIBRARY) @IntDef({BOX_NONE, BOX_LEFT, BOX_TOP, BOX_RIGHT, BOX_BOTTOM, BOX_ALL}) @Retention(RetentionPolicy.SOURCE) public @interface BoxedEdges {} diff --git a/android/support/wear/widget/CircledImageView.java b/android/support/wear/widget/CircledImageView.java index 03ed8c98..c441dd57 100644 --- a/android/support/wear/widget/CircledImageView.java +++ b/android/support/wear/widget/CircledImageView.java @@ -19,7 +19,6 @@ package android.support.wear.widget; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -32,7 +31,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; -import android.os.Build; import android.support.annotation.Px; import android.support.annotation.RestrictTo; import android.support.annotation.RestrictTo.Scope; @@ -47,8 +45,7 @@ import java.util.Objects; * * @hide */ -@TargetApi(Build.VERSION_CODES.M) -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) public class CircledImageView extends View { private static final ArgbEvaluator ARGB_EVALUATOR = new ArgbEvaluator(); @@ -133,13 +130,9 @@ public class CircledImageView extends View { if (mDrawable != null && mDrawable.getConstantState() != null) { // The provided Drawable may be used elsewhere, so make a mutable clone before setTint() // or setAlpha() is called on it. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mDrawable = - mDrawable.getConstantState() - .newDrawable(context.getResources(), context.getTheme()); - } else { - mDrawable = mDrawable.getConstantState().newDrawable(context.getResources()); - } + mDrawable = + mDrawable.getConstantState() + .newDrawable(context.getResources(), context.getTheme()); mDrawable = mDrawable.mutate(); } diff --git a/android/support/wear/widget/CurvingLayoutCallback.java b/android/support/wear/widget/CurvingLayoutCallback.java index 275f1f8b..5e88a8c4 100644 --- a/android/support/wear/widget/CurvingLayoutCallback.java +++ b/android/support/wear/widget/CurvingLayoutCallback.java @@ -113,7 +113,7 @@ public class CurvingLayoutCallback extends WearableLinearLayoutManager.LayoutCal */ public void adjustAnchorOffsetXY(View child, float[] anchorOffsetXY) { return; - }; + } @VisibleForTesting void setRound(boolean isScreenRound) { diff --git a/android/support/wear/widget/ProgressDrawable.java b/android/support/wear/widget/ProgressDrawable.java index 08e8ec2e..28e05708 100644 --- a/android/support/wear/widget/ProgressDrawable.java +++ b/android/support/wear/widget/ProgressDrawable.java @@ -19,14 +19,12 @@ package android.support.wear.widget; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; -import android.annotation.TargetApi; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.RectF; import android.graphics.drawable.Drawable; -import android.os.Build; import android.support.annotation.RestrictTo; import android.support.annotation.RestrictTo.Scope; import android.util.Property; @@ -37,8 +35,7 @@ import android.view.animation.LinearInterpolator; * * @hide */ -@TargetApi(Build.VERSION_CODES.KITKAT_WATCH) -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) class ProgressDrawable extends Drawable { private static final Property<ProgressDrawable, Integer> LEVEL = diff --git a/android/support/wear/widget/RoundedDrawable.java b/android/support/wear/widget/RoundedDrawable.java index fd09a878..300b6dd6 100644 --- a/android/support/wear/widget/RoundedDrawable.java +++ b/android/support/wear/widget/RoundedDrawable.java @@ -15,7 +15,6 @@ */ package android.support.wear.widget; -import android.annotation.TargetApi; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; @@ -29,7 +28,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; -import android.os.Build; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -76,7 +74,6 @@ import java.util.Objects; * app:radius="dimension" * app:clipEnabled="boolean" /></pre> */ -@TargetApi(Build.VERSION_CODES.N) public class RoundedDrawable extends Drawable { @VisibleForTesting diff --git a/android/support/wear/widget/ScrollManager.java b/android/support/wear/widget/ScrollManager.java index 8155f62d..e01a2710 100644 --- a/android/support/wear/widget/ScrollManager.java +++ b/android/support/wear/widget/ScrollManager.java @@ -16,11 +16,8 @@ package android.support.wear.widget; -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.annotation.TargetApi; -import android.os.Build; import android.support.annotation.RestrictTo; +import android.support.annotation.RestrictTo.Scope; import android.support.v7.widget.RecyclerView; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -30,8 +27,7 @@ import android.view.VelocityTracker; * * @hide */ -@TargetApi(Build.VERSION_CODES.M) -@RestrictTo(LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) class ScrollManager { // One second in milliseconds. private static final int ONE_SEC_IN_MS = 1000; diff --git a/android/support/wear/widget/SimpleAnimatorListener.java b/android/support/wear/widget/SimpleAnimatorListener.java index a60b0bd2..3a1e56b9 100644 --- a/android/support/wear/widget/SimpleAnimatorListener.java +++ b/android/support/wear/widget/SimpleAnimatorListener.java @@ -29,7 +29,7 @@ import android.support.annotation.RestrictTo.Scope; * @hide Hidden until this goes through review */ @RequiresApi(Build.VERSION_CODES.KITKAT_WATCH) -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) public class SimpleAnimatorListener implements Animator.AnimatorListener { private boolean mWasCanceled; diff --git a/android/support/wear/widget/SwipeDismissLayout.java b/android/support/wear/widget/SwipeDismissLayout.java index 6e7a6f36..33da79c2 100644 --- a/android/support/wear/widget/SwipeDismissLayout.java +++ b/android/support/wear/widget/SwipeDismissLayout.java @@ -16,12 +16,11 @@ package android.support.wear.widget; -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - import android.content.Context; import android.content.res.Resources; import android.support.annotation.Nullable; import android.support.annotation.RestrictTo; +import android.support.annotation.RestrictTo.Scope; import android.support.annotation.UiThread; import android.util.AttributeSet; import android.util.Log; @@ -40,7 +39,7 @@ import android.widget.FrameLayout; * * @hide */ -@RestrictTo(LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) @UiThread class SwipeDismissLayout extends FrameLayout { private static final String TAG = "SwipeDismissLayout"; diff --git a/android/support/wear/widget/WearableRecyclerView.java b/android/support/wear/widget/WearableRecyclerView.java index 5cacdfcb..1425e68b 100644 --- a/android/support/wear/widget/WearableRecyclerView.java +++ b/android/support/wear/widget/WearableRecyclerView.java @@ -16,11 +16,9 @@ package android.support.wear.widget; -import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Point; -import android.os.Build; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.support.wear.R; @@ -35,7 +33,6 @@ import android.view.ViewTreeObserver; * * @see #setCircularScrollingGestureEnabled(boolean) */ -@TargetApi(Build.VERSION_CODES.M) public class WearableRecyclerView extends RecyclerView { private static final String TAG = "WearableRecyclerView"; diff --git a/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java b/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java index f1cb640d..e9b2a40a 100644 --- a/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java +++ b/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java @@ -32,7 +32,7 @@ import java.lang.ref.WeakReference; * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) class AbsListViewFlingWatcher implements FlingWatcher, OnScrollListener { private final FlingListener mListener; diff --git a/android/support/wear/widget/drawer/FlingWatcherFactory.java b/android/support/wear/widget/drawer/FlingWatcherFactory.java index 3fe84c63..2fdfa13c 100644 --- a/android/support/wear/widget/drawer/FlingWatcherFactory.java +++ b/android/support/wear/widget/drawer/FlingWatcherFactory.java @@ -33,7 +33,7 @@ import java.util.WeakHashMap; * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) class FlingWatcherFactory { /** diff --git a/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java b/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java index ca95ab22..4c0e5c8c 100644 --- a/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java +++ b/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java @@ -38,7 +38,7 @@ import java.lang.ref.WeakReference; * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) class NestedScrollViewFlingWatcher implements FlingWatcher, OnScrollChangeListener { static final int MAX_WAIT_TIME_MS = 100; diff --git a/android/support/wear/widget/drawer/PageIndicatorView.java b/android/support/wear/widget/drawer/PageIndicatorView.java index 99c7c09f..1285f725 100644 --- a/android/support/wear/widget/drawer/PageIndicatorView.java +++ b/android/support/wear/widget/drawer/PageIndicatorView.java @@ -54,7 +54,7 @@ import java.util.concurrent.TimeUnit; * @hide */ @RequiresApi(Build.VERSION_CODES.M) -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) public class PageIndicatorView extends View implements OnPageChangeListener { private static final String TAG = "Dots"; diff --git a/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java b/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java index 7570fae5..7916875e 100644 --- a/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java +++ b/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java @@ -31,7 +31,7 @@ import java.lang.ref.WeakReference; * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) class RecyclerViewFlingWatcher extends OnScrollListener implements FlingWatcher { private final FlingListener mListener; diff --git a/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java b/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java index f0b973be..5154e7bb 100644 --- a/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java +++ b/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java @@ -38,7 +38,7 @@ import java.lang.ref.WeakReference; * * @hide */ -@RestrictTo(Scope.LIBRARY_GROUP) +@RestrictTo(Scope.LIBRARY) class ScrollViewFlingWatcher implements FlingWatcher, OnScrollChangeListener { static final int MAX_WAIT_TIME_MS = 100; diff --git a/android/support/wear/widget/drawer/WearableActionDrawerMenu.java b/android/support/wear/widget/drawer/WearableActionDrawerMenu.java index 158467dc..092ac72d 100644 --- a/android/support/wear/widget/drawer/WearableActionDrawerMenu.java +++ b/android/support/wear/widget/drawer/WearableActionDrawerMenu.java @@ -16,12 +16,10 @@ package android.support.wear.widget.drawer; -import android.annotation.TargetApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; -import android.os.Build; import android.support.annotation.Nullable; import android.view.ActionProvider; import android.view.ContextMenu; @@ -34,7 +32,6 @@ import android.view.View; import java.util.ArrayList; import java.util.List; -@TargetApi(Build.VERSION_CODES.M) /* package */ class WearableActionDrawerMenu implements Menu { private final Context mContext; diff --git a/android/support/wear/widget/drawer/WearableActionDrawerView.java b/android/support/wear/widget/drawer/WearableActionDrawerView.java index 8154e6b9..99cd4ff6 100644 --- a/android/support/wear/widget/drawer/WearableActionDrawerView.java +++ b/android/support/wear/widget/drawer/WearableActionDrawerView.java @@ -16,12 +16,10 @@ package android.support.wear.widget.drawer; -import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.os.Build; import android.support.annotation.Nullable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -38,6 +36,7 @@ import android.view.MenuItem; import android.view.MenuItem.OnMenuItemClickListener; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.ImageView; import android.widget.LinearLayout; @@ -75,7 +74,6 @@ import java.util.Objects; * <p>For {@link MenuItem}, setting and getting the title and icon, {@link MenuItem#getItemId}, and * {@link MenuItem#setOnMenuItemClickListener} are implemented. */ -@TargetApi(Build.VERSION_CODES.M) public class WearableActionDrawerView extends WearableDrawerView { private static final String TAG = "WearableActionDrawer"; @@ -140,12 +138,8 @@ public class WearableActionDrawerView extends WearableDrawerView { View peekView = layoutInflater.inflate(R.layout.ws_action_drawer_peek_view, getPeekContainer(), false /* attachToRoot */); setPeekContent(peekView); - mPeekActionIcon = - (ImageView) peekView - .findViewById(R.id.ws_action_drawer_peek_action_icon); - mPeekExpandIcon = - (ImageView) peekView - .findViewById(R.id.ws_action_drawer_expand_icon); + mPeekActionIcon = peekView.findViewById(R.id.ws_action_drawer_peek_action_icon); + mPeekExpandIcon = peekView.findViewById(R.id.ws_action_drawer_expand_icon); } else { mPeekActionIcon = null; mPeekExpandIcon = null; @@ -193,6 +187,16 @@ public class WearableActionDrawerView extends WearableDrawerView { } @Override + public void onDrawerOpened() { + if (mActionListAdapter.getItemCount() > 0) { + RecyclerView.ViewHolder holder = mActionList.findViewHolderForAdapterPosition(0); + if (holder != null && holder.itemView != null) { + holder.itemView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } + } + } + + @Override public boolean canScrollHorizontally(int direction) { // Prevent the window from being swiped closed while it is open by saying that it can scroll // horizontally. @@ -412,7 +416,6 @@ public class WearableActionDrawerView extends WearableDrawerView { CharSequence title = mActionMenu.getItem(titleAwarePosition).getTitle(); holder.textView.setText(title); holder.textView.setContentDescription(title); - holder.iconView.setContentDescription(title); holder.iconView.setImageDrawable(icon); } else if (viewHolder instanceof TitleViewHolder) { TitleViewHolder holder = (TitleViewHolder) viewHolder; diff --git a/android/support/wear/widget/drawer/WearableDrawerLayout.java b/android/support/wear/widget/drawer/WearableDrawerLayout.java index 6d270647..e100a467 100644 --- a/android/support/wear/widget/drawer/WearableDrawerLayout.java +++ b/android/support/wear/widget/drawer/WearableDrawerLayout.java @@ -19,11 +19,10 @@ package android.support.wear.widget.drawer; import static android.support.wear.widget.drawer.WearableDrawerView.STATE_IDLE; import static android.support.wear.widget.drawer.WearableDrawerView.STATE_SETTLING; -import android.annotation.TargetApi; import android.content.Context; -import android.os.Build; import android.os.Handler; import android.os.Looper; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.v4.view.NestedScrollingParent; @@ -98,7 +97,6 @@ import android.widget.FrameLayout; * </android.support.wear.widget.drawer.WearableDrawerView> * </android.support.wear.widget.drawer.WearableDrawerLayout></pre> */ -@TargetApi(Build.VERSION_CODES.M) public class WearableDrawerLayout extends FrameLayout implements View.OnLayoutChangeListener, NestedScrollingParent, FlingListener { @@ -654,12 +652,13 @@ public class WearableDrawerLayout extends FrameLayout } @Override // NestedScrollingParent - public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { + public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, + boolean consumed) { return false; } @Override // NestedScrollingParent - public boolean onNestedPreFling(View target, float velocityX, float velocityY) { + public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) { maybeUpdateScrollingContentView(target); mLastScrollWasFling = true; @@ -674,13 +673,13 @@ public class WearableDrawerLayout extends FrameLayout } @Override // NestedScrollingParent - public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) { maybeUpdateScrollingContentView(target); } @Override // NestedScrollingParent - public void onNestedScroll( - View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed) { boolean scrolledUp = dyConsumed < 0; boolean scrolledDown = dyConsumed > 0; @@ -873,18 +872,20 @@ public class WearableDrawerLayout extends FrameLayout } @Override // NestedScrollingParent - public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, + int nestedScrollAxes) { mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); } @Override // NestedScrollingParent - public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, + int nestedScrollAxes) { mCurrentNestedScrollSlopTracker = 0; return true; } @Override // NestedScrollingParent - public void onStopNestedScroll(View target) { + public void onStopNestedScroll(@NonNull View target) { mNestedScrollingParentHelper.onStopNestedScroll(target); } @@ -961,7 +962,7 @@ public class WearableDrawerLayout extends FrameLayout public abstract WearableDrawerView getDrawerView(); @Override - public boolean tryCaptureView(View child, int pointerId) { + public boolean tryCaptureView(@NonNull View child, int pointerId) { WearableDrawerView drawerView = getDrawerView(); // Returns true if the dragger is dragging the drawer. return child == drawerView && !drawerView.isLocked() @@ -969,13 +970,13 @@ public class WearableDrawerLayout extends FrameLayout } @Override - public int getViewVerticalDragRange(View child) { + public int getViewVerticalDragRange(@NonNull View child) { // Defines the vertical drag range of the drawer. return child == getDrawerView() ? child.getHeight() : 0; } @Override - public void onViewCaptured(View capturedChild, int activePointerId) { + public void onViewCaptured(@NonNull View capturedChild, int activePointerId) { showDrawerContentMaybeAnimate((WearableDrawerView) capturedChild); } @@ -1036,7 +1037,7 @@ public class WearableDrawerLayout extends FrameLayout private class TopDrawerDraggerCallback extends DrawerDraggerCallback { @Override - public int clampViewPositionVertical(View child, int top, int dy) { + public int clampViewPositionVertical(@NonNull View child, int top, int dy) { if (mTopDrawerView == child) { int peekHeight = mTopDrawerView.getPeekContainer().getHeight(); // The top drawer can be dragged vertically from peekHeight - height to 0. @@ -1063,7 +1064,7 @@ public class WearableDrawerLayout extends FrameLayout } @Override - public void onViewReleased(View releasedChild, float xvel, float yvel) { + public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) { if (releasedChild == mTopDrawerView) { // Settle to final position. Either swipe open or close. final float openedPercent = mTopDrawerView.getOpenedPercent(); @@ -1085,7 +1086,8 @@ public class WearableDrawerLayout extends FrameLayout } @Override - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { + public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, + int dy) { if (changedView == mTopDrawerView) { // Compute the offset and invalidate will move the drawer during layout. final int height = changedView.getHeight(); @@ -1106,7 +1108,7 @@ public class WearableDrawerLayout extends FrameLayout private class BottomDrawerDraggerCallback extends DrawerDraggerCallback { @Override - public int clampViewPositionVertical(View child, int top, int dy) { + public int clampViewPositionVertical(@NonNull View child, int top, int dy) { if (mBottomDrawerView == child) { // The bottom drawer can be dragged vertically from (parentHeight - height) to // (parentHeight - peekHeight). @@ -1131,7 +1133,7 @@ public class WearableDrawerLayout extends FrameLayout } @Override - public void onViewReleased(View releasedChild, float xvel, float yvel) { + public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) { if (releasedChild == mBottomDrawerView) { // Settle to final position. Either swipe open or close. final int parentHeight = getHeight(); @@ -1151,7 +1153,8 @@ public class WearableDrawerLayout extends FrameLayout } @Override - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { + public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, + int dy) { if (changedView == mBottomDrawerView) { // Compute the offset and invalidate will move the drawer during layout. final int height = changedView.getHeight(); diff --git a/android/support/wear/widget/drawer/WearableDrawerView.java b/android/support/wear/widget/drawer/WearableDrawerView.java index dafac39b..2462cba8 100644 --- a/android/support/wear/widget/drawer/WearableDrawerView.java +++ b/android/support/wear/widget/drawer/WearableDrawerView.java @@ -16,11 +16,9 @@ package android.support.wear.widget.drawer; -import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.os.Build; import android.support.annotation.IdRes; import android.support.annotation.IntDef; import android.support.annotation.Nullable; @@ -87,7 +85,6 @@ import java.lang.annotation.RetentionPolicy; * </LinearLayout> * </android.support.wear.widget.drawer.WearableDrawerView></pre> */ -@TargetApi(Build.VERSION_CODES.M) public class WearableDrawerView extends FrameLayout { /** * Indicates that the drawer is in an idle, settled state. No animation is in progress. @@ -109,7 +106,7 @@ public class WearableDrawerView extends FrameLayout { * @hide */ @Retention(RetentionPolicy.SOURCE) - @RestrictTo(Scope.LIBRARY_GROUP) + @RestrictTo(Scope.LIBRARY) @IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING}) public @interface DrawerState {} @@ -155,8 +152,8 @@ public class WearableDrawerView extends FrameLayout { setElevation(context.getResources() .getDimension(R.dimen.ws_wearable_drawer_view_elevation)); - mPeekContainer = (ViewGroup) findViewById(R.id.ws_drawer_view_peek_container); - mPeekIcon = (ImageView) findViewById(R.id.ws_drawer_view_peek_icon); + mPeekContainer = findViewById(R.id.ws_drawer_view_peek_container); + mPeekIcon = findViewById(R.id.ws_drawer_view_peek_icon); mPeekContainer.setOnClickListener( new OnClickListener() { diff --git a/android/support/wear/widget/drawer/WearableNavigationDrawerView.java b/android/support/wear/widget/drawer/WearableNavigationDrawerView.java index 480812b8..c5c49fe3 100644 --- a/android/support/wear/widget/drawer/WearableNavigationDrawerView.java +++ b/android/support/wear/widget/drawer/WearableNavigationDrawerView.java @@ -16,11 +16,9 @@ package android.support.wear.widget.drawer; -import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.os.Build; import android.os.Handler; import android.os.Looper; import android.support.annotation.IntDef; @@ -58,7 +56,6 @@ import java.util.concurrent.TimeUnit; * <p>The developer may specify which style to use with the {@code app:navigationStyle} custom * attribute. If not specified, {@link #SINGLE_PAGE singlePage} will be used as the default. */ -@TargetApi(Build.VERSION_CODES.M) public class WearableNavigationDrawerView extends WearableDrawerView { private static final String TAG = "WearableNavDrawer"; @@ -79,7 +76,7 @@ public class WearableNavigationDrawerView extends WearableDrawerView { * @hide */ @Retention(RetentionPolicy.SOURCE) - @RestrictTo(Scope.LIBRARY_GROUP) + @RestrictTo(Scope.LIBRARY) @IntDef({SINGLE_PAGE, MULTI_PAGE}) public @interface NavigationStyle {} @@ -282,7 +279,7 @@ public class WearableNavigationDrawerView extends WearableDrawerView { /** * @hide */ - @RestrictTo(Scope.LIBRARY_GROUP) + @RestrictTo(Scope.LIBRARY) public void setPresenter(WearableNavigationDrawerPresenter presenter) { mPresenter = presenter; } diff --git a/android/support/wearable/watchface/decomposition/package-info.java b/android/support/wearable/watchface/decomposition/package-info.java new file mode 100644 index 00000000..dbd815e7 --- /dev/null +++ b/android/support/wearable/watchface/decomposition/package-info.java @@ -0,0 +1,2 @@ +/** @hide */ +package android.support.wearable.watchface.decomposition; |