Merge "Add vendor display name and ID to artifact source properties" into nyc-mr1-dev
diff --git a/annotations/build.gradle b/annotations/build.gradle
index 61d036b..4907e2a 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -12,6 +12,14 @@
jar {
from sourceSets.main.output
+ // Strip out typedef classes. For Android libraries, this is done
+ // automatically by the Gradle plugin, but the Annotation library is a
+ // plain jar, built by the regular Gradle java plugin. The typedefs
+ // themselves have been manually extracted into the
+ // external-annotations directory, and those are packaged separately
+ // below by the annotationsZip task.
+ exclude('android/support/annotation/ProductionVisibility.class')
+ exclude('android/support/annotation/DimensionUnit.class')
}
uploadArchives {
@@ -72,9 +80,15 @@
from javadoc.destinationDir
}
-// add javadoc/source jar tasks as artifacts
+task annotationsZip(type: Zip) {
+ classifier 'annotations'
+ from 'external-annotations'
+}
+
+// add javadoc/source/annotations jar tasks as artifacts
artifacts {
archives jar
archives sourcesJar
archives javadocJar
+ archives annotationsZip
}
diff --git a/annotations/external-annotations/android/support/annotation/annotations.xml b/annotations/external-annotations/android/support/annotation/annotations.xml
new file mode 100644
index 0000000..23e9a01
--- /dev/null
+++ b/annotations/external-annotations/android/support/annotation/annotations.xml
@@ -0,0 +1,12 @@
+<root>
+ <item name="android.support.annotation.VisibleForTesting int otherwise()">
+ <annotation name="android.support.annotation.IntDef">
+ <val name="value" val="{android.support.annotation.VisibleForTesting.PRIVATE, android.support.annotation.VisibleForTesting.PACKAGE_PRIVATE, android.support.annotation.VisibleForTesting.PROTECTED, android.support.annotation.VisibleForTesting.NONE}" />
+ </annotation>
+ </item>
+ <item name="android.support.annotation.Dimension int unit()">
+ <annotation name="android.support.annotation.IntDef">
+ <val name="value" val="{android.support.annotation.Dimension.DP, android.support.annotation.Dimension.PX, android.support.annotation.Dimension.SP}" />
+ </annotation>
+ </item>
+</root>
diff --git a/annotations/src/android/support/annotation/Dimension.java b/annotations/src/android/support/annotation/Dimension.java
index cb4fb5e..b8c5590 100644
--- a/annotations/src/android/support/annotation/Dimension.java
+++ b/annotations/src/android/support/annotation/Dimension.java
@@ -18,7 +18,6 @@
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
@@ -36,14 +35,10 @@
@Retention(CLASS)
@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
public @interface Dimension {
- @Unit
+ @DimensionUnit
int unit() default PX;
int DP = 0;
int PX = 1;
int SP = 2;
-
- @IntDef({PX, DP, SP})
- @Retention(RetentionPolicy.SOURCE)
- @interface Unit {}
}
diff --git a/annotations/src/android/support/annotation/DimensionUnit.java b/annotations/src/android/support/annotation/DimensionUnit.java
new file mode 100644
index 0000000..d4d6398
--- /dev/null
+++ b/annotations/src/android/support/annotation/DimensionUnit.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 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.annotation;
+
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Typedef for the {@link Dimension#unit} attribute.
+ *
+ * @hide
+ */
+@IntDef({Dimension.PX,
+ Dimension.DP,
+ Dimension.SP}
+// Important: If updating these constants, also update
+// ../../../../external-annotations/android/support/annotation/annotations.xml
+)
+@Retention(SOURCE)
+@interface DimensionUnit {
+}
diff --git a/annotations/src/android/support/annotation/ProductionVisibility.java b/annotations/src/android/support/annotation/ProductionVisibility.java
new file mode 100644
index 0000000..6bd978e
--- /dev/null
+++ b/annotations/src/android/support/annotation/ProductionVisibility.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.annotation;
+
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Typedef for the {@link VisibleForTesting#otherwise} attribute.
+ *
+ * @hide
+ */
+@IntDef({VisibleForTesting.PRIVATE,
+ VisibleForTesting.PACKAGE_PRIVATE,
+ VisibleForTesting.PROTECTED,
+ VisibleForTesting.NONE}
+// Important: If updating these constants, also update
+// ../../../../external-annotations/android/support/annotation/annotations.xml
+)
+@Retention(SOURCE)
+@interface ProductionVisibility {
+}
diff --git a/annotations/src/android/support/annotation/VisibleForTesting.java b/annotations/src/android/support/annotation/VisibleForTesting.java
index ddce1d5..16d915a 100644
--- a/annotations/src/android/support/annotation/VisibleForTesting.java
+++ b/annotations/src/android/support/annotation/VisibleForTesting.java
@@ -18,7 +18,6 @@
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.CLASS;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
/**
* Denotes that the class, method or field has its visibility relaxed, so that it is more widely
@@ -66,9 +65,4 @@
* This is equivalent to {@code @RestrictTo.Scope.TESTS}.
*/
int NONE = 5;
-
- @IntDef({PRIVATE, PACKAGE_PRIVATE, PROTECTED, NONE})
- @Retention(SOURCE)
- @interface ProductionVisibility {
- }
}
diff --git a/api/current.txt b/api/current.txt
index e4cdafc..806a368 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -156,7 +156,7 @@
public final class CustomTabsIntent {
method public static int getMaxToolbarItems();
- method public void launchUrl(android.app.Activity, android.net.Uri);
+ method public void launchUrl(android.content.Context, android.net.Uri);
method public static android.content.Intent setAlwaysUseBrowserUI(android.content.Intent);
method public static boolean shouldAlwaysUseBrowserUI(android.content.Intent);
field public static final java.lang.String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
@@ -3729,7 +3729,6 @@
method public static void setEnterSharedElementCallback(android.app.Activity, android.support.v4.app.SharedElementCallback);
method public static void setExitSharedElementCallback(android.app.Activity, android.support.v4.app.SharedElementCallback);
method public static boolean shouldShowRequestPermissionRationale(android.app.Activity, java.lang.String);
- method public static void startActivity(android.app.Activity, android.content.Intent, android.os.Bundle);
method public static void startActivityForResult(android.app.Activity, android.content.Intent, int, android.os.Bundle);
method public static void startIntentSenderForResult(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public static void startPostponedEnterTransition(android.app.Activity);
@@ -4432,6 +4431,7 @@
method public android.support.v4.app.NotificationCompat.Builder extend(android.support.v4.app.NotificationCompat.Builder);
method public java.util.List<android.support.v4.app.NotificationCompat.Action> getActions();
method public android.graphics.Bitmap getBackground();
+ method public java.lang.String getBridgeTag();
method public int getContentAction();
method public int getContentIcon();
method public int getContentIconGravity();
@@ -4450,6 +4450,7 @@
method public java.util.List<android.app.Notification> getPages();
method public boolean getStartScrollBottom();
method public android.support.v4.app.NotificationCompat.WearableExtender setBackground(android.graphics.Bitmap);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setBridgeTag(java.lang.String);
method public android.support.v4.app.NotificationCompat.WearableExtender setContentAction(int);
method public android.support.v4.app.NotificationCompat.WearableExtender setContentIcon(int);
method public android.support.v4.app.NotificationCompat.WearableExtender setContentIconGravity(int);
@@ -4683,6 +4684,7 @@
method public static boolean isDeviceProtectedStorage(android.content.Context);
method public static boolean startActivities(android.content.Context, android.content.Intent[]);
method public static boolean startActivities(android.content.Context, android.content.Intent[], android.os.Bundle);
+ method public static void startActivity(android.content.Context, android.content.Intent, android.os.Bundle);
}
public class CursorLoader extends android.support.v4.content.AsyncTaskLoader {
diff --git a/build.gradle b/build.gradle
index a3f46a3..9d02725 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@
maven { url "../../prebuilts/maven_repo/android" }
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.2.0-rc1'
+ classpath 'com.android.tools.build:gradle:2.2.0'
}
}
diff --git a/compat/gingerbread/android/support/v4/os/ParcelableCompatCreatorBase.java b/compat/gingerbread/android/support/v4/os/ParcelableCompatCreatorBase.java
deleted file mode 100644
index afac122..0000000
--- a/compat/gingerbread/android/support/v4/os/ParcelableCompatCreatorBase.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2016 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.os;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-class ParcelableCompatCreatorBase<T> implements Parcelable.Creator<T> {
- final ParcelableCompatCreatorCallbacks<T> mCallbacks;
-
- public ParcelableCompatCreatorBase(ParcelableCompatCreatorCallbacks<T> callbacks) {
- mCallbacks = callbacks;
- }
-
- @Override
- public T createFromParcel(Parcel source) {
- return mCallbacks.createFromParcel(source, null);
- }
-
- @Override
- public T[] newArray(int size) {
- return mCallbacks.newArray(size);
- }
-}
diff --git a/compat/gingerbread/android/support/v4/os/ParcelableCompatCreatorCallbacks.java b/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatCreatorCallbacks.java
similarity index 100%
rename from compat/gingerbread/android/support/v4/os/ParcelableCompatCreatorCallbacks.java
rename to compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatCreatorCallbacks.java
diff --git a/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatCreatorHoneycombMR2.java b/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatCreatorHoneycombMR2.java
deleted file mode 100644
index 61a5924..0000000
--- a/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatCreatorHoneycombMR2.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2011 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.os;
-
-import android.os.Parcel;
-
-class ParcelableCompatCreatorHoneycombMR2<T> extends ParcelableCompatCreatorBase<T> {
-
- public ParcelableCompatCreatorHoneycombMR2(ParcelableCompatCreatorCallbacks<T> callbacks) {
- super(callbacks);
- }
-
- public T createFromParcel(Parcel in, ClassLoader loader) {
- return mCallbacks.createFromParcel(in, loader);
- }
-}
diff --git a/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatHoneycombMR2.java b/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatHoneycombMR2.java
new file mode 100644
index 0000000..08acb55
--- /dev/null
+++ b/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatHoneycombMR2.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 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.os;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+class ParcelableCompatCreatorHoneycombMR2Stub {
+ static <T> Parcelable.Creator<T> instantiate(ParcelableCompatCreatorCallbacks<T> callbacks) {
+ return new ParcelableCompatCreatorHoneycombMR2<T>(callbacks);
+ }
+}
+
+class ParcelableCompatCreatorHoneycombMR2<T> implements Parcelable.ClassLoaderCreator<T> {
+ private final ParcelableCompatCreatorCallbacks<T> mCallbacks;
+
+ public ParcelableCompatCreatorHoneycombMR2(ParcelableCompatCreatorCallbacks<T> callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ public T createFromParcel(Parcel in) {
+ return mCallbacks.createFromParcel(in, null);
+ }
+
+ public T createFromParcel(Parcel in, ClassLoader loader) {
+ return mCallbacks.createFromParcel(in, loader);
+ }
+
+ public T[] newArray(int size) {
+ return mCallbacks.newArray(size);
+ }
+}
diff --git a/compat/java/android/support/v4/app/ActivityCompat.java b/compat/java/android/support/v4/app/ActivityCompat.java
index b97d101..c30e49d 100644
--- a/compat/java/android/support/v4/app/ActivityCompat.java
+++ b/compat/java/android/support/v4/app/ActivityCompat.java
@@ -120,31 +120,6 @@
}
/**
- * Start an activity with additional launch information, if able.
- *
- * <p>In Android 4.1+ additional options were introduced to allow for more
- * control on activity launch animations. Applications can use this method
- * along with {@link ActivityOptionsCompat} to use these animations when
- * available. When run on versions of the platform where this feature does
- * not exist the activity will be launched normally.</p>
- *
- * @param activity Context to launch activity from.
- * @param intent The description of the activity to start.
- * @param options Additional options for how the Activity should be started.
- * May be null if there are no options. See
- * {@link ActivityOptionsCompat} for how to build the Bundle
- * supplied here; there are no supported definitions for
- * building it manually.
- */
- public static void startActivity(Activity activity, Intent intent, @Nullable Bundle options) {
- if (Build.VERSION.SDK_INT >= 16) {
- ActivityCompatJB.startActivity(activity, intent, options);
- } else {
- activity.startActivity(intent);
- }
- }
-
- /**
* Start new activity with options, if able, for which you would like a
* result when it finished.
*
diff --git a/compat/java/android/support/v4/app/ActivityOptionsCompat.java b/compat/java/android/support/v4/app/ActivityOptionsCompat.java
index 692b202..68fc441 100644
--- a/compat/java/android/support/v4/app/ActivityOptionsCompat.java
+++ b/compat/java/android/support/v4/app/ActivityOptionsCompat.java
@@ -442,7 +442,7 @@
/**
* Returns the created options as a Bundle, which can be passed to
- * {@link ActivityCompat#startActivity(android.app.Activity, android.content.Intent, android.os.Bundle)}.
+ * {@link android.support.v4.content.ContextCompat#startActivity(Context, android.content.Intent, Bundle)}.
* Note that the returned Bundle is still owned by the ActivityOptions
* object; you must not modify it, but can supply it to the startActivity
* methods that take an options Bundle.
diff --git a/compat/java/android/support/v4/app/NotificationCompat.java b/compat/java/android/support/v4/app/NotificationCompat.java
index fdf5a87..e2976c2 100644
--- a/compat/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/java/android/support/v4/app/NotificationCompat.java
@@ -3011,6 +3011,7 @@
private static final String KEY_GRAVITY = "gravity";
private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
private static final String KEY_DISMISSAL_ID = "dismissalId";
+ private static final String KEY_BRIDGE_TAG = "bridgeTag";
// Flags bitwise-ored to mFlags
private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
@@ -3040,6 +3041,7 @@
private int mGravity = DEFAULT_GRAVITY;
private int mHintScreenTimeout;
private String mDismissalId;
+ private String mBridgeTag;
/**
* Create a {@link NotificationCompat.WearableExtender} with default
@@ -3080,6 +3082,7 @@
mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
+ mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
}
}
@@ -3135,6 +3138,9 @@
if (mDismissalId != null) {
wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
}
+ if (mBridgeTag != null) {
+ wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
+ }
builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
return builder;
@@ -3156,6 +3162,7 @@
that.mGravity = this.mGravity;
that.mHintScreenTimeout = this.mHintScreenTimeout;
that.mDismissalId = this.mDismissalId;
+ that.mBridgeTag = this.mBridgeTag;
return that;
}
@@ -3644,12 +3651,11 @@
}
/**
- * When you post a notification, if you set the dismissal id field, then when that
- * notification is canceled, notifications on other wearables and the paired Android phone
- * having that same dismissal id will also be canceled. Note that this only works if you
- * have notification bridge mode set to NO_BRIDGING in your Wear app manifest. See
+ * Sets the dismissal id for this notification. If a notification is posted with a
+ * dismissal id, then when that notification is canceled, notifications on other wearables
+ * and the paired Android phone having that same dismissal id will also be canceled. See
* <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
- * Notifications</a> for more information on how to use the bridge mode feature.
+ * Notifications</a> for more information.
* @param dismissalId the dismissal id of the notification.
* @return this object for method chaining
*/
@@ -3666,6 +3672,27 @@
return mDismissalId;
}
+ /**
+ * Sets a bridge tag for this notification. A bridge tag can be set for notifications
+ * posted from a phone to provide finer-grained control on what notifications are bridged
+ * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
+ * Features to Notifications</a> for more information.
+ * @param bridgeTag the bridge tag of the notification.
+ * @return this object for method chaining
+ */
+ public WearableExtender setBridgeTag(String bridgeTag) {
+ mBridgeTag = bridgeTag;
+ return this;
+ }
+
+ /**
+ * Returns the bridge tag of the notification.
+ * @return the bridge tag or null if not present.
+ */
+ public String getBridgeTag() {
+ return mBridgeTag;
+ }
+
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
diff --git a/compat/java/android/support/v4/content/ContextCompat.java b/compat/java/android/support/v4/content/ContextCompat.java
index 05486c2..d51d6cb 100644
--- a/compat/java/android/support/v4/content/ContextCompat.java
+++ b/compat/java/android/support/v4/content/ContextCompat.java
@@ -29,6 +29,8 @@
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.os.BuildCompat;
import android.support.v4.os.EnvironmentCompat;
import android.util.Log;
@@ -128,6 +130,31 @@
}
/**
+ * Start an activity with additional launch information, if able.
+ *
+ * <p>In Android 4.1+ additional options were introduced to allow for more
+ * control on activity launch animations. Applications can use this method
+ * along with {@link ActivityOptionsCompat} to use these animations when
+ * available. When run on versions of the platform where this feature does
+ * not exist the activity will be launched normally.</p>
+ *
+ * @param context Context to launch activity from.
+ * @param intent The description of the activity to start.
+ * @param options Additional options for how the Activity should be started.
+ * May be null if there are no options. See
+ * {@link ActivityOptionsCompat} for how to build the Bundle
+ * supplied here; there are no supported definitions for
+ * building it manually.
+ */
+ public static void startActivity(Context context, Intent intent, @Nullable Bundle options) {
+ if (Build.VERSION.SDK_INT >= 16) {
+ ContextCompatJellybean.startActivity(context, intent, options);
+ } else {
+ context.startActivity(intent);
+ }
+ }
+
+ /**
* Returns the absolute path to the directory on the filesystem where all
* private files belonging to this app are stored. Apps should not use this
* path directly; they should instead use {@link Context#getFilesDir()},
diff --git a/compat/java/android/support/v4/os/ParcelableCompat.java b/compat/java/android/support/v4/os/ParcelableCompat.java
index 6c8820ca..10c03b5 100644
--- a/compat/java/android/support/v4/os/ParcelableCompat.java
+++ b/compat/java/android/support/v4/os/ParcelableCompat.java
@@ -16,6 +16,7 @@
package android.support.v4.os;
+import android.os.Parcel;
import android.os.Parcelable;
/**
@@ -23,6 +24,7 @@
* introduced after API level 4 in a backwards compatible fashion.
*/
public final class ParcelableCompat {
+
/**
* Factory method for {@link Parcelable.Creator}.
*
@@ -32,9 +34,27 @@
public static <T> Parcelable.Creator<T> newCreator(
ParcelableCompatCreatorCallbacks<T> callbacks) {
if (android.os.Build.VERSION.SDK_INT >= 13) {
- return new ParcelableCompatCreatorHoneycombMR2<T>(callbacks);
+ return ParcelableCompatCreatorHoneycombMR2Stub.instantiate(callbacks);
}
- return new ParcelableCompatCreatorBase<T>(callbacks);
+ return new CompatCreator<T>(callbacks);
+ }
+
+ static class CompatCreator<T> implements Parcelable.Creator<T> {
+ final ParcelableCompatCreatorCallbacks<T> mCallbacks;
+
+ public CompatCreator(ParcelableCompatCreatorCallbacks<T> callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public T createFromParcel(Parcel source) {
+ return mCallbacks.createFromParcel(source, null);
+ }
+
+ @Override
+ public T[] newArray(int size) {
+ return mCallbacks.newArray(size);
+ }
}
private ParcelableCompat() {}
diff --git a/compat/jellybean/android/support/v4/app/ActivityCompatJB.java b/compat/jellybean/android/support/v4/app/ActivityCompatJB.java
index 77fe7fb..13e6e4e 100644
--- a/compat/jellybean/android/support/v4/app/ActivityCompatJB.java
+++ b/compat/jellybean/android/support/v4/app/ActivityCompatJB.java
@@ -17,16 +17,11 @@
package android.support.v4.app;
import android.app.Activity;
-import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
class ActivityCompatJB {
- public static void startActivity(Context context, Intent intent, Bundle options) {
- context.startActivity(intent, options);
- }
-
public static void startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) {
activity.startActivityForResult(intent, requestCode, options);
}
diff --git a/compat/jellybean/android/support/v4/content/ContextCompatJellybean.java b/compat/jellybean/android/support/v4/content/ContextCompatJellybean.java
index c353b96..5e9f910 100644
--- a/compat/jellybean/android/support/v4/content/ContextCompatJellybean.java
+++ b/compat/jellybean/android/support/v4/content/ContextCompatJellybean.java
@@ -26,4 +26,8 @@
context.startActivities(intents, options);
}
+ public static void startActivity(Context context, Intent intent, Bundle options) {
+ context.startActivity(intent, options);
+ }
+
}
diff --git a/customtabs/src/android/support/customtabs/CustomTabsIntent.java b/customtabs/src/android/support/customtabs/CustomTabsIntent.java
index 75214ee..f2de314 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsIntent.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsIntent.java
@@ -28,9 +28,9 @@
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.app.BundleCompat;
+import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.RemoteViews;
@@ -254,12 +254,12 @@
/**
* Convenience method to launch a Custom Tabs Activity.
- * @param context The source Activity.
+ * @param context The source Context.
* @param url The URL to load in the Custom Tab.
*/
- public void launchUrl(Activity context, Uri url) {
+ public void launchUrl(Context context, Uri url) {
intent.setData(url);
- ActivityCompat.startActivity(context, intent, startAnimationBundle);
+ ContextCompat.startActivity(context, intent, startAnimationBundle);
}
private CustomTabsIntent(Intent intent, Bundle startAnimationBundle) {
diff --git a/samples/Support13Demos/src/com/example/android/supportv13/view/inputmethod/CommitContentSupport.java b/samples/Support13Demos/src/com/example/android/supportv13/view/inputmethod/CommitContentSupport.java
index 29f8a91f..4a6181d 100644
--- a/samples/Support13Demos/src/com/example/android/supportv13/view/inputmethod/CommitContentSupport.java
+++ b/samples/Support13Demos/src/com/example/android/supportv13/view/inputmethod/CommitContentSupport.java
@@ -29,18 +29,13 @@
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
-import android.view.Gravity;
-import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.webkit.WebView;
import android.widget.EditText;
-import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
-import static android.widget.LinearLayout.VERTICAL;
-
import java.util.ArrayList;
import java.util.Arrays;
@@ -120,7 +115,7 @@
mCurrentInputContentInfo.releasePermission();
}
} catch (Exception e) {
- Log.e(TAG, "InputContentInfo#releasePermission() failed.", e);
+ Log.e(TAG, "InputContentInfoCompat#releasePermission() failed.", e);
} finally {
mCurrentInputContentInfo = null;
}
@@ -151,7 +146,7 @@
try {
inputContentInfo.requestPermission();
} catch (Exception e) {
- Log.e(TAG, "InputContentInfo#requestPermission() failed.", e);
+ Log.e(TAG, "InputContentInfoCompat#requestPermission() failed.", e);
return false;
}
}
diff --git a/v13/java/android/support/v13/view/inputmethod/EditorInfoCompat.java b/v13/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
index c1bffd42..79d7066 100644
--- a/v13/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
+++ b/v13/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
@@ -23,6 +23,10 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
+/**
+ * Helper for accessing features in {@link EditorInfo} introduced after API level 13 in a backwards
+ * compatible fashion.
+ */
public final class EditorInfoCompat {
private interface EditorInfoCompatImpl {
diff --git a/v13/java/android/support/v13/view/inputmethod/InputConnectionCompat.java b/v13/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
index b740502..07a76aa 100644
--- a/v13/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
+++ b/v13/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
@@ -203,10 +203,8 @@
* <a href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files</a>.
*
* <p>Make sure that the content provider owning the Uri sets the
- * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions
- * grantUriPermissions} attribute in its manifest or included the
- * {@link android.R.styleable#AndroidManifestGrantUriPermission
- * <grant-uri-permissions>} tag.</p>
+ * {@link android.R.attr#grantUriPermissions grantUriPermissions} attribute in its manifest or
+ * included the {@code <grant-uri-permissions>} tag.</p>
*
* <p>Supported only on API >= 25.</p>
*
diff --git a/v13/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java b/v13/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java
index 6f20594..43ad7ff 100644
--- a/v13/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java
+++ b/v13/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java
@@ -22,6 +22,10 @@
import android.support.annotation.Nullable;
import android.support.v4.os.BuildCompat;
+/**
+ * Helper for accessing features in InputContentInfo introduced after API level 13 in a backwards
+ * compatible fashion.
+ */
public final class InputContentInfoCompat {
private interface InputContentInfoCompatImpl {
@@ -144,6 +148,19 @@
private final InputContentInfoCompatImpl mImpl;
+ /**
+ * Constructs {@link InputContentInfoCompat}.
+ *
+ * @param contentUri content URI to be exported from the input method. This cannot be
+ * {@code null}.
+ * @param description a {@link ClipDescription} object that contains the metadata of
+ * {@code contentUri} such as MIME type(s). This object cannot be
+ * {@code null}. Also {@link ClipDescription#getLabel()} should be describing
+ * the content specified by {@code contentUri} for accessibility reasons.
+ * @param linkUri an optional {@code http} or {@code https} URI. The editor author may provide
+ * a way to navigate the user to the specified web page if this is not
+ * {@code null}.
+ */
public InputContentInfoCompat(@NonNull Uri contentUri,
@NonNull ClipDescription description, @Nullable Uri linkUri) {
if (BuildCompat.isAtLeastNMR1()) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
index 004f6f0..2cf897f 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
@@ -492,14 +492,18 @@
boolean canPause = keyEvent == null ||
keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE;
- if (mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
- if (canPlay) {
- mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
- startPlayback(mPlaybackSpeed);
- }
- } else if (canPause) {
+ // PLAY_PAUSE PLAY PAUSE
+ // playing paused paused
+ // paused playing playing
+ // ff/rw playing playing paused
+ if (canPause &&
+ (canPlay ? mPlaybackSpeed == PLAYBACK_SPEED_NORMAL :
+ mPlaybackSpeed != PLAYBACK_SPEED_PAUSED)) {
mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
pausePlayback();
+ } else if (canPlay && mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
+ mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+ startPlayback(mPlaybackSpeed);
}
updatePlaybackStatusAfterUserAction();
handled = true;
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
index 3dcab93..5dbec44 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
@@ -348,4 +348,161 @@
assertEquals(0, rewind.getIndex());
}
}
+
+ @Test
+ public void testMediaPauseButtonOnFF() {
+ PlaybackControlsRow row = new PlaybackControlsRow();
+ glue.setControlsRow(row);
+ SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+ row.getPrimaryActionsAdapter();
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+ PlaybackControlsRow.MultiAction fastForward = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_FAST_FORWARD);
+
+ glue.onActionClicked(playPause);
+ glue.onActionClicked(fastForward);
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0, glue.getCurrentSpeedId());
+ glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_MEDIA_PAUSE));
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+ }
+
+ @Test
+ public void testMediaPauseButtonOnPlay() {
+ PlaybackControlsRow row = new PlaybackControlsRow();
+ glue.setControlsRow(row);
+ SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+ row.getPrimaryActionsAdapter();
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+ glue.onActionClicked(playPause);
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+ glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_MEDIA_PAUSE));
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+ }
+
+ @Test
+ public void testMediaPauseButtonOnPause() {
+ PlaybackControlsRow row = new PlaybackControlsRow();
+ glue.setControlsRow(row);
+ SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+ row.getPrimaryActionsAdapter();
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+ glue.onActionClicked(playPause);
+ glue.onActionClicked(playPause);
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+ glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_MEDIA_PAUSE));
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+ }
+
+ @Test
+ public void testMediaPlayButtonOnFF() {
+ PlaybackControlsRow row = new PlaybackControlsRow();
+ glue.setControlsRow(row);
+ SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+ row.getPrimaryActionsAdapter();
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+ PlaybackControlsRow.MultiAction fastForward = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_FAST_FORWARD);
+
+ glue.onActionClicked(playPause);
+ glue.onActionClicked(fastForward);
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0, glue.getCurrentSpeedId());
+ glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_MEDIA_PLAY));
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+ }
+
+ @Test
+ public void testMediaPlayButtonOnPlay() {
+ PlaybackControlsRow row = new PlaybackControlsRow();
+ glue.setControlsRow(row);
+ SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+ row.getPrimaryActionsAdapter();
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+ glue.onActionClicked(playPause);
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+ glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_MEDIA_PLAY));
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+ }
+
+ @Test
+ public void testMediaPlayButtonOnPause() {
+ PlaybackControlsRow row = new PlaybackControlsRow();
+ glue.setControlsRow(row);
+ SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+ row.getPrimaryActionsAdapter();
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+ glue.onActionClicked(playPause);
+ glue.onActionClicked(playPause);
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+ glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_MEDIA_PLAY));
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+ }
+
+ @Test
+ public void testMediaPlayPauseButtonOnFF() {
+ PlaybackControlsRow row = new PlaybackControlsRow();
+ glue.setControlsRow(row);
+ SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+ row.getPrimaryActionsAdapter();
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+ PlaybackControlsRow.MultiAction fastForward = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_FAST_FORWARD);
+
+ glue.onActionClicked(playPause);
+ glue.onActionClicked(fastForward);
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0, glue.getCurrentSpeedId());
+ glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+ }
+
+ @Test
+ public void testMediaPlayPauseButtonOnPlay() {
+ PlaybackControlsRow row = new PlaybackControlsRow();
+ glue.setControlsRow(row);
+ SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+ row.getPrimaryActionsAdapter();
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+ glue.onActionClicked(playPause);
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+ glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+ }
+
+ @Test
+ public void testMediaPlayPauseButtonOnPause() {
+ PlaybackControlsRow row = new PlaybackControlsRow();
+ glue.setControlsRow(row);
+ SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+ row.getPrimaryActionsAdapter();
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+ glue.onActionClicked(playPause);
+ glue.onActionClicked(playPause);
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+ glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+ }
+
}
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
index cd6f57e..9d8cf07 100644
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
+++ b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
@@ -16,6 +16,8 @@
package android.support.v7.app;
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
+
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
@@ -39,8 +41,6 @@
import java.util.List;
-import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
-
/**
* An extension of {@link android.support.v4.app.NotificationCompat} which supports
* {@link android.support.v7.app.NotificationCompat.MediaStyle},
@@ -172,25 +172,17 @@
? b.getColor()
: color;
}
- CharSequence senderText = bidiWrapIfNotSpanned(bidi, replyName);
+ CharSequence senderText = bidi.unicodeWrap(replyName);
sb.append(senderText);
sb.setSpan(makeFontColorSpan(color),
sb.length() - senderText.length(),
sb.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* flags */);
CharSequence text = m.getText() == null ? "" : m.getText();
- sb.append(" ").append(bidiWrapIfNotSpanned(bidi, text));
+ sb.append(" ").append(bidi.unicodeWrap(text));
return sb;
}
- private static CharSequence bidiWrapIfNotSpanned(BidiFormatter bidi, CharSequence replyName) {
- // Unfortunately bidiFormatter doesn't support CharSequences in support
- if (replyName instanceof Spanned) {
- return replyName;
- }
- return bidi.unicodeWrap(replyName.toString());
- }
-
private static TextAppearanceSpan makeFontColorSpan(int color) {
return new TextAppearanceSpan(null, 0, 0, ColorStateList.valueOf(color), null);
}
diff --git a/v7/appcompat/src/android/support/v7/widget/SearchView.java b/v7/appcompat/src/android/support/v7/widget/SearchView.java
index b224b42..79df8f4 100644
--- a/v7/appcompat/src/android/support/v7/widget/SearchView.java
+++ b/v7/appcompat/src/android/support/v7/widget/SearchView.java
@@ -856,9 +856,11 @@
switch (heightMode) {
case MeasureSpec.AT_MOST:
- case MeasureSpec.UNSPECIFIED:
height = Math.min(getPreferredHeight(), height);
break;
+ case MeasureSpec.UNSPECIFIED:
+ height = getPreferredHeight();
+ break;
}
heightMode = MeasureSpec.EXACTLY;
diff --git a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
index 19c62db..5040bba 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
@@ -22,8 +22,6 @@
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
-import android.support.v7.app.AppCompatDelegate;
-import android.support.v7.widget.VectorEnabledTintResources;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -39,25 +37,38 @@
@RestrictTo(GROUP_ID)
public class TintContextWrapper extends ContextWrapper {
- private static final ArrayList<WeakReference<TintContextWrapper>> sCache = new ArrayList<>();
+ private static final Object CACHE_LOCK = new Object();
+ private static ArrayList<WeakReference<TintContextWrapper>> sCache;
public static Context wrap(@NonNull final Context context) {
if (shouldWrap(context)) {
- // First check our instance cache
- for (int i = 0, count = sCache.size(); i < count; i++) {
- final WeakReference<TintContextWrapper> ref = sCache.get(i);
- final TintContextWrapper wrapper = ref != null ? ref.get() : null;
- if (wrapper != null && wrapper.getBaseContext() == context) {
- return wrapper;
+ synchronized (CACHE_LOCK) {
+ if (sCache == null) {
+ sCache = new ArrayList<>();
+ } else {
+ // This is a convenient place to prune any dead reference entries
+ for (int i = sCache.size() - 1; i >= 0; i--) {
+ final WeakReference<TintContextWrapper> ref = sCache.get(i);
+ if (ref == null || ref.get() == null) {
+ sCache.remove(i);
+ }
+ }
+ // Now check our instance cache
+ for (int i = sCache.size() - 1; i >= 0; i--) {
+ final WeakReference<TintContextWrapper> ref = sCache.get(i);
+ final TintContextWrapper wrapper = ref != null ? ref.get() : null;
+ if (wrapper != null && wrapper.getBaseContext() == context) {
+ return wrapper;
+ }
+ }
}
+ // If we reach here then the cache didn't have a hit, so create a new instance
+ // and add it to the cache
+ final TintContextWrapper wrapper = new TintContextWrapper(context);
+ sCache.add(new WeakReference<>(wrapper));
+ return wrapper;
}
- // If we reach here then the cache didn't have a hit, so create a new instance
- // and add it to the cache
- final TintContextWrapper wrapper = new TintContextWrapper(context);
- sCache.add(new WeakReference<>(wrapper));
- return wrapper;
}
-
return context;
}
@@ -69,14 +80,7 @@
// If the Context is already a TintContextWrapper, no need to wrap again
return false;
}
- if (AppCompatDelegate.isCompatVectorFromResourcesEnabled()
- && Build.VERSION.SDK_INT > VectorEnabledTintResources.MAX_SDK_WHERE_REQUIRED) {
- // If we're running on API 21+ and have the vector resources enabled, there's
- // no need to wrap
- return false;
- }
- // Else, we should wrap
- return true;
+ return Build.VERSION.SDK_INT < 21 || VectorEnabledTintResources.shouldBeUsed();
}
private final Resources mResources;
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceCategory.java b/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
index b375b45..25f0b69 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
@@ -23,8 +23,8 @@
import android.util.AttributeSet;
/**
- * Used to group {@link android.preference.Preference} objects
- * and provide a disabled title above the group.
+ * Used to group {@link Preference} objects and provide a disabled title above
+ * the group.
*
* <div class="special reference">
* <h3>Developer Guides</h3>
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 88e0c41..33f4ede 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -4439,7 +4439,7 @@
private int mDx;
private int mDy;
- private int[] mItemPrefetchArray;
+ int[] mItemPrefetchArray;
/**
* Schedule a prefetch immediately after the current traversal.
@@ -4456,6 +4456,24 @@
}
}
+ public boolean lastPrefetchIncludedPosition(int position) {
+ if (mItemPrefetchArray != null) {
+ for (int i = 0; i < mItemPrefetchArray.length; i++) {
+ if (mItemPrefetchArray[i] == position) return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called when prefetch indices are no longer valid for cache prioritization.
+ */
+ public void clearPrefetchPositions() {
+ if (mItemPrefetchArray != null) {
+ Arrays.fill(mItemPrefetchArray, -1);
+ }
+ }
+
@Override
public void run() {
try {
@@ -4612,6 +4630,9 @@
if (scroller.isFinished() || !fullyConsumedAny) {
setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this.
+ if (ALLOW_PREFETCHING) {
+ mViewPrefetcher.clearPrefetchPositions();
+ }
} else {
postOnAnimation();
if (ALLOW_PREFETCHING) {
@@ -5346,6 +5367,9 @@
recycleCachedViewAt(i);
}
mCachedViews.clear();
+ if (ALLOW_PREFETCHING) {
+ mViewPrefetcher.clearPrefetchPositions();
+ }
}
/**
@@ -5406,18 +5430,33 @@
holder);
}
if (forceRecycle || holder.isRecyclable()) {
- if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
- | ViewHolder.FLAG_UPDATE)) {
+ if (mViewCacheMax > 0
+ && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
+ | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
- cachedViewSize --;
+ cachedViewSize--;
}
- if (cachedViewSize < mViewCacheMax) {
- mCachedViews.add(holder);
- cached = true;
+
+ int targetCacheIndex = cachedViewSize;
+ if (ALLOW_PREFETCHING
+ && cachedViewSize > 0
+ && !mViewPrefetcher.lastPrefetchIncludedPosition(holder.mPosition)) {
+ // when adding the view, skip past most recently prefetched views
+ int cacheIndex = cachedViewSize - 1;
+ while (cacheIndex >= 0) {
+ int cachedPos = mCachedViews.get(cacheIndex).mPosition;
+ if (!mViewPrefetcher.lastPrefetchIncludedPosition(cachedPos)) {
+ break;
+ }
+ cacheIndex--;
+ }
+ targetCacheIndex = cacheIndex + 1;
}
+ mCachedViews.add(targetCacheIndex, holder);
+ cached = true;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
new file mode 100644
index 0000000..f0225d3
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2016 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.v7.widget;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+
+@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+@RunWith(AndroidJUnit4.class)
+public class RecyclerViewCacheTest {
+ RecyclerView mRecyclerView;
+ RecyclerView.Recycler mRecycler;
+ RecyclerView.ViewPrefetcher mViewPrefetcher;
+
+ @Before
+ public void setUp() throws Exception {
+ mRecyclerView = new RecyclerView(getContext());
+ mRecycler = mRecyclerView.mRecycler;
+ mViewPrefetcher = mRecyclerView.mViewPrefetcher;
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Test
+ public void prefetchReusesCacheItems() {
+ RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() {
+ @Override
+ public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+ return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ int getItemPrefetchCount() {
+ return 3;
+ }
+
+ @Override
+ int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) {
+ outIndices[0] = 0;
+ outIndices[1] = 1;
+ outIndices[2] = 2;
+ return 3;
+ }
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ }
+ };
+ mRecyclerView.setLayoutManager(prefetchingLayoutManager);
+
+ RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
+ when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
+ .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
+ @Override
+ public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
+ throws Throwable {
+ return new RecyclerView.ViewHolder(new View(getContext())) {};
+ }
+ });
+ when(mockAdapter.getItemCount()).thenReturn(10);
+ mRecyclerView.setAdapter(mockAdapter);
+
+ mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240);
+ mRecyclerView.layout(0, 0, 320, 320);
+
+ verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt());
+ verify(mockAdapter, never()).onBindViewHolder(
+ any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
+ assertTrue(mRecycler.mCachedViews.isEmpty());
+
+ // Prefetch multiple times...
+ for (int i = 0; i < 4; i++) {
+ int[] itemPrefetchArray = new int[] {-1, -1, -1};
+ int viewCount = prefetchingLayoutManager.gatherPrefetchIndices(1, 1,
+ mRecyclerView.mState, itemPrefetchArray);
+ mRecycler.prefetch(itemPrefetchArray, viewCount);
+
+ // ...but should only see the same three items fetched/bound once each
+ verify(mockAdapter, times(3)).onCreateViewHolder(any(ViewGroup.class), anyInt());
+ verify(mockAdapter, times(3)).onBindViewHolder(
+ any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
+
+ assertTrue(mRecycler.mCachedViews.size() == 3);
+ verifyCacheContainsPositions(0, 1, 2);
+ }
+ }
+
+ private void verifyCacheContainsPosition(int position) {
+ for (int i = 0; i < mRecycler.mCachedViews.size(); i++) {
+ if (mRecycler.mCachedViews.get(i).mPosition == position) return;
+ }
+ fail("Cache does not contain position " + position);
+ }
+
+ private void verifyCacheContainsPositions(Integer... positions) {
+ for (int i = 0; i < positions.length; i++) {
+ verifyCacheContainsPosition(positions[i]);
+ }
+ }
+
+ @Test
+ public void prefetchItemsNotEvictedWithInserts() {
+ mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
+
+ RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
+ when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
+ .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
+ @Override
+ public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
+ throws Throwable {
+ return new RecyclerView.ViewHolder(new View(getContext())) {};
+ }
+ });
+ when(mockAdapter.getItemCount()).thenReturn(100);
+ mRecyclerView.setAdapter(mockAdapter);
+
+
+ mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 320);
+ mRecyclerView.layout(0, 0, 320, 320);
+
+ mViewPrefetcher.mItemPrefetchArray = new int[] { 0, 1, 2 };
+ mRecycler.prefetch(mViewPrefetcher.mItemPrefetchArray, 3);
+ verifyCacheContainsPositions(0, 1, 2);
+
+ // further views recycled, as though from scrolling, shouldn't evict prefetched views:
+ mRecycler.recycleView(mRecycler.getViewForPosition(10));
+ verifyCacheContainsPositions(0, 1, 2, 10);
+
+ mRecycler.recycleView(mRecycler.getViewForPosition(20));
+ verifyCacheContainsPositions(0, 1, 2, 10, 20);
+
+ mRecycler.recycleView(mRecycler.getViewForPosition(30));
+ verifyCacheContainsPositions(0, 1, 2, 20, 30);
+
+ mRecycler.recycleView(mRecycler.getViewForPosition(40));
+ verifyCacheContainsPositions(0, 1, 2, 30, 40);
+
+
+ // After clearing the cache, the prefetch priorities should be cleared as well:
+ mRecyclerView.mRecycler.recycleAndClearCachedViews();
+ for (int i : new int[] {0, 1, 2, 50, 60, 70, 80, 90}) {
+ mRecycler.recycleView(mRecycler.getViewForPosition(i));
+ }
+
+ // cache only contains most recent positions, no priority for previous prefetches:
+ verifyCacheContainsPositions(50, 60, 70, 80, 90);
+
+ }
+
+ @Test
+ public void prefetchItemsNotEvictedOnScroll() {
+ mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
+
+ // 100x100 pixel views
+ RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
+ when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
+ .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
+ @Override
+ public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
+ throws Throwable {
+ View view = new View(getContext());
+ view.setMinimumWidth(100);
+ view.setMinimumHeight(100);
+ return new RecyclerView.ViewHolder(view) {};
+ }
+ });
+ when(mockAdapter.getItemCount()).thenReturn(100);
+ mRecyclerView.setAdapter(mockAdapter);
+
+ // NOTE: requested cache size must be smaller than span count so two rows cannot fit
+ mRecyclerView.setItemViewCacheSize(2);
+
+ mRecyclerView.measure(View.MeasureSpec.AT_MOST | 300, View.MeasureSpec.AT_MOST | 200);
+ mRecyclerView.layout(0, 0, 300, 150);
+ mRecyclerView.scrollBy(0, 75);
+ assertTrue(mRecycler.mCachedViews.isEmpty());
+
+ // rows 0, 1, and 2 are all attached and visible. Prefetch row 3:
+ mViewPrefetcher.mItemPrefetchArray = new int[] {-1, -1, -1};
+ int viewCount = mRecyclerView.getLayoutManager().gatherPrefetchIndices(0, 1,
+ mRecyclerView.mState, mViewPrefetcher.mItemPrefetchArray);
+ mRecycler.prefetch(mViewPrefetcher.mItemPrefetchArray, viewCount);
+
+ // row 3 is cached:
+ verifyCacheContainsPositions(9, 10, 11);
+ assertTrue(mRecycler.mCachedViews.size() == 3);
+
+ // Scroll so 1 falls off (though 3 is still not on screen)
+ mRecyclerView.scrollBy(0, 50);
+
+ // row 3 is still cached, with a couple other recycled views:
+ verifyCacheContainsPositions(9, 10, 11);
+ assertTrue(mRecycler.mCachedViews.size() == 5);
+ }
+}
\ No newline at end of file