Merge "Tint wrap all Views when running on < v21" into mnc-ub-dev
am: 2aa5f24e18
* commit '2aa5f24e1840309c61b58d10e632e7115c5ea8c1':
Tint wrap all Views when running on < v21
diff --git a/Android.mk b/Android.mk
index f373f85..0d8e2f9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -16,7 +16,7 @@
# Don't include in unbundled build.
ifeq ($(TARGET_BUILD_APPS),)
-SUPPORT_CURRENT_SDK_VERSION := 23
+SUPPORT_CURRENT_SDK_VERSION := current
SUPPORT_API_CHECK := $(LOCAL_PATH)/apicheck.mk
api_check_current_msg_file := $(LOCAL_PATH)/apicheck_msg_current.txt
api_check_last_msg_file := $(LOCAL_PATH)/apicheck_msg_last.txt
diff --git a/annotations/build.gradle b/annotations/build.gradle
index 6fdbc91..61d036b 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -54,6 +54,13 @@
source sourceSets.main.allJava
}
+// Disable strict javadoc lint checks with javadoc v8 builds on Mac. http://b/26744780
+if (JavaVersion.current().isJava8Compatible()) {
+ tasks.withType(Javadoc) {
+ options.addBooleanOption('Xdoclint:none', true)
+ }
+}
+
// custom tasks for creating source/javadoc jars
task sourcesJar(type: Jar, dependsOn:classes) {
classifier = 'sources'
diff --git a/annotations/src/android/support/annotation/AnimRes.java b/annotations/src/android/support/annotation/AnimRes.java
index 906461b..e91fa81 100644
--- a/annotations/src/android/support/annotation/AnimRes.java
+++ b/annotations/src/android/support/annotation/AnimRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an anim resource reference (e.g. {@link android.R.anim#fade_in}).
+ * to be an anim resource reference (e.g. {@code android.R.anim.fade_in}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/AnimatorRes.java b/annotations/src/android/support/annotation/AnimatorRes.java
index 4681236..e969356 100644
--- a/annotations/src/android/support/annotation/AnimatorRes.java
+++ b/annotations/src/android/support/annotation/AnimatorRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an animator resource reference (e.g. {@link android.R.animator#fade_in}).
+ * to be an animator resource reference (e.g. {@code android.R.animator.fade_in}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/ArrayRes.java b/annotations/src/android/support/annotation/ArrayRes.java
index 347de36..758bce4 100644
--- a/annotations/src/android/support/annotation/ArrayRes.java
+++ b/annotations/src/android/support/annotation/ArrayRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an array resource reference (e.g. {@link android.R.array#phoneTypes}).
+ * to be an array resource reference (e.g. {@code android.R.array.phoneTypes}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/AttrRes.java b/annotations/src/android/support/annotation/AttrRes.java
index 7ba1f0d..2034be5 100644
--- a/annotations/src/android/support/annotation/AttrRes.java
+++ b/annotations/src/android/support/annotation/AttrRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an attribute reference (e.g. {@link android.R.attr#action}).
+ * to be an attribute reference (e.g. {@code android.R.attr.action}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/BinderThread.java b/annotations/src/android/support/annotation/BinderThread.java
index 4dc4233..db7dd3a 100644
--- a/annotations/src/android/support/annotation/BinderThread.java
+++ b/annotations/src/android/support/annotation/BinderThread.java
@@ -30,10 +30,10 @@
* on the binder thread.
* <p>
* Example:
- * <pre>{@code
- * (@BinderThread
+ * <pre><code>
+ * @BinderThread
* public BeamShareData createBeamShareData() { ... }
- * }</pre>
+ * </code></pre>
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/CallSuper.java b/annotations/src/android/support/annotation/CallSuper.java
index d321e87..9c01cdf 100644
--- a/annotations/src/android/support/annotation/CallSuper.java
+++ b/annotations/src/android/support/annotation/CallSuper.java
@@ -26,10 +26,10 @@
* Denotes that any overriding methods should invoke this method as well.
* <p>
* Example:
- * <pre>{@code
+ * <pre><code>
* @CallSuper
* public abstract void onFocusLost();
- * }</pre>
+ * </code></pre>
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/CheckResult.java b/annotations/src/android/support/annotation/CheckResult.java
index 2a11dd4..7f40676 100644
--- a/annotations/src/android/support/annotation/CheckResult.java
+++ b/annotations/src/android/support/annotation/CheckResult.java
@@ -30,7 +30,7 @@
* <p>
* Example:
* <pre>{@code
- * public @CheckResult String trim(String s) { return s.trim(); }
+ * public @CheckResult String trim(String s) { return s.trim(); }
* ...
* s.trim(); // this is probably an error
* s = s.trim(); // ok
diff --git a/annotations/src/android/support/annotation/ColorInt.java b/annotations/src/android/support/annotation/ColorInt.java
index 9b3cb7e..4fa46eb 100644
--- a/annotations/src/android/support/annotation/ColorInt.java
+++ b/annotations/src/android/support/annotation/ColorInt.java
@@ -31,7 +31,7 @@
* <p>
* Example:
* <pre>{@code
- * public abstract void setTextColor(@ColorInt int color);
+ * public abstract void setTextColor(@ColorInt int color);
* }</pre>
*/
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/ColorRes.java b/annotations/src/android/support/annotation/ColorRes.java
index eb273c4..d0fd49a 100644
--- a/annotations/src/android/support/annotation/ColorRes.java
+++ b/annotations/src/android/support/annotation/ColorRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a color resource reference (e.g. {@link android.R.color#black}).
+ * to be a color resource reference (e.g. {@code android.R.color.black}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/DimenRes.java b/annotations/src/android/support/annotation/DimenRes.java
index c3492a5..08dc4a9 100644
--- a/annotations/src/android/support/annotation/DimenRes.java
+++ b/annotations/src/android/support/annotation/DimenRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a dimension resource reference (e.g. {@link android.R.dimen#app_icon_size}).
+ * to be a dimension resource reference (e.g. {@code android.R.dimen.app_icon_size}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/DrawableRes.java b/annotations/src/android/support/annotation/DrawableRes.java
index 0ea1bca..f130380 100644
--- a/annotations/src/android/support/annotation/DrawableRes.java
+++ b/annotations/src/android/support/annotation/DrawableRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a drawable resource reference (e.g. {@link android.R.attr#alertDialogIcon}).
+ * to be a drawable resource reference (e.g. {@code android.R.attr.alertDialogIcon}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/FloatRange.java b/annotations/src/android/support/annotation/FloatRange.java
index 2544388..275b5b6 100644
--- a/annotations/src/android/support/annotation/FloatRange.java
+++ b/annotations/src/android/support/annotation/FloatRange.java
@@ -29,12 +29,12 @@
* Denotes that the annotated element should be a float or double in the given range
* <p>
* Example:
- * <pre>{@code
+ * <pre><code>
* @FloatRange(from=0.0,to=1.0)
* public float getAlpha() {
* ...
* }
- * }</pre>
+ * </code></pre>
*/
@Retention(CLASS)
@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
diff --git a/annotations/src/android/support/annotation/IdRes.java b/annotations/src/android/support/annotation/IdRes.java
index 9a0060f..7cb3f98 100644
--- a/annotations/src/android/support/annotation/IdRes.java
+++ b/annotations/src/android/support/annotation/IdRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an id resource reference (e.g. {@link android.R.id#copy}).
+ * to be an id resource reference (e.g. {@code android.R.id.copy}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/IntDef.java b/annotations/src/android/support/annotation/IntDef.java
index fedd7b4..be2e2b8 100644
--- a/annotations/src/android/support/annotation/IntDef.java
+++ b/annotations/src/android/support/annotation/IntDef.java
@@ -32,24 +32,24 @@
* multiple constants can be combined.
* <p>
* Example:
- * <pre>{@code
+ * <pre><code>
* @Retention(SOURCE)
- * @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
- * public @interface NavigationMode {}
+ * @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * public @interface NavigationMode {}
* public static final int NAVIGATION_MODE_STANDARD = 0;
* public static final int NAVIGATION_MODE_LIST = 1;
* public static final int NAVIGATION_MODE_TABS = 2;
* ...
- * public abstract void setNavigationMode(@NavigationMode int mode);
+ * public abstract void setNavigationMode(@NavigationMode int mode);
* @NavigationMode
* public abstract int getNavigationMode();
- * }</pre>
+ * </code></pre>
* For a flag, set the flag attribute:
- * <pre>{@code
+ * <pre><code>
* @IntDef(
* flag = true
- * value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
- * }</pre>
+ * value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * </code></pre>
*/
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
diff --git a/annotations/src/android/support/annotation/IntRange.java b/annotations/src/android/support/annotation/IntRange.java
index 6011276..d489acb 100644
--- a/annotations/src/android/support/annotation/IntRange.java
+++ b/annotations/src/android/support/annotation/IntRange.java
@@ -18,6 +18,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
@@ -28,15 +29,15 @@
* Denotes that the annotated element should be an int or long in the given range
* <p>
* Example:
- * <pre>{@code
+ * <pre><code>
* @IntRange(from=0,to=255)
* public int getAlpha() {
* ...
* }
- * }</pre>
+ * </code></pre>
*/
@Retention(CLASS)
-@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE})
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
public @interface IntRange {
/** Smallest value, inclusive */
long from() default Long.MIN_VALUE;
diff --git a/annotations/src/android/support/annotation/IntegerRes.java b/annotations/src/android/support/annotation/IntegerRes.java
index 6bfcc37..d965675 100644
--- a/annotations/src/android/support/annotation/IntegerRes.java
+++ b/annotations/src/android/support/annotation/IntegerRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an integer resource reference (e.g. {@link android.R.integer#config_shortAnimTime}).
+ * to be an integer resource reference (e.g. {@code android.R.integer.config_shortAnimTime}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/InterpolatorRes.java b/annotations/src/android/support/annotation/InterpolatorRes.java
index 20f42b8..3195c57 100644
--- a/annotations/src/android/support/annotation/InterpolatorRes.java
+++ b/annotations/src/android/support/annotation/InterpolatorRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an interpolator resource reference (e.g. {@link android.R.interpolator#cycle}).
+ * to be an interpolator resource reference (e.g. {@code android.R.interpolator.cycle}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/Keep.java b/annotations/src/android/support/annotation/Keep.java
index de1c9e5..111971c 100644
--- a/annotations/src/android/support/annotation/Keep.java
+++ b/annotations/src/android/support/annotation/Keep.java
@@ -33,12 +33,12 @@
* so a compiler may think that the code is unused.
* <p>
* Example:
- * <pre>{@code
+ * <pre><code>
* @Keep
* public void foo() {
* ...
* }
- * }</pre>
+ * </code></pre>
*/
@Retention(CLASS)
@Target({PACKAGE,TYPE,ANNOTATION_TYPE,CONSTRUCTOR,METHOD,FIELD})
diff --git a/annotations/src/android/support/annotation/LayoutRes.java b/annotations/src/android/support/annotation/LayoutRes.java
index ad04ef0..0de4e84 100644
--- a/annotations/src/android/support/annotation/LayoutRes.java
+++ b/annotations/src/android/support/annotation/LayoutRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a layout resource reference (e.g. {@link android.R.layout#list_content}).
+ * to be a layout resource reference (e.g. {@code android.R.layout.list_content}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/MainThread.java b/annotations/src/android/support/annotation/MainThread.java
index cff464f..1ce8b3c 100644
--- a/annotations/src/android/support/annotation/MainThread.java
+++ b/annotations/src/android/support/annotation/MainThread.java
@@ -30,10 +30,10 @@
* on the main thread.
* <p>
* Example:
- * <pre>{@code
+ * <pre><code>
* @MainThread
* public void deliverResult(D data) { ... }
- * }</pre>
+ * </code></pre>
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/RequiresPermission.java b/annotations/src/android/support/annotation/RequiresPermission.java
index bb9afb2..1ff1964 100644
--- a/annotations/src/android/support/annotation/RequiresPermission.java
+++ b/annotations/src/android/support/annotation/RequiresPermission.java
@@ -22,40 +22,59 @@
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* Denotes that the annotated element requires (or may require) one or more permissions.
- * <p/>
+ * <p>
* Example of requiring a single permission:
- * <pre>{@code
+ * <pre><code>
* @RequiresPermission(Manifest.permission.SET_WALLPAPER)
* public abstract void setWallpaper(Bitmap bitmap) throws IOException;
*
* @RequiresPermission(ACCESS_COARSE_LOCATION)
* public abstract Location getLastKnownLocation(String provider);
- * }</pre>
+ * </code></pre>
* Example of requiring at least one permission from a set:
- * <pre>{@code
+ * <pre><code>
* @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
* public abstract Location getLastKnownLocation(String provider);
- * }</pre>
+ * </code></pre>
* Example of requiring multiple permissions:
- * <pre>{@code
+ * <pre><code>
* @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
* public abstract Location getLastKnownLocation(String provider);
- * }</pre>
+ * </code></pre>
* Example of requiring separate read and write permissions for a content provider:
- * <pre>{@code
- * @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
- * @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
+ * <pre><code>
+ * @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
+ * @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
* public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
+ * </code></pre>
+ * <p>
+ * When specified on a parameter, the annotation indicates that the method requires
+ * a permission which depends on the value of the parameter. For example, consider
+ * {@code android.app.Activity.startActivity(android.content.Intent)}:
+ * <pre>{@code
+ * public void startActivity(@RequiresPermission Intent intent) { ... }
* }</pre>
- *
- * @hide
+ * Notice how there are no actual permission names listed in the annotation. The actual
+ * permissions required will depend on the particular intent passed in. For example,
+ * the code may look like this:
+ * <pre>{@code
+ * Intent intent = new Intent(Intent.ACTION_CALL);
+ * startActivity(intent);
+ * }</pre>
+ * and the actual permission requirement for this particular intent is described on
+ * the Intent name itself:
+ * <pre><code>
+ * @RequiresPermission(Manifest.permission.CALL_PHONE)
+ * public static final String ACTION_CALL = "android.intent.action.CALL";
+ * </code></pre>
*/
@Retention(CLASS)
-@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD})
+@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
public @interface RequiresPermission {
/**
* The name of the permission that is required, if precisely one permission
@@ -87,18 +106,28 @@
boolean conditional() default false;
/**
- * Specifies that the given permission is required for read operations
+ * Specifies that the given permission is required for read operations.
+ * <p>
+ * When specified on a parameter, the annotation indicates that the method requires
+ * a permission which depends on the value of the parameter (and typically
+ * the corresponding field passed in will be one of a set of constants which have
+ * been annotated with a {@code @RequiresPermission} annotation.)
*/
- @Target(FIELD)
+ @Target({FIELD, METHOD, PARAMETER})
@interface Read {
- RequiresPermission value();
+ RequiresPermission value() default @RequiresPermission;
}
/**
- * Specifies that the given permission is required for write operations
+ * Specifies that the given permission is required for write operations.
+ * <p>
+ * When specified on a parameter, the annotation indicates that the method requires
+ * a permission which depends on the value of the parameter (and typically
+ * the corresponding field passed in will be one of a set of constants which have
+ * been annotated with a {@code @RequiresPermission} annotation.)
*/
- @Target(FIELD)
+ @Target({FIELD, METHOD, PARAMETER})
@interface Write {
- RequiresPermission value();
+ RequiresPermission value() default @RequiresPermission;
}
}
diff --git a/annotations/src/android/support/annotation/Size.java b/annotations/src/android/support/annotation/Size.java
index 5903890..b1c0308 100644
--- a/annotations/src/android/support/annotation/Size.java
+++ b/annotations/src/android/support/annotation/Size.java
@@ -32,7 +32,7 @@
* <p>
* Example:
* <pre>{@code
- * public void getLocationInWindow(@Size(2) int[] location) {
+ * public void getLocationInWindow(@Size(2) int[] location) {
* ...
* }
* }</pre>
diff --git a/annotations/src/android/support/annotation/StringDef.java b/annotations/src/android/support/annotation/StringDef.java
index cae227e..33f71e9 100644
--- a/annotations/src/android/support/annotation/StringDef.java
+++ b/annotations/src/android/support/annotation/StringDef.java
@@ -29,20 +29,20 @@
* type and that its value should be one of the explicitly named constants.
* <p>
* Example:
- * <pre>{@code
+ * <pre><code>
* @Retention(SOURCE)
- * @StringDef({
+ * @StringDef({
* POWER_SERVICE,
* WINDOW_SERVICE,
* LAYOUT_INFLATER_SERVICE
- * })
- * public @interface ServiceName {}
+ * })
+ * public @interface ServiceName {}
* public static final String POWER_SERVICE = "power";
* public static final String WINDOW_SERVICE = "window";
* public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
* ...
- * public abstract Object getSystemService(@ServiceName String name);
- * }</pre>
+ * public abstract Object getSystemService(@ServiceName String name);
+ * </code></pre>
*/
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
diff --git a/annotations/src/android/support/annotation/StringRes.java b/annotations/src/android/support/annotation/StringRes.java
index 2c1149c..cf427f6 100644
--- a/annotations/src/android/support/annotation/StringRes.java
+++ b/annotations/src/android/support/annotation/StringRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a String resource reference (e.g. {@link android.R.string#ok}).
+ * to be a String resource reference (e.g. {@code android.R.string.ok}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/StyleRes.java b/annotations/src/android/support/annotation/StyleRes.java
index 6d931bf..ca7a187 100644
--- a/annotations/src/android/support/annotation/StyleRes.java
+++ b/annotations/src/android/support/annotation/StyleRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a style resource reference (e.g. {@link android.R.style#TextAppearance}).
+ * to be a style resource reference (e.g. {@code android.R.style.TextAppearance}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/StyleableRes.java b/annotations/src/android/support/annotation/StyleableRes.java
index d7902d1..6e4d97f 100644
--- a/annotations/src/android/support/annotation/StyleableRes.java
+++ b/annotations/src/android/support/annotation/StyleableRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a styleable resource reference (e.g. {@link android.R.styleable#TextView_text}).
+ * to be a styleable resource reference (e.g. {@code android.R.styleable.TextView_text}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/UiThread.java b/annotations/src/android/support/annotation/UiThread.java
index 98318b4..98d35f9 100644
--- a/annotations/src/android/support/annotation/UiThread.java
+++ b/annotations/src/android/support/annotation/UiThread.java
@@ -30,11 +30,11 @@
* on the UI thread.
* <p>
* Example:
- * <pre>{@code
+ * <pre><code>
* @UiThread
*
- * public abstract void setText(@NonNull String text) { ... }
- * }</pre>
+ * public abstract void setText(@NonNull String text) { ... }
+ * </code></pre>
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/WorkerThread.java b/annotations/src/android/support/annotation/WorkerThread.java
index 2919286..a5aabd7 100644
--- a/annotations/src/android/support/annotation/WorkerThread.java
+++ b/annotations/src/android/support/annotation/WorkerThread.java
@@ -30,10 +30,10 @@
* on a worker thread.
* <p>
* Example:
- * <pre>{@code
- * (@WorkerThread
+ * <pre><code>
+ * @WorkerThread
* protected abstract FilterResults performFiltering(CharSequence constraint);
- * }</pre>
+ * </code></pre>
*/
@Documented
@Retention(CLASS)
diff --git a/apicheck.mk b/apicheck.mk
index 9644f56..7f56866 100644
--- a/apicheck.mk
+++ b/apicheck.mk
@@ -46,8 +46,9 @@
$(call intermediates-dir-for,$(LOCAL_MODULE_CLASS),$(support_module),,COMMON)/src
LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_DROIDDOC_STUB_OUT_DIR := $(TARGET_OUT_COMMON_INTERMEDIATES)/$(LOCAL_MODULE_CLASS)/$(LOCAL_MODULE)_intermediates/src
+
LOCAL_DROIDDOC_OPTIONS:= \
- -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/$(LOCAL_MODULE_CLASS)/$(LOCAL_MODULE)_intermediates/src \
-stubpackages "$(subst $(space),:,$(support_module_java_packages))" \
-api $(support_module_api_file) \
-removedApi $(support_module_removed_file) \
diff --git a/build.gradle b/build.gradle
index 56f0479..14a4c77 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,7 @@
}
}
-ext.supportVersion = '23.2.0-SNAPSHOT'
+ext.supportVersion = '24.0.0-SNAPSHOT'
ext.extraVersion = 25
ext.supportRepoOut = ''
ext.buildToolsVersion = '23.0.2'
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 100150e..17fada5 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -386,19 +386,26 @@
&& mDummyView.getVisibility() == VISIBLE;
if (mDrawCollapsingTitle) {
+ final boolean isRtl = ViewCompat.getLayoutDirection(this)
+ == ViewCompat.LAYOUT_DIRECTION_RTL;
+
+ // Update the collapsed bounds
int bottomOffset = 0;
if (mToolbarDirectChild != null && mToolbarDirectChild != this) {
final LayoutParams lp = (LayoutParams) mToolbarDirectChild.getLayoutParams();
bottomOffset = lp.bottomMargin;
}
ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
- mCollapsingTextHelper.setCollapsedBounds(mTmpRect.left,
- bottom - mTmpRect.height() - bottomOffset,
- mTmpRect.right,
- bottom - bottomOffset);
+ mCollapsingTextHelper.setCollapsedBounds(
+ mTmpRect.left + (isRtl
+ ? mToolbar.getTitleMarginEnd()
+ : mToolbar.getTitleMarginStart()),
+ bottom + mToolbar.getTitleMarginTop() - mTmpRect.height() - bottomOffset,
+ mTmpRect.right + (isRtl
+ ? mToolbar.getTitleMarginStart()
+ : mToolbar.getTitleMarginEnd()),
+ bottom - bottomOffset - mToolbar.getTitleMarginBottom());
- final boolean isRtl = ViewCompat.getLayoutDirection(this)
- == ViewCompat.LAYOUT_DIRECTION_RTL;
// Update the expanded bounds
mCollapsingTextHelper.setExpandedBounds(
isRtl ? mExpandedMarginEnd : mExpandedMarginStart,
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index f5d8797..d891a36 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -410,7 +410,7 @@
// Cancel all behaviors beneath the one that intercepted.
// If the event is "down" then we don't have anything to cancel yet.
if (b != null) {
- if (cancelEvent == null) {
+ if (cancelEvent != null) {
final long now = SystemClock.uptimeMillis();
cancelEvent = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
diff --git a/design/src/android/support/design/widget/Snackbar.java b/design/src/android/support/design/widget/Snackbar.java
index fb9b912..f20e837 100644
--- a/design/src/android/support/design/widget/Snackbar.java
+++ b/design/src/android/support/design/widget/Snackbar.java
@@ -25,6 +25,7 @@
import android.os.Message;
import android.support.annotation.ColorInt;
import android.support.annotation.IntDef;
+import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.design.R;
@@ -121,6 +122,7 @@
* @hide
*/
@IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})
+ @IntRange(from = 1)
@Retention(RetentionPolicy.SOURCE)
public @interface Duration {}
diff --git a/design/tests/AndroidManifest.xml b/design/tests/AndroidManifest.xml
index 4f65f92..dd1bbf0 100755
--- a/design/tests/AndroidManifest.xml
+++ b/design/tests/AndroidManifest.xml
@@ -32,7 +32,7 @@
<activity
android:theme="@style/Theme.AppCompat.NoActionBar"
- android:name="android.support.design.widget.SnackbarBucketTestsActivity"/>
+ android:name="android.support.design.widget.SnackbarActivity"/>
<activity
android:theme="@style/Theme.AppCompat.NoActionBar"
diff --git a/design/tests/src/android/support/design/widget/BaseTestActivity.java b/design/tests/src/android/support/design/widget/BaseTestActivity.java
index 164a191..0c801cb 100755
--- a/design/tests/src/android/support/design/widget/BaseTestActivity.java
+++ b/design/tests/src/android/support/design/widget/BaseTestActivity.java
@@ -27,6 +27,8 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ overridePendingTransition(0, 0);
+
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
final int contentView = getContentViewLayoutResId();
if (contentView > 0) {
@@ -36,6 +38,12 @@
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
+
protected abstract int getContentViewLayoutResId();
protected void onContentViewSet() {}
diff --git a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java b/design/tests/src/android/support/design/widget/SnackbarActivity.java
similarity index 76%
rename from design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
rename to design/tests/src/android/support/design/widget/SnackbarActivity.java
index 124d683..5d8e118 100644
--- a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
+++ b/design/tests/src/android/support/design/widget/SnackbarActivity.java
@@ -18,17 +18,9 @@
import android.support.design.test.R;
-public class SnackbarBucketTestsActivity extends BaseTestActivity {
-
- CoordinatorLayout mCoordinatorLayout;
-
+public class SnackbarActivity extends BaseTestActivity {
@Override
protected int getContentViewLayoutResId() {
return R.layout.test_design_snackbar;
}
-
- @Override
- protected void onContentViewSet() {
- mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.col);
- }
}
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/widget/SnackbarBucketTests.java b/design/tests/src/android/support/design/widget/SnackbarBucketTests.java
deleted file mode 100644
index db8d33a..0000000
--- a/design/tests/src/android/support/design/widget/SnackbarBucketTests.java
+++ /dev/null
@@ -1,126 +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.design.widget;
-
-import android.os.SystemClock;
-import android.support.design.test.R;
-import android.support.test.espresso.ViewAction;
-import android.support.test.espresso.ViewInteraction;
-import android.support.test.espresso.action.ViewActions;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.view.View;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import static android.support.test.espresso.Espresso.onView;
-import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
-import static android.support.test.espresso.matcher.ViewMatchers.withId;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public class SnackbarBucketTests extends BaseInstrumentationTestCase<SnackbarBucketTestsActivity> {
-
- private static final String MESSAGE_TEXT = "Test Message";
- private static final String ACTION_TEXT = "Action";
-
- public SnackbarBucketTests() {
- super(SnackbarBucketTestsActivity.class);
- }
-
- @Test
- @MediumTest
- public void testActionClickDismiss() {
- testDismissCallback(
- onView(withId(R.id.snackbar_action)),
- ViewActions.click(),
- Snackbar.LENGTH_LONG,
- Snackbar.Callback.DISMISS_EVENT_ACTION);
- }
-
- @Test
- @MediumTest
- public void testSwipeDismissCallback() {
- testDismissCallback(
- onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
- ViewActions.swipeRight(),
- Snackbar.LENGTH_LONG,
- Snackbar.Callback.DISMISS_EVENT_SWIPE);
- }
-
- @Test
- @MediumTest
- public void testActionClickListener() {
- final AtomicBoolean clicked = new AtomicBoolean();
-
- final Snackbar snackbar = Snackbar.make(getCoordinatorLayout(), MESSAGE_TEXT,
- Snackbar.LENGTH_SHORT)
- .setAction(ACTION_TEXT, new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- clicked.set(true);
- }
- });
- // Now show the Snackbar
- snackbar.show();
- // Sleep for the animation
- SystemClock.sleep(Snackbar.ANIMATION_DURATION + 50);
- // Perform the action click
- onView(withId(R.id.snackbar_action)).perform(ViewActions.click());
-
- assertTrue("Action click listener called", clicked.get());
- }
-
- private void testDismissCallback(final ViewInteraction interaction, final ViewAction action,
- final int length, @Snackbar.Callback.DismissEvent final int expectedEvent) {
- final ArrayList<Integer> dismissEvents = new ArrayList<>();
-
- final Snackbar snackbar = Snackbar.make(getCoordinatorLayout(), MESSAGE_TEXT, length)
- .setAction(ACTION_TEXT, new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- // no-op
- }
- })
- .setCallback(new Snackbar.Callback() {
- @Override
- public void onDismissed(Snackbar snackbar, @DismissEvent int event) {
- dismissEvents.add(event);
- }
- });
-
- // Now show the Snackbar
- snackbar.show();
- // Sleep for the animation
- SystemClock.sleep(Snackbar.ANIMATION_DURATION + 50);
- // ...and the perform the UI interaction
- interaction.perform(action);
- // Now wait until the Snackbar has been removed from the view hierarchy
- while (snackbar.isShownOrQueued()) {
- SystemClock.sleep(20);
- }
-
- assertTrue("onDismissed has not been called once only. Events: " + dismissEvents,
- dismissEvents.size() == 1);
- assertEquals(expectedEvent, (int) dismissEvents.get(0));
- }
-
- private CoordinatorLayout getCoordinatorLayout() {
- return mActivityTestRule.getActivity().mCoordinatorLayout;
- }
-}
diff --git a/design/tests/src/android/support/design/widget/SnackbarTest.java b/design/tests/src/android/support/design/widget/SnackbarTest.java
new file mode 100644
index 0000000..9f429a7
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/SnackbarTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.design.widget;
+
+import android.os.SystemClock;
+import android.support.design.test.R;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.ViewInteraction;
+import android.support.test.espresso.action.ViewActions;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+public class SnackbarTest extends BaseInstrumentationTestCase<SnackbarActivity> {
+ private static final String MESSAGE_TEXT = "Test Message";
+ private static final String ACTION_TEXT = "Action";
+
+ private CoordinatorLayout mCoordinatorLayout;
+
+ public SnackbarTest() {
+ super(SnackbarActivity.class);
+ }
+
+ @Before
+ public void setup() {
+ mCoordinatorLayout =
+ (CoordinatorLayout) mActivityTestRule.getActivity().findViewById(R.id.col);
+ }
+
+ private void verifyDismissCallback(final ViewInteraction interaction, final ViewAction action,
+ final int length, @Snackbar.Callback.DismissEvent final int expectedEvent) {
+ final Snackbar.Callback mockCallback = mock(Snackbar.Callback.class);
+ final Snackbar snackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, length)
+ .setAction(ACTION_TEXT, mock(View.OnClickListener.class))
+ .setCallback(mockCallback);
+
+ // Now show the Snackbar
+ snackbar.show();
+ // Sleep for the animation
+ SystemClock.sleep(Snackbar.ANIMATION_DURATION + 50);
+ // ...and perform the UI interaction
+ interaction.perform(action);
+ // Now wait until the Snackbar has been removed from the view hierarchy
+ while (snackbar.isShownOrQueued()) {
+ SystemClock.sleep(20);
+ }
+
+ verify(mockCallback, times(1)).onShown(snackbar);
+ verify(mockCallback, times(1)).onDismissed(snackbar, expectedEvent);
+ verifyNoMoreInteractions(mockCallback);
+ }
+
+ @Test
+ @MediumTest
+ public void testActionClickDismiss() {
+ verifyDismissCallback(
+ onView(withId(R.id.snackbar_action)),
+ ViewActions.click(),
+ Snackbar.LENGTH_LONG,
+ Snackbar.Callback.DISMISS_EVENT_ACTION);
+ }
+
+ @Test
+ @MediumTest
+ public void testSwipeDismissCallback() {
+ verifyDismissCallback(
+ onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+ ViewActions.swipeRight(),
+ Snackbar.LENGTH_LONG,
+ Snackbar.Callback.DISMISS_EVENT_SWIPE);
+ }
+
+ @Test
+ @MediumTest
+ public void testActionClickListener() {
+ final View.OnClickListener mockClickListener = mock(View.OnClickListener.class);
+ final Snackbar snackbar =
+ Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT)
+ .setAction(ACTION_TEXT, mockClickListener);
+ // Now show the Snackbar
+ snackbar.show();
+ // Sleep for the animation
+ SystemClock.sleep(Snackbar.ANIMATION_DURATION + 50);
+ // Perform the action click
+ onView(withId(R.id.snackbar_action)).perform(ViewActions.click());
+
+ verify(mockClickListener, times(1)).onClick(any(View.class));
+ }
+}
diff --git a/documents-archive/Android.mk b/documents-archive/Android.mk
new file mode 100644
index 0000000..0c7a819
--- /dev/null
+++ b/documents-archive/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2015 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Here is the final static library that apps can link against.
+# The R class is automatically excluded from the generated library.
+# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# in their makefiles to include the resources in their package.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-documents-archive
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_AIDL_INCLUDES := $LOCAL_PATH/src
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_LIBRARIES := android-support-annotations \
+ android-support-v4
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/appcompat/res/values-h320dp/bools.xml b/documents-archive/AndroidManifest.xml
similarity index 77%
rename from v7/appcompat/res/values-h320dp/bools.xml
rename to documents-archive/AndroidManifest.xml
index 5576c18..2cd0f7a 100644
--- a/v7/appcompat/res/values-h320dp/bools.xml
+++ b/documents-archive/AndroidManifest.xml
@@ -13,7 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<resources>
- <bool name="abc_allow_stacked_button_bar">true</bool>
-</resources>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.support.provider">
+ <uses-sdk android:minSdkVersion="19"/>
+ <application />
+</manifest>
diff --git a/documents-archive/api/current.txt b/documents-archive/api/current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/documents-archive/api/current.txt
diff --git a/documents-archive/api/removed.txt b/documents-archive/api/removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/documents-archive/api/removed.txt
diff --git a/documents-archive/src/android/support/provider/DocumentArchive.java b/documents-archive/src/android/support/provider/DocumentArchive.java
new file mode 100644
index 0000000..1182b0d
--- /dev/null
+++ b/documents-archive/src/android/support/provider/DocumentArchive.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2015 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.provider;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.OperationCanceledException;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsProvider;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+import java.lang.UnsupportedOperationException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Provides basic implementation for creating, extracting and accessing
+ * files within archives exposed by a document provider. The id delimiter
+ * must be a character which is not used in document ids generated by the
+ * document provider.
+ *
+ * <p>This class is thread safe.
+ *
+ * @hide
+ */
+public class DocumentArchive implements Closeable {
+ private static final String TAG = "DocumentArchive";
+
+ private static final String[] DEFAULT_PROJECTION = new String[] {
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_SIZE,
+ Document.COLUMN_FLAGS
+ };
+
+ private final Context mContext;
+ private final String mDocumentId;
+ private final char mIdDelimiter;
+ private final Uri mNotificationUri;
+ private final ZipFile mZipFile;
+ private final ExecutorService mExecutor;
+ private final Map<String, List<ZipEntry>> mTree;
+
+ private DocumentArchive(
+ Context context,
+ File file,
+ String documentId,
+ char idDelimiter,
+ @Nullable Uri notificationUri)
+ throws IOException {
+ mContext = context;
+ mDocumentId = documentId;
+ mIdDelimiter = idDelimiter;
+ mNotificationUri = notificationUri;
+ mZipFile = new ZipFile(file);
+ mExecutor = Executors.newSingleThreadExecutor();
+
+ // Build the tree structure in memory.
+ mTree = new HashMap<String, List<ZipEntry>>();
+ mTree.put("/", new ArrayList<ZipEntry>());
+
+ // Traversing entries via ZipFile.entries() is very slow, so copy the entries to a temporary
+ // map for building the tree.
+ final Map<String, ZipEntry> entries = new HashMap<String, ZipEntry>();
+ for (final ZipEntry entry : Collections.list(mZipFile.entries())) {
+ entries.put(entry.getName(), entry);
+ if (entry.isDirectory()) {
+ mTree.put(entry.getName(), new ArrayList<ZipEntry>());
+ }
+ }
+
+ int delimiterIndex;
+ List<ZipEntry> parentList;
+ String parentPath;
+ for (final ZipEntry entry : entries.values()) {
+ delimiterIndex = entry.getName().lastIndexOf('/', entry.isDirectory()
+ ? entry.getName().length() - 2 : entry.getName().length() - 1);
+ parentPath =
+ delimiterIndex != -1 ? entry.getName().substring(0, delimiterIndex) + "/" : "/";
+ parentList = mTree.get(parentPath);
+ if (parentList != null) {
+ parentList.add(entry);
+ } else {
+ Log.w(TAG, "Archived files without a parent are not supported: " + entry.getName());
+ }
+ }
+ }
+
+ /**
+ * Creates a DocumentsArchive instance for opening, browsing and accessing
+ * documents within the archive passed as a local file.
+ *
+ * @param context Context of the provider.
+ * @param File Local file containing the archive.
+ * @param documentId ID of the archive document.
+ * @param idDelimiter Delimiter for constructing IDs of documents within the archive.
+ * The delimiter must never be used for IDs of other documents.
+ * @param Uri notificationUri Uri for notifying that the archive file has changed.
+ * @see createForParcelFileDescriptor(DocumentsProvider, ParcelFileDescriptor, String, char,
+ * Uri)
+ */
+ public static DocumentArchive createForLocalFile(
+ Context context, File file, String documentId, char idDelimiter,
+ @Nullable Uri notificationUri)
+ throws IOException {
+ return new DocumentArchive(context, file, documentId, idDelimiter, notificationUri);
+ }
+
+ /**
+ * Creates a DocumentsArchive instance for opening, browsing and accessing
+ * documents within the archive passed as a file descriptor.
+ *
+ * <p>Note, that this method should be used only if the document does not exist
+ * on the local storage. A snapshot file will be created, which may be slower
+ * and consume significant resources, in contrast to using
+ * {@see createForLocalFile(Context, File, String, char, Uri}.
+ *
+ * @param context Context of the provider.
+ * @param descriptor File descriptor for the archive's contents.
+ * @param documentId ID of the archive document.
+ * @param idDelimiter Delimiter for constructing IDs of documents within the archive.
+ * The delimiter must never be used for IDs of other documents.
+ * @param Uri notificationUri Uri for notifying that the archive file has changed.
+ * @see createForLocalFile(Context, File, String, char, Uri)
+ */
+ public static DocumentArchive createForParcelFileDescriptor(
+ Context context, ParcelFileDescriptor descriptor, String documentId,
+ char idDelimiter, @Nullable Uri notificationUri)
+ throws IOException {
+ File snapshotFile = null;
+ try {
+ // Create a copy of the archive, as ZipFile doesn't operate on streams.
+ // Moreover, ZipInputStream would be inefficient for large files on
+ // pipes.
+ snapshotFile = File.createTempFile("android.support.provider.snapshot{",
+ "}.zip", context.getCacheDir());
+
+ try (
+ final FileOutputStream outputStream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(
+ ParcelFileDescriptor.open(
+ snapshotFile, ParcelFileDescriptor.MODE_WRITE_ONLY));
+ final ParcelFileDescriptor.AutoCloseInputStream inputStream =
+ new ParcelFileDescriptor.AutoCloseInputStream(descriptor);
+ ) {
+ final byte[] buffer = new byte[32 * 1024];
+ int bytes;
+ while ((bytes = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, bytes);
+ }
+ outputStream.flush();
+ return new DocumentArchive(context, snapshotFile, documentId, idDelimiter,
+ notificationUri);
+ }
+ } finally {
+ // On UNIX the file will be still available for processes which opened it, even
+ // after deleting it. Remove it ASAP, as it won't be used by anyone else.
+ if (snapshotFile != null) {
+ snapshotFile.delete();
+ }
+ }
+ }
+
+ /**
+ * Lists child documents of an archive or a directory within an
+ * archive. Must be called only for archives with supported mime type,
+ * or for documents within archives.
+ *
+ * @see DocumentsProvider.queryChildDocuments(String, String[], String)
+ */
+ public Cursor queryChildDocuments(String documentId, @Nullable String[] projection,
+ @Nullable String sortOrder) throws FileNotFoundException {
+ final ParsedDocumentId parsedParentId = ParsedDocumentId.fromDocumentId(
+ documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedParentId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+
+ final String parentPath = parsedParentId.mPath != null ? parsedParentId.mPath : "/";
+ final MatrixCursor result = new MatrixCursor(
+ projection != null ? projection : DEFAULT_PROJECTION);
+ if (mNotificationUri != null) {
+ result.setNotificationUri(mContext.getContentResolver(), mNotificationUri);
+ }
+
+ final List<ZipEntry> parentList = mTree.get(parentPath);
+ if (parentList == null) {
+ throw new FileNotFoundException();
+ }
+ for (final ZipEntry entry : parentList) {
+ addCursorRow(result, entry);
+ }
+ return result;
+ }
+
+ /**
+ * Returns a MIME type of a document within an archive.
+ *
+ * @see DocumentsProvider.getDocumentType(String)
+ */
+ public String getDocumentType(String documentId) throws FileNotFoundException {
+ final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+ documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+ Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+
+ final ZipEntry entry = mZipFile.getEntry(parsedId.mPath);
+ if (entry == null) {
+ throw new FileNotFoundException();
+ }
+ return getMimeTypeForEntry(entry);
+ }
+
+ /**
+ * Returns true if a document within an archive is a child or any descendant of the archive
+ * document or another document within the archive.
+ *
+ * @see DocumentsProvider.isChildDocument(String, String)
+ */
+ public boolean isChildDocument(String parentDocumentId, String documentId) {
+ final ParsedDocumentId parsedParentId = ParsedDocumentId.fromDocumentId(
+ parentDocumentId, mIdDelimiter);
+ final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+ documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedParentId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+ Preconditions.checkArgumentNotNull(parsedId.mPath,
+ "Not a document within an archive.");
+
+ final ZipEntry entry = mZipFile.getEntry(parsedId.mPath);
+ if (entry == null) {
+ return false;
+ }
+
+ if (parsedParentId.mPath == null) {
+ // No need to compare paths. Every file in the archive is a child of the archive
+ // file.
+ return true;
+ }
+
+ final ZipEntry parentEntry = mZipFile.getEntry(parsedParentId.mPath);
+ if (parentEntry == null || !parentEntry.isDirectory()) {
+ return false;
+ }
+
+ final String parentPath = entry.getName();
+
+ // Add a trailing slash even if it's not a directory, so it's easy to check if the
+ // entry is a descendant.
+ final String pathWithSlash = entry.isDirectory() ? entry.getName() : entry.getName() + "/";
+ return pathWithSlash.startsWith(parentPath) && !parentPath.equals(pathWithSlash);
+ }
+
+ /**
+ * Returns metadata of a document within an archive.
+ *
+ * @see DocumentsProvider.queryDocument(String, String[])
+ */
+ public Cursor queryDocument(String documentId, @Nullable String[] projection)
+ throws FileNotFoundException {
+ final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+ documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+ Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+
+ final ZipEntry entry = mZipFile.getEntry(parsedId.mPath);
+ if (entry == null) {
+ throw new FileNotFoundException();
+ }
+
+ final MatrixCursor result = new MatrixCursor(
+ projection != null ? projection : DEFAULT_PROJECTION);
+ if (mNotificationUri != null) {
+ result.setNotificationUri(mContext.getContentResolver(), mNotificationUri);
+ }
+ addCursorRow(result, entry);
+ return result;
+ }
+
+ /**
+ * Opens a file within an archive.
+ *
+ * @see DocumentsProvider.openDocument(String, String, CancellationSignal))
+ */
+ public ParcelFileDescriptor openDocument(
+ String documentId, String mode, @Nullable final CancellationSignal signal)
+ throws FileNotFoundException {
+ Preconditions.checkArgumentEquals("r", mode,
+ "Invalid mode. Only reading \"r\" supported, but got: \"%s\".");
+ final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+ documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+ Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+
+ final ZipEntry entry = mZipFile.getEntry(parsedId.mPath);
+ if (entry == null) {
+ throw new FileNotFoundException();
+ }
+
+ ParcelFileDescriptor[] pipe;
+ InputStream inputStream = null;
+ try {
+ pipe = ParcelFileDescriptor.createReliablePipe();
+ inputStream = mZipFile.getInputStream(entry);
+ } catch (IOException e) {
+ if (inputStream != null) {
+ IoUtils.closeQuietly(inputStream);
+ }
+ // Ideally we'd simply throw IOException to the caller, but for consistency
+ // with DocumentsProvider::openDocument, converting it to IllegalStateException.
+ throw new IllegalStateException("Failed to open the document.", e);
+ }
+ final ParcelFileDescriptor outputPipe = pipe[1];
+ final InputStream finalInputStream = inputStream;
+ mExecutor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ try (final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(outputPipe)) {
+ try {
+ final byte buffer[] = new byte[32 * 1024];
+ int bytes;
+ while ((bytes = finalInputStream.read(buffer)) != -1) {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
+ if (signal != null) {
+ signal.throwIfCanceled();
+ }
+ outputStream.write(buffer, 0, bytes);
+ }
+ } catch (IOException | InterruptedException e) {
+ // Catch the exception before the outer try-with-resource closes the
+ // pipe with close() instead of closeWithError().
+ try {
+ outputPipe.closeWithError(e.getMessage());
+ } catch (IOException e2) {
+ Log.e(TAG, "Failed to close the pipe after an error.", e2);
+ }
+ }
+ } catch (OperationCanceledException e) {
+ // Cancelled gracefully.
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close the output stream gracefully.", e);
+ } finally {
+ IoUtils.closeQuietly(finalInputStream);
+ }
+ }
+ });
+
+ return pipe[0];
+ }
+
+ /**
+ * Opens a thumbnail of a file within an archive.
+ *
+ * @see DocumentsProvider.openDocumentThumbnail(String, Point, CancellationSignal))
+ */
+ public AssetFileDescriptor openDocumentThumbnail(
+ String documentId, Point sizeHint, final CancellationSignal signal)
+ throws FileNotFoundException {
+ final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+ Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+ Preconditions.checkArgument(getDocumentType(documentId).startsWith("image/"),
+ "Thumbnails only supported for image/* MIME type.");
+
+ // TODO: Extract thumbnails from EXIF.
+ final ZipEntry entry = mZipFile.getEntry(parsedId.mPath);
+ if (entry == null) {
+ throw new FileNotFoundException();
+ }
+
+ return new AssetFileDescriptor(
+ openDocument(documentId, "r", signal), 0, entry.getSize(), null);
+ }
+
+ /**
+ * Schedules a gracefully close of the archive after any opened files are closed.
+ *
+ * <p>This method does not block until shutdown. Once called, other methods should not be
+ * called.
+ */
+ @Override
+ public void close() {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ IoUtils.closeQuietly(mZipFile);
+ }
+ });
+ mExecutor.shutdown();
+ }
+
+ private void addCursorRow(MatrixCursor cursor, ZipEntry entry) {
+ final MatrixCursor.RowBuilder row = cursor.newRow();
+ final ParsedDocumentId parsedId = new ParsedDocumentId(mDocumentId, entry.getName());
+ row.add(Document.COLUMN_DOCUMENT_ID, parsedId.toDocumentId(mIdDelimiter));
+
+ final File file = new File(entry.getName());
+ row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
+ row.add(Document.COLUMN_SIZE, entry.getSize());
+
+ final String mimeType = getMimeTypeForEntry(entry);
+ row.add(Document.COLUMN_MIME_TYPE, mimeType);
+
+ final int flags = mimeType.startsWith("image/") ? Document.FLAG_SUPPORTS_THUMBNAIL : 0;
+ row.add(Document.COLUMN_FLAGS, flags);
+ }
+
+ private String getMimeTypeForEntry(ZipEntry entry) {
+ if (entry.isDirectory()) {
+ return Document.MIME_TYPE_DIR;
+ }
+
+ final int lastDot = entry.getName().lastIndexOf('.');
+ if (lastDot >= 0) {
+ final String extension = entry.getName().substring(lastDot + 1).toLowerCase();
+ final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (mimeType != null) {
+ return mimeType;
+ }
+ }
+
+ return "application/octet-stream";
+ }
+};
diff --git a/documents-archive/src/android/support/provider/DocumentArchiveHelper.java b/documents-archive/src/android/support/provider/DocumentArchiveHelper.java
new file mode 100644
index 0000000..6cb4218
--- /dev/null
+++ b/documents-archive/src/android/support/provider/DocumentArchiveHelper.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2015 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.provider;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsProvider;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.util.LruCache;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Provides basic implementation for creating, extracting and accessing
+ * files within archives exposed by a document provider.
+ *
+ * <p>This class is thread safe. All methods can be called on any thread without
+ * synchronization.
+ *
+ * TODO: Update the documentation. b/26047732
+ * @hide
+ */
+public class DocumentArchiveHelper implements Closeable {
+ /**
+ * Cursor column to be used for passing the local file path for documents.
+ * If it's not specified, then a snapshot will be created, which is slower
+ * and consumes more resources.
+ *
+ * <p>Type: STRING
+ */
+ public static final String COLUMN_LOCAL_FILE_PATH = "local_file_path";
+
+ private static final String TAG = "DocumentArchiveHelper";
+ private static final int OPENED_ARCHIVES_CACHE_SIZE = 4;
+ private static final String[] ZIP_MIME_TYPES = {
+ "application/zip", "application/x-zip", "application/x-zip-compressed"
+ };
+
+ private final DocumentsProvider mProvider;
+ private final char mIdDelimiter;
+
+ // @GuardedBy("mArchives")
+ private final LruCache<String, Loader> mArchives =
+ new LruCache<String, Loader>(OPENED_ARCHIVES_CACHE_SIZE) {
+ @Override
+ public void entryRemoved(boolean evicted, String key,
+ Loader oldValue, Loader newValue) {
+ oldValue.getWriteLock().lock();
+ try {
+ oldValue.get().close();
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Failed to close an archive as it no longer exists.");
+ } finally {
+ oldValue.getWriteLock().unlock();
+ }
+ }
+ };
+
+ /**
+ * Creates a helper for handling archived documents.
+ *
+ * @param provider Instance of a documents provider which provides archived documents.
+ * @param idDelimiter A character used to create document IDs within archives. Can be any
+ * character which is not used in any other document ID. If your provider uses
+ * numbers as document IDs, the delimiter can be eg. a colon. However if your
+ * provider uses paths, then a delimiter can be any character not allowed in the
+ * path, which is often \0.
+ */
+ public DocumentArchiveHelper(DocumentsProvider provider, char idDelimiter) {
+ mProvider = provider;
+ mIdDelimiter = idDelimiter;
+ }
+
+ /**
+ * Lists child documents of an archive or a directory within an
+ * archive. Must be called only for archives with supported mime type,
+ * or for documents within archives.
+ *
+ * @see DocumentsProvider.queryChildDocuments(String, String[], String)
+ */
+ public Cursor queryChildDocuments(String documentId, @Nullable String[] projection,
+ @Nullable String sortOrder)
+ throws FileNotFoundException {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().queryChildDocuments(documentId, projection, sortOrder);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Returns a MIME type of a document within an archive.
+ *
+ * @see DocumentsProvider.getDocumentType(String)
+ */
+ public String getDocumentType(String documentId) throws FileNotFoundException {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().getDocumentType(documentId);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Returns true if a document within an archive is a child or any descendant of the archive
+ * document or another document within the archive.
+ *
+ * @see DocumentsProvider.isChildDocument(String, String)
+ */
+ public boolean isChildDocument(String parentDocumentId, String documentId) {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().isChildDocument(parentDocumentId, documentId);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Returns metadata of a document within an archive.
+ *
+ * @see DocumentsProvider.queryDocument(String, String[])
+ */
+ public Cursor queryDocument(String documentId, @Nullable String[] projection)
+ throws FileNotFoundException {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().queryDocument(documentId, projection);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Opens a file within an archive.
+ *
+ * @see DocumentsProvider.openDocument(String, String, CancellationSignal))
+ */
+ public ParcelFileDescriptor openDocument(
+ String documentId, String mode, final CancellationSignal signal)
+ throws FileNotFoundException {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().openDocument(documentId, mode, signal);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Opens a thumbnail of a file within an archive.
+ *
+ * @see DocumentsProvider.openDocumentThumbnail(String, Point, CancellationSignal))
+ */
+ public AssetFileDescriptor openDocumentThumbnail(
+ String documentId, Point sizeHint, final CancellationSignal signal)
+ throws FileNotFoundException {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().openDocumentThumbnail(documentId, sizeHint, signal);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Returns true if the passed document ID is for a document within an archive.
+ */
+ public boolean isArchivedDocument(String documentId) {
+ return ParsedDocumentId.hasPath(documentId, mIdDelimiter);
+ }
+
+ /**
+ * Returns true if the passed mime type is supported by the helper.
+ */
+ public boolean isSupportedArchiveType(String mimeType) {
+ for (final String zipMimeType : ZIP_MIME_TYPES) {
+ if (zipMimeType.equals(mimeType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Closes the helper and disposes all existing archives. It will block until all ongoing
+ * operations on each opened archive are finished.
+ */
+ @Override
+ public void close() {
+ synchronized (mArchives) {
+ mArchives.evictAll();
+ }
+ }
+
+ /**
+ * Releases resources for an archive with the specified document ID. It will block until all
+ * operations on the archive are finished. If not opened, the method does nothing.
+ *
+ * <p>Calling this method is optional. The helper automatically closes the least recently used
+ * archives if too many archives are opened.
+ *
+ * @param archiveDocumentId ID of the archive file.
+ */
+ public void closeArchive(String documentId) {
+ synchronized (mArchives) {
+ mArchives.remove(documentId);
+ }
+ }
+
+ private Loader obtainInstance(String documentId) throws FileNotFoundException {
+ Loader loader;
+ synchronized (mArchives) {
+ loader = getInstanceUncheckedLocked(documentId);
+ loader.getReadLock().lock();
+ }
+ return loader;
+ }
+
+ private void releaseInstance(@Nullable Loader loader) {
+ if (loader != null) {
+ loader.getReadLock().unlock();
+ }
+ }
+
+ private Loader getInstanceUncheckedLocked(String documentId)
+ throws FileNotFoundException {
+ try {
+ final ParsedDocumentId id = ParsedDocumentId.fromDocumentId(documentId, mIdDelimiter);
+ if (mArchives.get(id.mArchiveId) != null) {
+ return mArchives.get(id.mArchiveId);
+ }
+
+ final Cursor cursor = mProvider.queryDocument(id.mArchiveId, new String[]
+ { Document.COLUMN_MIME_TYPE, COLUMN_LOCAL_FILE_PATH });
+ cursor.moveToFirst();
+ final String mimeType = cursor.getString(cursor.getColumnIndex(
+ Document.COLUMN_MIME_TYPE));
+ Preconditions.checkArgument(isSupportedArchiveType(mimeType),
+ "Unsupported archive type.");
+ final int columnIndex = cursor.getColumnIndex(COLUMN_LOCAL_FILE_PATH);
+ final String localFilePath = columnIndex != -1 ? cursor.getString(columnIndex) : null;
+ final File localFile = localFilePath != null ? new File(localFilePath) : null;
+ final Uri notificationUri = cursor.getNotificationUri();
+ final Loader loader = new Loader(mProvider, localFile, id, mIdDelimiter,
+ notificationUri);
+
+ // Remove the instance from mArchives collection once the archive file changes.
+ if (notificationUri != null) {
+ final LruCache<String, Loader> finalArchives = mArchives;
+ mProvider.getContext().getContentResolver().registerContentObserver(notificationUri,
+ false,
+ new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ synchronized (mArchives) {
+ final Loader currentLoader = mArchives.get(id.mArchiveId);
+ if (currentLoader == loader) {
+ mArchives.remove(id.mArchiveId);
+ }
+ }
+ }
+ });
+ }
+
+ mArchives.put(id.mArchiveId, loader);
+ return loader;
+ } catch (IOException e) {
+ // DocumentsProvider doesn't use IOException. For consistency convert it to
+ // IllegalStateException.
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Loads an instance of DocumentArchive lazily.
+ */
+ private static final class Loader {
+ private final DocumentsProvider mProvider;
+ private final File mLocalFile;
+ private final ParsedDocumentId mId;
+ private final char mIdDelimiter;
+ private final Uri mNotificationUri;
+ private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+ private DocumentArchive mArchive = null;
+
+ Loader(DocumentsProvider provider, @Nullable File localFile, ParsedDocumentId id,
+ char idDelimiter, Uri notificationUri) {
+ this.mProvider = provider;
+ this.mLocalFile = localFile;
+ this.mId = id;
+ this.mIdDelimiter = idDelimiter;
+ this.mNotificationUri = notificationUri;
+ }
+
+ synchronized DocumentArchive get() throws FileNotFoundException {
+ if (mArchive != null) {
+ return mArchive;
+ }
+
+ try {
+ if (mLocalFile != null) {
+ mArchive = DocumentArchive.createForLocalFile(
+ mProvider.getContext(), mLocalFile, mId.mArchiveId, mIdDelimiter,
+ mNotificationUri);
+ } else {
+ mArchive = DocumentArchive.createForParcelFileDescriptor(
+ mProvider.getContext(),
+ mProvider.openDocument(mId.mArchiveId, "r", null /* signal */),
+ mId.mArchiveId, mIdDelimiter, mNotificationUri);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
+ return mArchive;
+ }
+
+ Lock getReadLock() {
+ return mLock.readLock();
+ }
+
+ Lock getWriteLock() {
+ return mLock.writeLock();
+ }
+ }
+}
diff --git a/documents-archive/src/android/support/provider/IoUtils.java b/documents-archive/src/android/support/provider/IoUtils.java
new file mode 100644
index 0000000..61047dc
--- /dev/null
+++ b/documents-archive/src/android/support/provider/IoUtils.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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.provider;
+
+import android.support.annotation.Nullable;
+
+import java.io.Closeable;
+import java.io.InputStream;
+import java.util.Collection;
+
+/**
+ * Simple static methods to perform common IO operations.
+ * @hide
+ */
+final class IoUtils {
+ static void closeQuietly(@Nullable Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ // Ignore.
+ }
+ }
+ }
+
+ static void closeQuietly(@Nullable InputStream stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ // Ignore.
+ }
+ }
+ }
+}
diff --git a/documents-archive/src/android/support/provider/ParsedDocumentId.java b/documents-archive/src/android/support/provider/ParsedDocumentId.java
new file mode 100644
index 0000000..ee2c366
--- /dev/null
+++ b/documents-archive/src/android/support/provider/ParsedDocumentId.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 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.provider;
+
+/**
+ * @hide
+ */
+class ParsedDocumentId {
+ public final String mArchiveId;
+ public final String mPath;
+
+ public ParsedDocumentId(String archiveId, String path) {
+ mArchiveId = archiveId;
+ mPath = path;
+ }
+
+ static public ParsedDocumentId fromDocumentId(String documentId, char idDelimiter) {
+ final int delimiterPosition = documentId.indexOf(idDelimiter);
+ if (delimiterPosition == -1) {
+ return new ParsedDocumentId(documentId, null);
+ } else {
+ return new ParsedDocumentId(documentId.substring(0, delimiterPosition),
+ documentId.substring((delimiterPosition + 1)));
+ }
+ }
+
+ static public boolean hasPath(String documentId, char idDelimiter) {
+ return documentId.indexOf(idDelimiter) != -1;
+ }
+
+ public String toDocumentId(char idDelimiter) {
+ if (mPath == null) {
+ return mArchiveId;
+ } else {
+ return mArchiveId + idDelimiter + mPath;
+ }
+ }
+};
diff --git a/documents-archive/src/android/support/provider/Preconditions.java b/documents-archive/src/android/support/provider/Preconditions.java
new file mode 100644
index 0000000..fd62a2e
--- /dev/null
+++ b/documents-archive/src/android/support/provider/Preconditions.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 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.provider;
+
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.util.Collection;
+
+/**
+ * Simple static methods to be called at the start of your own methods to verify
+ * correct arguments and state.
+ * @hide
+ */
+final class Preconditions {
+ static void checkArgument(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ static void checkArgumentNotNull(Object object, String message) {
+ if (object == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ static void checkArgumentEquals(String expected, @Nullable String actual, String message) {
+ if (!TextUtils.equals(expected, actual)) {
+ throw new IllegalArgumentException(String.format(message, String.valueOf(expected),
+ String.valueOf(actual)));
+ }
+ }
+
+ static void checkState(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalStateException(message);
+ }
+ }
+}
diff --git a/documents-archive/tests/Android.mk b/documents-archive/tests/Android.mk
new file mode 100644
index 0000000..7269434
--- /dev/null
+++ b/documents-archive/tests/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2015 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-documents-archive
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_AAPT_FLAGS := --auto-add-overlay -0 zip
+LOCAL_PACKAGE_NAME := AndroidSupportDocumentsArchiveTests
+
+include $(BUILD_PACKAGE)
diff --git a/documents-archive/tests/AndroidManifest.xml b/documents-archive/tests/AndroidManifest.xml
new file mode 100644
index 0000000..47da733
--- /dev/null
+++ b/documents-archive/tests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.support.provider.tests">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <provider
+ android:name="android.support.provider.tests.StubProvider"
+ android:authorities="android.support.provider.tests.mystubprovider"
+ android:grantUriPermissions="true"
+ android:exported="true"
+ android:permission="android.permission.MANAGE_DOCUMENTS" />
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.provider.tests"
+ android:label="Tests for android.support.provider." />
+</manifest>
diff --git a/documents-archive/tests/res/raw/archive.zip b/documents-archive/tests/res/raw/archive.zip
new file mode 100644
index 0000000..c3b8d22
--- /dev/null
+++ b/documents-archive/tests/res/raw/archive.zip
Binary files differ
diff --git a/documents-archive/tests/src/android/support/provider/DocumentArchiveTest.java b/documents-archive/tests/src/android/support/provider/DocumentArchiveTest.java
new file mode 100644
index 0000000..afe99b4
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/DocumentArchiveTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2015 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.provider.tests;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.support.provider.DocumentArchive;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Scanner;
+
+/**
+ * Tests for DocumentArchive.
+ */
+public class DocumentArchiveTest extends AndroidTestCase {
+ private static final String DOCUMENT_ID = "document-id";
+ private static final char DELIMITER = ':';
+ private static final String NOTIFICATION_URI = "content://notification-uri";
+ private DocumentArchive mArchive = null;
+
+ @Override
+ public void setUp() {
+ // Extract the file from resources.
+ File file = null;
+ try {
+ file = File.createTempFile("android.support.provider.tests{",
+ "}.zip", mContext.getCacheDir());
+ try (
+ final FileOutputStream outputStream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(
+ ParcelFileDescriptor.open(
+ file, ParcelFileDescriptor.MODE_WRITE_ONLY));
+ final InputStream inputStream =
+ mContext.getResources().openRawResource(R.raw.archive);
+ ) {
+ final byte[] buffer = new byte[32 * 1024];
+ int bytes;
+ while ((bytes = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, bytes);
+ }
+ outputStream.flush();
+ mArchive = DocumentArchive.createForLocalFile(
+ mContext,
+ file,
+ DOCUMENT_ID,
+ DELIMITER,
+ Uri.parse(NOTIFICATION_URI));
+
+ }
+ } catch (IOException e) {
+ fail(String.valueOf(e));
+ } finally {
+ // On UNIX the file will be still available for processes which opened it, even
+ // after deleting it. Remove it ASAP, as it won't be used by anyone else.
+ if (file != null) {
+ file.delete();
+ }
+ }
+ }
+
+ @Override
+ public void tearDown() {
+ if (mArchive != null) {
+ mArchive.close();
+ }
+ }
+
+ public void testQueryChildDocument() throws IOException {
+ final Cursor cursor = mArchive.queryChildDocuments(DOCUMENT_ID, null, null);
+
+ assertTrue(cursor.moveToFirst());
+ assertEquals("document-id:dir2/",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("dir2",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+ assertEquals(Document.MIME_TYPE_DIR,
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+ assertEquals(0,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+ assertTrue(cursor.moveToNext());
+ assertEquals("document-id:dir1/",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("dir1",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+ assertEquals(Document.MIME_TYPE_DIR,
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+ assertEquals(0,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+ assertTrue(cursor.moveToNext());
+ assertEquals("document-id:file1.txt",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("file1.txt",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+ assertEquals("text/plain",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+ assertEquals(13,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+ assertFalse(cursor.moveToNext());
+
+ // Check if querying children works too.
+ final Cursor childCursor = mArchive.queryChildDocuments("document-id:dir1/", null, null);
+
+ assertTrue(childCursor.moveToFirst());
+ assertEquals("document-id:dir1/cherries.txt",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("cherries.txt",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_DISPLAY_NAME)));
+ assertEquals("text/plain",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_MIME_TYPE)));
+ assertEquals(17,
+ childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+ }
+
+ public void testGetDocumentType() throws IOException {
+ assertEquals(Document.MIME_TYPE_DIR, mArchive.getDocumentType("document-id:dir1/"));
+ assertEquals("text/plain", mArchive.getDocumentType("document-id:file1.txt"));
+ }
+
+ public void testIsChildDocument() throws IOException {
+ assertTrue(mArchive.isChildDocument(DOCUMENT_ID, "document-id:dir1"));
+ assertTrue(mArchive.isChildDocument("document-id:dir1/", "document-id:dir1"));
+ assertFalse(mArchive.isChildDocument(DOCUMENT_ID, "document-id:this-does-not-exist"));
+ assertTrue(mArchive.isChildDocument("document-id:dir1/", "document-id:dir1/cherries.txt"));
+ assertTrue(mArchive.isChildDocument(DOCUMENT_ID, "document-id:dir1/cherries.txt"));
+ }
+
+ public void testQueryDocument() throws IOException {
+ final Cursor cursor = mArchive.queryDocument("document-id:dir2/strawberries.txt", null);
+
+ assertTrue(cursor.moveToFirst());
+ assertEquals("document-id:dir2/strawberries.txt",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("strawberries.txt",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+ assertEquals("text/plain",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+ assertEquals(21,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+ }
+
+ public void testOpenDocument() throws IOException {
+ final ParcelFileDescriptor descriptor = mArchive.openDocument(
+ "document-id:dir2/strawberries.txt", "r", null /* signal */);
+ try (final ParcelFileDescriptor.AutoCloseInputStream inputStream =
+ new ParcelFileDescriptor.AutoCloseInputStream(descriptor)) {
+ assertEquals("I love strawberries!", new Scanner(inputStream).nextLine());
+ }
+ }
+}
diff --git a/documents-archive/tests/src/android/support/provider/IntegrationTest.java b/documents-archive/tests/src/android/support/provider/IntegrationTest.java
new file mode 100644
index 0000000..0445d82
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/IntegrationTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 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.provider.tests;
+
+import android.content.ContentProviderClient;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.IllegalArgumentException;
+import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Integration tests for DocumentsProvider and DocumentArchiveHelper.
+ *
+ * <p>Only checks if the provider, then helper are forwarding the calls to the
+ * underlying {@code ArchiveDocument} correctly. More detailed output testing is
+ * done in {@code DocumentArchiveTest}.
+ */
+public class IntegrationTest extends AndroidTestCase {
+ private ContentProviderClient mClient;
+
+ @Override
+ public void setUp() throws RemoteException {
+ mClient = getContext().getContentResolver().acquireContentProviderClient(
+ StubProvider.AUTHORITY);
+ assertNotNull(mClient);
+ mClient.call("reset", null, null);
+ }
+
+ @Override
+ public void tearDown() {
+ if (mClient != null) {
+ mClient.release();
+ mClient = null;
+ }
+ }
+
+ public void testQueryForChildren() throws IOException {
+ final Cursor cursor = mContext.getContentResolver().query(
+ DocumentsContract.buildChildDocumentsUri(
+ StubProvider.AUTHORITY, StubProvider.DOCUMENT_ID),
+ null, null, null, null);
+ assertEquals(3, cursor.getCount());
+ }
+
+ public void testQueryForDocument_Archive()
+ throws IOException, RemoteException, InterruptedException {
+ final Cursor cursor = mContext.getContentResolver().query(
+ DocumentsContract.buildDocumentUri(
+ StubProvider.AUTHORITY, StubProvider.DOCUMENT_ID),
+ null, null, null, null);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToFirst());
+ assertEquals(Document.FLAG_ARCHIVE,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS)));
+ }
+
+ public void testQueryForDocument_ArchiveDescendant()
+ throws IOException, RemoteException, InterruptedException {
+ final Cursor cursor = mContext.getContentResolver().query(
+ DocumentsContract.buildDocumentUri(
+ StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID),
+ null, null, null, null);
+ assertEquals(1, cursor.getCount());
+ assertEquals(StubProvider.NOTIFY_URI, cursor.getNotificationUri());
+
+ final CountDownLatch changeSignal = new CountDownLatch(1);
+ final ContentObserver observer = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ changeSignal.countDown();
+ }
+ };
+
+ try {
+ getContext().getContentResolver().registerContentObserver(
+ cursor.getNotificationUri(), false /* notifyForDescendants */, observer);
+
+ // Simulate deleting the archive file, then confirm that the notification is
+ // propagated and the archive closed.
+ mClient.call("delete", null, null);
+ changeSignal.await();
+
+ mContext.getContentResolver().query(
+ DocumentsContract.buildChildDocumentsUri(
+ StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID),
+ null, null, null, null);
+ fail("Expected IllegalStateException, but succeeded.");
+ } catch (IllegalStateException e) {
+ // Expected, as the file is gone.
+ } finally {
+ getContext().getContentResolver().unregisterContentObserver(observer);
+ }
+ }
+
+ public void testGetType() throws IOException {
+ assertEquals("text/plain", mContext.getContentResolver().getType(
+ DocumentsContract.buildDocumentUri(
+ StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID)));
+ }
+
+ public void testOpenFileDescriptor() throws IOException {
+ final ParcelFileDescriptor descriptor = mContext.getContentResolver().openFileDescriptor(
+ DocumentsContract.buildDocumentUri(
+ StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID),
+ "r", null);
+ assertNotNull(descriptor);
+ }
+}
diff --git a/documents-archive/tests/src/android/support/provider/StubProvider.java b/documents-archive/tests/src/android/support/provider/StubProvider.java
new file mode 100644
index 0000000..3f72cd2
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/StubProvider.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2015 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.provider.tests;
+
+import android.database.Cursor;
+import android.database.MatrixCursor.RowBuilder;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+import android.support.provider.DocumentArchiveHelper;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Stub provider for testing support for archives.
+ */
+public class StubProvider extends DocumentsProvider {
+ public static final String AUTHORITY = "android.support.provider.tests.mystubprovider";
+ public static final String DOCUMENT_ID = "document-id";
+ public static final String FILE_DOCUMENT_ID = "document-id:dir1/cherries.txt";
+ public static final Uri NOTIFY_URI = DocumentsContract.buildRootsUri(AUTHORITY);
+
+ private static final String TAG = "StubProvider";
+ private static final String[] DEFAULT_PROJECTION = new String[] {
+ Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_SIZE,
+ Document.COLUMN_MIME_TYPE, Document.COLUMN_FLAGS,
+ DocumentArchiveHelper.COLUMN_LOCAL_FILE_PATH
+ };
+
+ public File file;
+ public DocumentArchiveHelper archiveHelper;
+ public boolean simulatedDelete = false;
+
+ @Override
+ public Bundle call(String method, String args, Bundle extras) {
+ switch (method) {
+ case "reset":
+ simulatedDelete = false;
+ getContext().getContentResolver().notifyChange(NOTIFY_URI, null);
+ return null;
+ case "delete":
+ simulatedDelete = true;
+ getContext().getContentResolver().notifyChange(NOTIFY_URI, null);
+ return null;
+ default:
+ return super.call(method, args, extras);
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ try {
+ archiveHelper = new DocumentArchiveHelper(this, ':');
+ file = TestUtils.createFileFromResource(getContext(), R.raw.archive);
+ return true;
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to initialize StubProvider.");
+ return false;
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor openDocument(
+ String documentId, String mode, CancellationSignal signal)
+ throws FileNotFoundException {
+ if (archiveHelper.isArchivedDocument(documentId)) {
+ return archiveHelper.openDocument(documentId, mode, signal);
+ }
+
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Cursor queryChildDocuments(
+ String parentDocumentId, String[] projection, String sortOrder)
+ throws FileNotFoundException {
+ if (archiveHelper.isArchivedDocument(parentDocumentId) ||
+ archiveHelper.isSupportedArchiveType(getDocumentType(parentDocumentId))) {
+ return archiveHelper.queryChildDocuments(parentDocumentId, projection, sortOrder);
+ }
+
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Cursor queryDocument(String documentId, String[] projection)
+ throws FileNotFoundException {
+ if (archiveHelper.isArchivedDocument(documentId)) {
+ return archiveHelper.queryDocument(documentId, projection);
+ }
+
+ if (DOCUMENT_ID.equals(documentId)) {
+ if (simulatedDelete) {
+ throw new FileNotFoundException();
+ }
+
+ final MatrixCursor result = new MatrixCursor(
+ projection != null ? projection : DEFAULT_PROJECTION);
+ result.setNotificationUri(getContext().getContentResolver(), NOTIFY_URI);
+ final RowBuilder row = result.newRow();
+ row.add(Document.COLUMN_DOCUMENT_ID, DOCUMENT_ID);
+ row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
+ row.add(Document.COLUMN_SIZE, file.length());
+ row.add(Document.COLUMN_MIME_TYPE, "application/zip");
+ final int flags = archiveHelper.isSupportedArchiveType("application/zip")
+ ? Document.FLAG_ARCHIVE : 0;
+ row.add(Document.COLUMN_FLAGS, flags);
+ row.add(DocumentArchiveHelper.COLUMN_LOCAL_FILE_PATH, file.getPath());
+ return result;
+ }
+
+ throw new FileNotFoundException();
+ }
+
+ @Override
+ public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getDocumentType(String documentId) throws FileNotFoundException {
+ if (archiveHelper.isArchivedDocument(documentId)) {
+ return archiveHelper.getDocumentType(documentId);
+ }
+
+ if (DOCUMENT_ID.equals(documentId)) {
+ return "application/zip";
+ }
+
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/documents-archive/tests/src/android/support/provider/TestUtils.java b/documents-archive/tests/src/android/support/provider/TestUtils.java
new file mode 100644
index 0000000..17ec3e1
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/TestUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 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.provider.tests;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Utilities for tests.
+ */
+final class TestUtils {
+ /**
+ * Saves a file from resources to a temporary location and returns a File instance for it.
+ *
+ * @param id Resource ID
+ */
+ static File createFileFromResource(Context context, int id) throws IOException {
+ final File file = File.createTempFile("android.support.provider.tests{",
+ "}.zip", context.getCacheDir());
+ try (
+ final FileOutputStream outputStream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(
+ ParcelFileDescriptor.open(
+ file, ParcelFileDescriptor.MODE_WRITE_ONLY));
+ final InputStream inputStream = context.getResources().openRawResource(id);
+ ) {
+ final byte[] buffer = new byte[32 * 1024];
+ int bytes;
+ while ((bytes = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, bytes);
+ }
+ outputStream.flush();
+ return file;
+ }
+ }
+}
diff --git a/graphics/drawable/Android.mk b/graphics/drawable/Android.mk
index c376062..9273bed 100644
--- a/graphics/drawable/Android.mk
+++ b/graphics/drawable/Android.mk
@@ -67,7 +67,8 @@
# Animated API Check
support_module := $(LOCAL_MODULE)
support_module_api_dir := $(LOCAL_PATH)/animated/api
-support_module_src_files := $(LOCAL_SRC_FILES)
+support_module_src_files := $(LOCAL_SRC_FILES) \
+ static/src/android/support/graphics/drawable/VectorDrawableCommon.java
support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES)
support_module_java_packages := android.support.graphics.drawable
include $(SUPPORT_API_CHECK)
diff --git a/graphics/drawable/animated/AndroidManifest.xml b/graphics/drawable/animated/AndroidManifest.xml
index 17e9ce5..98f9e17 100644
--- a/graphics/drawable/animated/AndroidManifest.xml
+++ b/graphics/drawable/animated/AndroidManifest.xml
@@ -1,4 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+ -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.support.graphics.drawable.animated">
<application/>
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index c3698b8..02bb5f6 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -4,6 +4,12 @@
dependencies {
compile project(':support-vector-drawable')
+ androidTestCompile ('com.android.support.test:runner:0.4.1') {
+ exclude module: 'support-annotations'
+ }
+ androidTestCompile ('com.android.support.test.espresso:espresso-core:2.2.1') {
+ exclude module: 'support-annotations'
+ }
testCompile 'junit:junit:4.12'
}
@@ -11,6 +17,7 @@
compileSdkVersion 23
defaultConfig {
minSdkVersion 11
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// This disables the builds tools automatic vector -> PNG generation
generatedDensities = []
}
diff --git a/graphics/drawable/animated/tests/AndroidManifest.xml b/graphics/drawable/animated/tests/AndroidManifest.xml
index f317b13..8999852 100644
--- a/graphics/drawable/animated/tests/AndroidManifest.xml
+++ b/graphics/drawable/animated/tests/AndroidManifest.xml
@@ -1,8 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+ -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.support.graphics.drawable.animated.tests">
+ xmlns:tools="http://schemas.android.com/tools"
+ package="android.support.graphics.drawable.animated.test">
+ <uses-sdk
+ android:minSdkVersion="11"
+ android:targetSdkVersion="23"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+ android.support.test.espresso, android.support.test.espresso.idling" />
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application>
<activity android:name="android.support.graphics.drawable.tests.DrawableStubActivity"/>
</application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.graphics.drawable.animated.test" />
</manifest>
diff --git a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java
index d01d868..5318653 100644
--- a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java
+++ b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java
@@ -16,21 +16,26 @@
package android.support.graphics.drawable.tests;
+import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.support.annotation.UiThread;
-import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
import android.graphics.drawable.Drawable.ConstantState;
-import android.test.ActivityInstrumentationTestCase2;
+import android.support.annotation.DrawableRes;
+import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
+import android.support.graphics.drawable.animated.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
-
-import android.support.graphics.drawable.animated.test.R;
-
import android.view.View;
import android.widget.ImageButton;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -40,34 +45,37 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-public class AnimatedVectorDrawableTest extends ActivityInstrumentationTestCase2<DrawableStubActivity> {
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+public class AnimatedVectorDrawableTest {
+ @Rule public final ActivityTestRule<DrawableStubActivity> mActivityTestRule;
private static final String LOGTAG = AnimatedVectorDrawableTest.class.getSimpleName();
private static final int IMAGE_WIDTH = 64;
private static final int IMAGE_HEIGHT = 64;
+ private static final @DrawableRes int DRAWABLE_RES_ID =
+ R.drawable.animation_vector_drawable_grouping_1;
- private DrawableStubActivity mActivity;
+ private Context mContext;
private Resources mResources;
private AnimatedVectorDrawableCompat mAnimatedVectorDrawable;
private Bitmap mBitmap;
private Canvas mCanvas;
private static final boolean DBG_DUMP_PNG = false;
- private int mResId = R.drawable.animation_vector_drawable_grouping_1;
public AnimatedVectorDrawableTest() {
- super(DrawableStubActivity.class);
+ mActivityTestRule = new ActivityTestRule<>(DrawableStubActivity.class);
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
+ @Before
+ public void setup() throws Exception {
mBitmap = Bitmap.createBitmap(IMAGE_WIDTH, IMAGE_HEIGHT, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
- mActivity = getActivity();
- mResources = mActivity.getResources();
+ mContext = mActivityTestRule.getActivity();
+ mResources = mContext.getResources();
- mAnimatedVectorDrawable = AnimatedVectorDrawableCompat.create(mActivity, mResId);
+ mAnimatedVectorDrawable = AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
}
// This is only for debugging or golden image (re)generation purpose.
@@ -102,10 +110,10 @@
}
}
- @UiThread
+ @Test
public void testInflate() throws Exception {
// Setup AnimatedVectorDrawableCompat from xml file
- XmlPullParser parser = mResources.getXml(mResId);
+ XmlPullParser parser = mResources.getXml(DRAWABLE_RES_ID);
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
@@ -128,50 +136,52 @@
assertTrue(earthColor == 0xFF5656EA);
if (DBG_DUMP_PNG) {
- saveVectorDrawableIntoPNG(mBitmap, mResId);
+ saveVectorDrawableIntoPNG(mBitmap, DRAWABLE_RES_ID);
}
}
+ @Test
public void testGetChangingConfigurations() {
- AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.create(mActivity, mResId);
- ConstantState constantState = avd.getConstantState();
+ ConstantState constantState = mAnimatedVectorDrawable.getConstantState();
if (constantState != null) {
// default
assertEquals(0, constantState.getChangingConfigurations());
- assertEquals(0, avd.getChangingConfigurations());
+ assertEquals(0, mAnimatedVectorDrawable.getChangingConfigurations());
// change the drawable's configuration does not affect the state's configuration
- avd.setChangingConfigurations(0xff);
- assertEquals(0xff, avd.getChangingConfigurations());
+ mAnimatedVectorDrawable.setChangingConfigurations(0xff);
+ assertEquals(0xff, mAnimatedVectorDrawable.getChangingConfigurations());
assertEquals(0, constantState.getChangingConfigurations());
// the state's configuration get refreshed
- constantState = avd.getConstantState();
+ constantState = mAnimatedVectorDrawable.getConstantState();
assertEquals(0xff, constantState.getChangingConfigurations());
// set a new configuration to drawable
- avd.setChangingConfigurations(0xff00);
+ mAnimatedVectorDrawable.setChangingConfigurations(0xff00);
assertEquals(0xff, constantState.getChangingConfigurations());
- assertEquals(0xffff, avd.getChangingConfigurations());
+ assertEquals(0xffff, mAnimatedVectorDrawable.getChangingConfigurations());
}
}
- public void testGetConstantState() throws InterruptedException, Throwable {
- AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.create(mActivity, mResId);
- ConstantState constantState = avd.getConstantState();
+ @Test
+ public void testGetConstantState() {
+ ConstantState constantState = mAnimatedVectorDrawable.getConstantState();
if (constantState != null) {
assertEquals(0, constantState.getChangingConfigurations());
- avd.setChangingConfigurations(1);
- constantState = avd.getConstantState();
+ mAnimatedVectorDrawable.setChangingConfigurations(1);
+ constantState = mAnimatedVectorDrawable.getConstantState();
assertNotNull(constantState);
assertEquals(1, constantState.getChangingConfigurations());
}
}
- public void testAnimateColor() throws InterruptedException, Throwable {
- final ImageButton imageButton = (ImageButton) getActivity().findViewById(R.id.imageButton);
+ @Test
+ public void testAnimateColor() throws Throwable {
+ final ImageButton imageButton =
+ (ImageButton) mActivityTestRule.getActivity().findViewById(R.id.imageButton);
final int viewW = imageButton.getWidth();
final int viewH = imageButton.getHeight();
int pixelX = viewW / 2;
@@ -181,10 +191,11 @@
Bitmap.Config.ARGB_8888);
final Canvas c = new Canvas(bitmap);
CountDownLatch latch = new CountDownLatch(numTests);
- runTestOnUiThread(new Runnable() {
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
- AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.create(mActivity,
+ AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.create(mContext,
R.drawable.animated_color_fill);
imageButton.setBackgroundDrawable(avd);
avd.start();
@@ -207,7 +218,7 @@
*/
private void verifyRedOnly(final int pixelX, final int pixelY, final View button,
final Bitmap bitmap, final Canvas canvas, final CountDownLatch latch) throws Throwable {
- runTestOnUiThread(new Runnable() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
button.draw(canvas);
@@ -221,10 +232,14 @@
});
}
+ @Test
public void testMutate() {
- AnimatedVectorDrawableCompat d1 = AnimatedVectorDrawableCompat.create(mActivity, mResId);
- AnimatedVectorDrawableCompat d2 = AnimatedVectorDrawableCompat.create(mActivity, mResId);
- AnimatedVectorDrawableCompat d3 = AnimatedVectorDrawableCompat.create(mActivity, mResId);
+ AnimatedVectorDrawableCompat d1 =
+ AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
+ AnimatedVectorDrawableCompat d2 =
+ AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
+ AnimatedVectorDrawableCompat d3 =
+ AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
if (d1.getConstantState() != null) {
int originalAlpha = d2.getAlpha();
diff --git a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/DrawableStubActivity.java b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/DrawableStubActivity.java
index 1a47250..0cd197e 100644
--- a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/DrawableStubActivity.java
+++ b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/DrawableStubActivity.java
@@ -17,16 +17,24 @@
import android.app.Activity;
import android.os.Bundle;
-
import android.support.graphics.drawable.animated.test.R;
+import android.view.WindowManager;
public class DrawableStubActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ overridePendingTransition(0, 0);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.avd_layout);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
}
diff --git a/graphics/drawable/static/AndroidManifest.xml b/graphics/drawable/static/AndroidManifest.xml
index c170289..e91290d 100644
--- a/graphics/drawable/static/AndroidManifest.xml
+++ b/graphics/drawable/static/AndroidManifest.xml
@@ -1,5 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.support.graphics.drawable">
+<!--
+ Copyright (C) 2015 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.
+ -->
+<manifest package="android.support.graphics.drawable">
<application/>
</manifest>
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index f0a363f..f7a5b97 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -4,6 +4,12 @@
dependencies {
compile project(':support-v4')
+ androidTestCompile ('com.android.support.test:runner:0.4.1') {
+ exclude module: 'support-annotations'
+ }
+ androidTestCompile ('com.android.support.test.espresso:espresso-core:2.2.1') {
+ exclude module: 'support-annotations'
+ }
testCompile 'junit:junit:4.12'
}
@@ -12,6 +18,7 @@
defaultConfig {
minSdkVersion 7
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// This disables the builds tools automatic vector -> PNG generation
generatedDensities = []
}
diff --git a/graphics/drawable/static/tests/AndroidManifest.xml b/graphics/drawable/static/tests/AndroidManifest.xml
index 97e74c6..27f3fbd 100644
--- a/graphics/drawable/static/tests/AndroidManifest.xml
+++ b/graphics/drawable/static/tests/AndroidManifest.xml
@@ -1,6 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+ -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.support.graphics.drawable.tests">
+ xmlns:tools="http://schemas.android.com/tools"
+ package="android.support.graphics.drawable.test">
+ <uses-sdk
+ android:minSdkVersion="8"
+ android:targetSdkVersion="23"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+ android.support.test.espresso, android.support.test.espresso.idling" />
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application/>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.graphics.drawable.test" />
</manifest>
diff --git a/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java b/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java
index 85fd597..80bc51d 100644
--- a/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java
+++ b/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java
@@ -16,6 +16,7 @@
package android.support.graphics.drawable.tests;
+import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
@@ -25,17 +26,24 @@
import android.graphics.drawable.Drawable;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.graphics.drawable.test.R;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
@MediumTest
-public class VectorDrawableTest extends AndroidTestCase {
+public class VectorDrawableTest {
private static final String LOGTAG = "VectorDrawableTest";
private static final int[] ICON_RES_IDS = new int[]{
@@ -108,30 +116,31 @@
private static final boolean DBG_DUMP_PNG = false;
+ private Context mContext;
private Resources mResources;
private VectorDrawableCompat mVectorDrawable;
private Bitmap mBitmap;
private Canvas mCanvas;
private Theme mTheme;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setup() {
final int width = IMAGE_WIDTH;
final int height = IMAGE_HEIGHT;
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
+ mContext = InstrumentationRegistry.getContext();
mResources = mContext.getResources();
mTheme = mContext.getTheme();
}
- public void testSimpleVectorDrawables() throws XmlPullParserException, IOException {
+ @Test
+ public void testSimpleVectorDrawables() throws Exception {
verifyVectorDrawables(ICON_RES_IDS, GOLDEN_IMAGES, null);
}
-
private void verifyVectorDrawables(int[] resIds, int[] goldenImages, int[] stateSet)
throws XmlPullParserException, IOException {
for (int i = 0; i < resIds.length; i++) {
@@ -204,12 +213,11 @@
return "";
}
- final Resources res = getContext().getResources();
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < stateSet.length; i++) {
builder.append('_');
- final String state = res.getResourceName(stateSet[i]);
+ final String state = mResources.getResourceName(stateSet[i]);
final int stateIndex = state.indexOf("state_");
if (stateIndex >= 0) {
builder.append(state.substring(stateIndex + 6));
@@ -258,6 +266,7 @@
}
+ @Test
public void testGetChangingConfigurations() {
VectorDrawableCompat vectorDrawable =
VectorDrawableCompat.create(mResources, TEST_ICON, mTheme);
@@ -282,6 +291,7 @@
assertEquals(0xffff, vectorDrawable.getChangingConfigurations());
}
+ @Test
public void testGetConstantState() {
VectorDrawableCompat vectorDrawable =
VectorDrawableCompat.create(mResources, R.drawable.vector_icon_delete, mTheme);
@@ -295,8 +305,8 @@
assertEquals(1, constantState.getChangingConfigurations());
}
+ @Test
public void testMutate() {
- Resources resources = mContext.getResources();
VectorDrawableCompat d1 =
VectorDrawableCompat.create(mResources, TEST_ICON, mTheme);
VectorDrawableCompat d2 =
diff --git a/percent/src/android/support/percent/PercentLayoutHelper.java b/percent/src/android/support/percent/PercentLayoutHelper.java
index 956b428..2416f88 100644
--- a/percent/src/android/support/percent/PercentLayoutHelper.java
+++ b/percent/src/android/support/percent/PercentLayoutHelper.java
@@ -72,6 +72,9 @@
public class PercentLayoutHelper {
private static final String TAG = "PercentLayout";
+ private static final boolean DEBUG = false;
+ private static final boolean VERBOSE = false;
+
private final ViewGroup mHost;
public PercentLayoutHelper(ViewGroup host) {
@@ -96,7 +99,7 @@
* @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup.
*/
public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "adjustChildren: " + mHost + " widthMeasureSpec: "
+ View.MeasureSpec.toString(widthMeasureSpec) + " heightMeasureSpec: "
+ View.MeasureSpec.toString(heightMeasureSpec));
@@ -107,13 +110,13 @@
for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "should adjust " + view + " " + params);
}
if (params instanceof PercentLayoutParams) {
PercentLayoutInfo info =
((PercentLayoutParams) params).getPercentLayoutInfo();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "using " + info);
}
if (info != null) {
@@ -139,7 +142,7 @@
float value = array.getFraction(R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent width: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -147,7 +150,7 @@
}
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_heightPercent, 1, 1, -1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent height: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -155,7 +158,7 @@
}
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginPercent, 1, 1, -1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -167,7 +170,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginLeftPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent left margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -176,7 +179,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginTopPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent top margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -185,7 +188,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginRightPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent right margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -194,7 +197,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginBottomPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent bottom margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -203,7 +206,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginStartPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent start margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -212,7 +215,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginEndPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent end margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -221,7 +224,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_aspectRatio, 1, 1, -1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "aspect ratio: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -229,7 +232,7 @@
}
array.recycle();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "constructed: " + info);
}
return info;
@@ -244,13 +247,13 @@
for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "should restore " + view + " " + params);
}
if (params instanceof PercentLayoutParams) {
PercentLayoutInfo info =
((PercentLayoutParams) params).getPercentLayoutInfo();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "using " + info);
}
if (info != null) {
@@ -283,7 +286,7 @@
for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "should handle measured state too small " + view + " " + params);
}
if (params instanceof PercentLayoutParams) {
@@ -301,7 +304,7 @@
}
}
}
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "should trigger second measure pass: " + needsSecondMeasure);
}
return needsSecondMeasure;
@@ -411,7 +414,7 @@
}
}
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")");
}
}
@@ -474,7 +477,7 @@
MarginLayoutParamsCompat.resolveLayoutDirection(params,
ViewCompat.getLayoutDirection(view));
}
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "after fillMarginLayoutParams: (" + params.width + ", " + params.height
+ ")");
}
diff --git a/percent/tests/java/android/support/percent/BaseTestActivity.java b/percent/tests/java/android/support/percent/BaseTestActivity.java
index ec7d7bb..c0c9c7d 100755
--- a/percent/tests/java/android/support/percent/BaseTestActivity.java
+++ b/percent/tests/java/android/support/percent/BaseTestActivity.java
@@ -24,6 +24,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ overridePendingTransition(0, 0);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
final int contentView = getContentViewLayoutResId();
if (contentView > 0) {
@@ -33,6 +34,12 @@
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
+
protected abstract int getContentViewLayoutResId();
protected void onContentViewSet() {}
diff --git a/tests/Android.mk b/tests/Android.mk
index 7bd3b00..7993eed 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -5,7 +5,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, java)
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target android-support-v4 junit-runner
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := AndroidSupportTests
diff --git a/v13/Android.mk b/v13/Android.mk
index 946d1f8..5a9c236 100644
--- a/v13/Android.mk
+++ b/v13/Android.mk
@@ -40,6 +40,14 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13-ics-mr1
include $(BUILD_STATIC_JAVA_LIBRARY)
+# A helper sub-library that makes direct use of NYC APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v13-nyc
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api24)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13-mnc
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
# -----------------------------------------------------------------------
include $(CLEAR_VARS)
@@ -47,8 +55,7 @@
LOCAL_SDK_VERSION := 13
LOCAL_SRC_FILES := $(call all-java-files-under, java)
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4 \
- android-support-v13-ics-mr1 \
- android-support-v13-mnc
+ android-support-v13-nyc
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v13/api/current.txt b/v13/api/current.txt
index 36ea6c0..240d449 100644
--- a/v13/api/current.txt
+++ b/v13/api/current.txt
@@ -1,5 +1,9 @@
package android.support.v13.app {
+ public class ActivityCompat extends android.support.v4.app.ActivityCompat {
+ method public static android.support.v13.view.DropPermissionsCompat requestDropPermissions(android.app.Activity, android.view.DragEvent);
+ }
+
public class FragmentCompat {
ctor public FragmentCompat();
method public static void requestPermissions(android.app.Fragment, java.lang.String[], int);
@@ -36,3 +40,34 @@
}
+package android.support.v13.view {
+
+ public abstract class DragStartHelper implements android.view.View.OnLongClickListener android.view.View.OnTouchListener {
+ ctor public DragStartHelper(android.view.View);
+ method public void attach();
+ method public android.view.View.DragShadowBuilder getShadowBuilder(android.view.View);
+ method public void getTouchPosition(android.view.View, android.graphics.Point);
+ method public boolean handleLongClick(android.view.View);
+ method public boolean handleTouch(android.view.View, android.view.MotionEvent);
+ method protected abstract void onDragStart(android.view.View);
+ method public boolean onLongClick(android.view.View);
+ method public boolean onTouch(android.view.View, android.view.MotionEvent);
+ method public void remove();
+ }
+
+ public class DragStartHelper.ShadowBuilder extends android.view.View.DragShadowBuilder {
+ ctor public DragStartHelper.ShadowBuilder(android.view.View);
+ }
+
+ public final class DropPermissionsCompat {
+ method public void release();
+ }
+
+ public class ViewCompat extends android.support.v4.view.ViewCompat {
+ method public static void cancelDragAndDrop(android.view.View);
+ method public static boolean startDragAndDrop(android.view.View, android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+ method public static void updateDragShadow(android.view.View, android.view.View.DragShadowBuilder);
+ }
+
+}
+
diff --git a/v13/api24/android/support/v13/view/DropPermissionsCompatApi24.java b/v13/api24/android/support/v13/view/DropPermissionsCompatApi24.java
new file mode 100644
index 0000000..47ee4d1
--- /dev/null
+++ b/v13/api24/android/support/v13/view/DropPermissionsCompatApi24.java
@@ -0,0 +1,31 @@
+/*
+ * 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.v13.view;
+
+import android.app.Activity;
+import android.view.DragEvent;
+import android.view.DropPermissions;
+
+class DropPermissionsCompatApi24 {
+ public static Object request(Activity activity, DragEvent dragEvent) {
+ return activity.requestDropPermissions(dragEvent);
+ }
+
+ public static void release(Object dropPermissions) {
+ ((DropPermissions)dropPermissions).release();
+ }
+}
diff --git a/v13/api24/android/support/v13/view/ViewCompatApi24.java b/v13/api24/android/support/v13/view/ViewCompatApi24.java
new file mode 100644
index 0000000..725cad0
--- /dev/null
+++ b/v13/api24/android/support/v13/view/ViewCompatApi24.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.v13.view;
+
+import android.content.ClipData;
+import android.view.View;
+
+class ViewCompatApi24 {
+ public static boolean startDragAndDrop(View v, ClipData data,
+ View.DragShadowBuilder shadowBuilder, Object localState, int flags) {
+ return v.startDragAndDrop(data, shadowBuilder, localState, flags);
+ }
+
+ public static void cancelDragAndDrop(View v) {
+ v.cancelDragAndDrop();
+ }
+
+ public static void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+ v.updateDragShadow(shadowBuilder);
+ }
+
+ private ViewCompatApi24() {}
+}
diff --git a/v13/build.gradle b/v13/build.gradle
index b0d59e6..f65557a 100644
--- a/v13/build.gradle
+++ b/v13/build.gradle
@@ -11,7 +11,9 @@
def icsSS = createApiSourceset('ics', 'ics', '14', null)
def icsMr1SS = createApiSourceset('icsmr1', 'ics-mr1', '15', icsSS)
-def api23SS = createApiSourceset('api23', 'api23', 'current', icsMr1SS)
+def api23SS = createApiSourceset('api23', 'api23', '23', icsMr1SS)
+def api24SS = createApiSourceset('api24', 'api24', 'current', api23SS)
+
def createApiSourceset(String name, String folder, String apiLevel, SourceSet previousSource) {
def sourceSet = sourceSets.create(name)
diff --git a/v13/java/android/support/v13/app/ActivityCompat.java b/v13/java/android/support/v13/app/ActivityCompat.java
new file mode 100644
index 0000000..a97d2ef
--- /dev/null
+++ b/v13/java/android/support/v13/app/ActivityCompat.java
@@ -0,0 +1,43 @@
+/*
+ * 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.v13.app;
+
+import android.app.Activity;
+import android.support.v13.view.DropPermissionsCompat;
+import android.view.DragEvent;
+
+/**
+ * Helper for accessing features in {@link android.app.Activity}
+ * introduced after API level 13 in a backwards compatible fashion.
+ */
+public class ActivityCompat extends android.support.v4.app.ActivityCompat {
+
+ /**
+ * Create {@link DropPermissionsCompat} object bound to this activity and controlling
+ * the access permissions for content URIs associated with the {@link android.view.DragEvent}.
+ * @param dragEvent Drag event to request permission for
+ * @return The {@link DropPermissionsCompat} object used to control access to the content URIs.
+ * {@code null} if no content URIs are associated with the event or if permissions could not be
+ * granted.
+ */
+ public static DropPermissionsCompat requestDropPermissions(Activity activity,
+ DragEvent dragEvent) {
+ return DropPermissionsCompat.request(activity, dragEvent);
+ }
+
+ private ActivityCompat() {}
+}
diff --git a/v13/java/android/support/v13/view/DragStartHelper.java b/v13/java/android/support/v13/view/DragStartHelper.java
new file mode 100644
index 0000000..da8824e
--- /dev/null
+++ b/v13/java/android/support/v13/view/DragStartHelper.java
@@ -0,0 +1,188 @@
+/*
+ * 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.v13.view;
+
+
+import android.graphics.Point;
+import android.support.v4.view.InputDeviceCompat;
+import android.support.v4.view.MotionEventCompat;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * DragStartHelper is a utility class for implementing drag and drop support.
+ * <p>
+ * It detects gestures commonly used to start drag (long click for any input source,
+ * click and drag for mouse).
+ * <p>
+ * It also keeps track of the screen location where the drag started, and helps determining
+ * the hot spot position for drag shadow.
+ * <p>
+ * Implement {@link #onDragStart(View)} method to start the drag operation:
+ * <pre>
+ * mDragStartHelper = new DragStartHelper(mDraggableView) {
+ * protected void onDragStart(View v) {
+ * v.startDrag(mClipData, getShadowBuilder(view), mLocalState, mDragFlags);
+ * }
+ * };
+ * </pre>
+ * Once created, DragStartHelper can be attached to a view (this will replace existing long click
+ * and touch listeners):
+ * <pre>
+ * mDragStartHelper.attach();
+ * </pre>
+ * It may also be used in combination with existing listeners:
+ * <pre>
+ * public boolean onTouch(View view, MotionEvent event) {
+ * return mDragStartHelper.handleTouch(view, event) || handleTouchEvent(view, event);
+ * }
+ * public boolean onLongClick(View view) {
+ * return mDragStartHelper.handleLongClick(view) || handleLongClickEvent(view);
+ * }
+ * </pre>
+ */
+public abstract class DragStartHelper implements View.OnLongClickListener, View.OnTouchListener {
+ final private View mView;
+ private float mLastTouchRawX, mLastTouchRawY;
+
+ /**
+ * Create a DragStartHelper associated with the specified view.
+ * The newly created helper is not initally attached to the view, {@link #attach} must be
+ * called explicitly.
+ * @param view A View
+ */
+ public DragStartHelper(View view) {
+ mView = view;
+ }
+
+ /**
+ * Attach the helper to the view.
+ * <p>
+ * This will replace previously existing touch and long click listeners.
+ */
+ public void attach() {
+ mView.setOnLongClickListener(this);
+ mView.setOnTouchListener(this);
+ }
+
+ /**
+ * Detach the helper from the view.
+ * <p>
+ * This will reset touch and long click listeners to {@code null}.
+ */
+ public void remove() {
+ mView.setOnLongClickListener(null);
+ mView.setOnTouchListener(null);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return handleTouch(v, event);
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ return handleLongClick(v);
+ }
+
+ /**
+ * Handle a touch event.
+ * @param v The view the touch event has been dispatched to.
+ * @param event The MotionEvent object containing full information about
+ * the event.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ public boolean handleTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN ||
+ event.getAction() == MotionEvent.ACTION_MOVE) {
+ mLastTouchRawX = event.getRawX();
+ mLastTouchRawY = event.getRawY();
+ }
+ if (event.getAction() == MotionEvent.ACTION_MOVE &&
+ MotionEventCompat.isFromSource(event, InputDeviceCompat.SOURCE_MOUSE) &&
+ (MotionEventCompat.getButtonState(event) & MotionEventCompat.BUTTON_PRIMARY) != 0) {
+ onDragStart(v);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handle a long click event.
+ * @param v The view that was clicked and held.
+ * @return true if the callback consumed the long click, false otherwise.
+ */
+ public boolean handleLongClick(View v) {
+ onDragStart(v);
+ return true;
+ }
+
+ /**
+ * Compute the position of the touch event that started the drag operation.
+ * @param v The view relative to which the position should be computed.
+ * @param point The position of the touch event that started the drag operation.
+ */
+ public void getTouchPosition(View v, Point point) {
+ int [] viewLocation = new int[2];
+ v.getLocationOnScreen(viewLocation);
+ point.set((int) mLastTouchRawX - viewLocation[0], (int) mLastTouchRawY - viewLocation[1]);
+ }
+
+ /**
+ * Create a {@link View.DragShadowBuilder} which will build a drag shadow with the same
+ * appearance and dimensions as the specified view and with the hot spot at the location of
+ * the touch event that started the drag operation.
+ * @param view A view
+ * @return {@link View.DragShadowBuilder}
+ */
+ public View.DragShadowBuilder getShadowBuilder(View view) {
+ return new ShadowBuilder(view);
+ }
+
+ /**
+ * A utility class that builds a drag shadow with the same appearance and dimensions as the
+ * specified view and with the hot spot at the location of the touch event that started the
+ * drag operation.
+ * <p>
+ * At the start of the drag operation a drag shadow built this way will be positioned directly
+ * over the specified view.
+ */
+ public class ShadowBuilder extends View.DragShadowBuilder {
+ /**
+ * Constructs a shadow image builder based on a View. By default, the resulting drag
+ * shadow will have the same appearance and dimensions as the View, with the touch point
+ * at the location of the touch event that started the drag operation.
+ * @param view A view.
+ */
+ public ShadowBuilder(View view) {
+ super(view);
+ }
+
+ @Override
+ public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
+ super.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
+ getTouchPosition(getView(), shadowTouchPoint);
+ }
+ }
+
+ /**
+ * Called when the drag start gesture has been detected.
+ * @param v A view on which the drag start gesture has been detected.
+ */
+ protected abstract void onDragStart(View v);
+}
+
diff --git a/v13/java/android/support/v13/view/DropPermissionsCompat.java b/v13/java/android/support/v13/view/DropPermissionsCompat.java
new file mode 100644
index 0000000..7b6078e
--- /dev/null
+++ b/v13/java/android/support/v13/view/DropPermissionsCompat.java
@@ -0,0 +1,88 @@
+/*
+ * 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.v13.view;
+
+import android.app.Activity;
+import android.support.v4.os.BuildCompat;
+import android.view.DragEvent;
+
+/**
+ * Helper for accessing features in {@link android.view.DropPermissions}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public final class DropPermissionsCompat {
+
+ interface DropPermissionsCompatImpl {
+ Object request(Activity activity, DragEvent dragEvent);
+ void release(Object dropPermissions);
+ }
+
+ static class BaseDropPermissionsCompatImpl implements DropPermissionsCompatImpl {
+ @Override
+ public Object request(Activity activity, DragEvent dragEvent) {
+ return null;
+ }
+
+ @Override
+ public void release(Object dropPermissions) {
+ // no-op
+ }
+ }
+
+ static class Api24DropPermissionsCompatImpl extends BaseDropPermissionsCompatImpl {
+ @Override
+ public Object request(Activity activity, DragEvent dragEvent) {
+ return DropPermissionsCompatApi24.request(activity, dragEvent);
+ }
+
+ @Override
+ public void release(Object dropPermissions) {
+ DropPermissionsCompatApi24.release(dropPermissions);
+ }
+ }
+
+ private static DropPermissionsCompatImpl IMPL;
+ static {
+ if (BuildCompat.isAtLeastN()) {
+ IMPL = new Api24DropPermissionsCompatImpl();
+ } else {
+ IMPL = new BaseDropPermissionsCompatImpl();
+ }
+ }
+
+ private Object mDropPermissions;
+
+ private DropPermissionsCompat(Object dropPermissions) {
+ mDropPermissions = dropPermissions;
+ }
+
+ /** @hide */
+ public static DropPermissionsCompat request(Activity activity, DragEvent dragEvent) {
+ Object dropPermissions = IMPL.request(activity, dragEvent);
+ if (dropPermissions != null) {
+ return new DropPermissionsCompat(dropPermissions);
+ }
+ return null;
+ }
+
+ /*
+ * Revoke the permission grant explicitly.
+ */
+ public void release() {
+ IMPL.release(mDropPermissions);
+ }
+}
diff --git a/v13/java/android/support/v13/view/ViewCompat.java b/v13/java/android/support/v13/view/ViewCompat.java
new file mode 100644
index 0000000..38db5fa
--- /dev/null
+++ b/v13/java/android/support/v13/view/ViewCompat.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 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.v13.view;
+
+import android.content.ClipData;
+import android.support.v4.os.BuildCompat;
+import android.view.View;
+
+/**
+ * Helper for accessing features in {@link View} introduced after API
+ * level 13 in a backwards compatible fashion.
+ */
+public class ViewCompat extends android.support.v4.view.ViewCompat {
+ interface ViewCompatImpl {
+ boolean startDragAndDrop(View v, ClipData data, View.DragShadowBuilder shadowBuilder,
+ Object localState, int flags);
+ void cancelDragAndDrop(View v);
+ void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder);
+ }
+
+ private static class BaseViewCompatImpl implements ViewCompatImpl {
+ @Override
+ public boolean startDragAndDrop(View v, ClipData data, View.DragShadowBuilder shadowBuilder,
+ Object localState, int flags) {
+ return v.startDrag(data, shadowBuilder, localState, flags);
+ }
+
+ @Override
+ public void cancelDragAndDrop(View v) {
+ // no-op
+ }
+
+ @Override
+ public void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+ // no-op
+ }
+ }
+
+ private static class Api24ViewCompatImpl implements ViewCompatImpl {
+ @Override
+ public boolean startDragAndDrop(View v, ClipData data, View.DragShadowBuilder shadowBuilder,
+ Object localState, int flags) {
+ return ViewCompatApi24.startDragAndDrop(
+ v, data, shadowBuilder, localState, flags);
+ }
+
+ @Override
+ public void cancelDragAndDrop(View v) {
+ ViewCompatApi24.cancelDragAndDrop(v);
+ }
+
+ @Override
+ public void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+ ViewCompatApi24.updateDragShadow(v, shadowBuilder);
+ }
+ }
+
+ static ViewCompatImpl IMPL;
+ static {
+ if (BuildCompat.isAtLeastN()) {
+ IMPL = new Api24ViewCompatImpl();
+ } else {
+ IMPL = new BaseViewCompatImpl();
+ }
+ }
+
+ /**
+ * Start the drag and drop operation.
+ */
+ public static boolean startDragAndDrop(View v, ClipData data,
+ View.DragShadowBuilder shadowBuilder, Object localState, int flags) {
+ return IMPL.startDragAndDrop(v, data, shadowBuilder, localState, flags);
+ }
+
+ /**
+ * Cancel the drag and drop operation.
+ */
+ public static void cancelDragAndDrop(View v) {
+ IMPL.cancelDragAndDrop(v);
+ }
+
+ /**
+ * Update the drag shadow while drag and drop is in progress.
+ */
+ public static void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+ IMPL.updateDragShadow(v, shadowBuilder);
+ }
+
+ private ViewCompat() {
+ }
+}
diff --git a/v14/preference/Android.mk b/v14/preference/Android.mk
index e0a5243..52c1abb 100644
--- a/v14/preference/Android.mk
+++ b/v14/preference/Android.mk
@@ -20,8 +20,8 @@
# contains will not be linked into the final static library.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v14-preference-res
+LOCAL_SRC_FILES := $(call all-java-files-under, ../../v7/preference/constants)
LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
LOCAL_RESOURCE_DIR := \
frameworks/support/v7/appcompat/res \
frameworks/support/v7/preference/res \
@@ -56,4 +56,4 @@
support_module_src_files := $(LOCAL_SRC_FILES)
support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES)
support_module_java_packages := android.support.v14.preference
-include $(SUPPORT_API_CHECK)
\ No newline at end of file
+include $(SUPPORT_API_CHECK)
diff --git a/v14/preference/res/layout/preference_dropdown_material.xml b/v14/preference/res/layout/preference_dropdown_material.xml
new file mode 100644
index 0000000..d1ca64e
--- /dev/null
+++ b/v14/preference/res/layout/preference_dropdown_material.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:visibility="invisible" />
+
+ <include layout="@layout/preference_material" />
+
+</FrameLayout>
diff --git a/v14/preference/res/layout/preference_widget_switch.xml b/v14/preference/res/layout/preference_widget_switch.xml
index ae83afa..afc4351 100644
--- a/v14/preference/res/layout/preference_widget_switch.xml
+++ b/v14/preference/res/layout/preference_widget_switch.xml
@@ -18,7 +18,7 @@
<!-- Layout used by SwitchPreference for the switch widget style. This is inflated
inside android.R.layout.preference. -->
<Switch xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/switchWidget"
+ android:id="@android:id/switch_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
diff --git a/v14/preference/res/values/styles.xml b/v14/preference/res/values/styles.xml
index c197121..a83417a 100644
--- a/v14/preference/res/values/styles.xml
+++ b/v14/preference/res/values/styles.xml
@@ -60,6 +60,10 @@
<item name="android:layout">@layout/preference_material</item>
</style>
+ <style name="Preference.DropDown.Material">
+ <item name="android:layout">@layout/preference_dropdown_material</item>
+ </style>
+
<style name="Preference_TextAppearanceMaterialBody2">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif</item>
diff --git a/v14/preference/res/values/themes.xml b/v14/preference/res/values/themes.xml
index 64bc91e..026d2d8 100644
--- a/v14/preference/res/values/themes.xml
+++ b/v14/preference/res/values/themes.xml
@@ -33,6 +33,7 @@
<item name="switchPreferenceStyle">@style/Preference.SwitchPreference.Material</item>
<item name="dialogPreferenceStyle">@style/Preference.DialogPreference.Material</item>
<item name="editTextPreferenceStyle">@style/Preference.DialogPreference.EditTextPreference.Material</item>
+ <item name="dropdownPreferenceStyle">@style/Preference.DropDown.Material</item>
<item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.Material</item>
</style>
</resources>
diff --git a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
index 2a636fe..8316b0d 100644
--- a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
+++ b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
@@ -72,7 +72,8 @@
}
public MultiSelectListPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.dialogPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
+ android.R.attr.dialogPreferenceStyle));
}
public MultiSelectListPreference(Context context) {
diff --git a/v14/preference/src/android/support/v14/preference/PreferenceFragment.java b/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
index b9fc935..c0b69f0 100644
--- a/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
+++ b/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
@@ -28,7 +28,9 @@
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.annotation.XmlRes;
+import android.support.v4.content.res.TypedArrayUtils;
import android.support.v4.view.ViewCompat;
+import android.support.v7.preference.AndroidResources;
import android.support.v7.preference.DialogPreference;
import android.support.v7.preference.EditTextPreference;
import android.support.v7.preference.ListPreference;
@@ -242,7 +244,8 @@
TypedArray a = mStyledContext.obtainStyledAttributes(null,
R.styleable.PreferenceFragment,
- R.attr.preferenceFragmentStyle,
+ TypedArrayUtils.getAttr(mStyledContext, R.attr.preferenceFragmentStyle,
+ AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE),
0);
mLayoutResId = a.getResourceId(R.styleable.PreferenceFragment_android_layout, mLayoutResId);
@@ -263,10 +266,10 @@
final View view = themedInflater.inflate(mLayoutResId, container, false);
- final View rawListContainer = view.findViewById(R.id.list_container);
+ final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER);
if (!(rawListContainer instanceof ViewGroup)) {
- throw new RuntimeException("Content has view with id attribute 'R.id.list_container' "
- + "that is not a ViewGroup class");
+ throw new RuntimeException("Content has view with id attribute "
+ + "'android.R.id.list_container' that is not a ViewGroup class");
}
final ViewGroup listContainer = (ViewGroup) rawListContainer;
@@ -315,14 +318,19 @@
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
if (mHavePrefs) {
bindPreferences();
}
mInitDone = true;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
@@ -351,9 +359,12 @@
@Override
public void onDestroyView() {
- mList = null;
mHandler.removeCallbacks(mRequestFocus);
mHandler.removeMessages(MSG_BIND_PREFERENCES);
+ if (mHavePrefs) {
+ unbindPreferences();
+ }
+ mList = null;
super.onDestroyView();
}
@@ -520,6 +531,15 @@
onBindPreferences();
}
+ private void unbindPreferences() {
+ final PreferenceScreen preferenceScreen = getPreferenceScreen();
+ if (preferenceScreen != null) {
+ preferenceScreen.onDetached();
+ getListView().setAdapter(null);
+ }
+ onUnbindPreferences();
+ }
+
/** @hide */
protected void onBindPreferences() {
}
@@ -668,22 +688,26 @@
private boolean shouldDrawDividerAbove(View view, RecyclerView parent) {
final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
- return holder.getAdapterPosition() == 0 &&
+ return holder.getAdapterPosition() == 0 && holder instanceof PreferenceViewHolder &&
((PreferenceViewHolder) holder).isDividerAllowedAbove();
}
private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
- final PreferenceViewHolder holder =
- (PreferenceViewHolder) parent.getChildViewHolder(view);
+ final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
+ final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder
+ && ((PreferenceViewHolder) holder).isDividerAllowedBelow();
+ if (!dividerAllowedBelow) {
+ return false;
+ }
boolean nextAllowed = true;
int index = parent.indexOfChild(view);
if (index < parent.getChildCount() - 1) {
final View nextView = parent.getChildAt(index + 1);
- final PreferenceViewHolder nextHolder =
- (PreferenceViewHolder) parent.getChildViewHolder(nextView);
- nextAllowed = nextHolder.isDividerAllowedAbove();
+ final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView);
+ nextAllowed = nextHolder instanceof PreferenceViewHolder
+ && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove();
}
- return nextAllowed && holder.isDividerAllowedBelow();
+ return nextAllowed;
}
public void setDivider(Drawable divider) {
diff --git a/v14/preference/src/android/support/v14/preference/SwitchPreference.java b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
index 1a46cc4..ac2a9c6 100644
--- a/v14/preference/src/android/support/v14/preference/SwitchPreference.java
+++ b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.content.res.TypedArrayUtils;
+import android.support.v7.preference.AndroidResources;
import android.support.v7.preference.PreferenceViewHolder;
import android.support.v7.preference.TwoStatePreference;
import android.util.AttributeSet;
@@ -121,7 +122,8 @@
* @param attrs Style attributes that differ from the default
*/
public SwitchPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.switchPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.switchPreferenceStyle,
+ android.R.attr.switchPreferenceStyle));
}
/**
@@ -136,7 +138,7 @@
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- View switchView = holder.findViewById(R.id.switchWidget);
+ View switchView = holder.findViewById(AndroidResources.ANDROID_R_SWITCH_WIDGET);
syncSwitchView(switchView);
syncSummaryView(holder);
}
diff --git a/v17/leanback/api/current.txt b/v17/leanback/api/current.txt
index f054a8d..c666649 100644
--- a/v17/leanback/api/current.txt
+++ b/v17/leanback/api/current.txt
@@ -485,6 +485,7 @@
public class PlaybackOverlayFragment extends android.support.v17.leanback.app.DetailsFragment {
ctor public PlaybackOverlayFragment();
+ method public void fadeOut();
method public int getBackgroundType();
method public android.support.v17.leanback.app.PlaybackOverlayFragment.OnFadeCompleteListener getFadeCompleteListener();
method public final android.support.v17.leanback.app.PlaybackOverlayFragment.InputEventHandler getInputEventHandler();
@@ -513,6 +514,7 @@
public class PlaybackOverlaySupportFragment extends android.support.v17.leanback.app.DetailsSupportFragment {
ctor public PlaybackOverlaySupportFragment();
+ method public void fadeOut();
method public int getBackgroundType();
method public android.support.v17.leanback.app.PlaybackOverlaySupportFragment.OnFadeCompleteListener getFadeCompleteListener();
method public final android.support.v17.leanback.app.PlaybackOverlaySupportFragment.InputEventHandler getInputEventHandler();
@@ -1531,6 +1533,10 @@
method public void setSecondaryLabels(java.lang.String[]);
}
+ public static class PlaybackControlsRow.PictureInPictureAction extends android.support.v17.leanback.widget.Action {
+ ctor public PlaybackControlsRow.PictureInPictureAction(android.content.Context);
+ }
+
public static class PlaybackControlsRow.PlayPauseAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
ctor public PlaybackControlsRow.PlayPauseAction(android.content.Context);
field public static int PAUSE;
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_pip.png b/v17/leanback/res/drawable-xhdpi/lb_ic_pip.png
new file mode 100644
index 0000000..1a59e47
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_pip.png
Binary files differ
diff --git a/v17/leanback/res/values-af/strings.xml b/v17/leanback/res/values-af/strings.xml
index a450df0..1167402 100644
--- a/v17/leanback/res/values-af/strings.xml
+++ b/v17/leanback/res/values-af/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Deaktiveer hoë gehalte"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktiveer onderskrifte"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Deaktiveer onderskrifte"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Voer prent in prentmodus in"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Voltooi"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Gaan voort"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-am/strings.xml b/v17/leanback/res/values-am/strings.xml
index dc048b3..56a623d 100644
--- a/v17/leanback/res/values-am/strings.xml
+++ b/v17/leanback/res/values-am/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ከፍተኛ ጥራትን አሰናክል"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ዝግ የምስል ስር ጽሑፍ አጻጻፍን አንቃ"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ዝግ የምስል ስር ጽሑፍ አጻጻፍን አሰናክል"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ስዕሉን በስዕል ሁነታ ውስጥ ያክሉ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ጨርስ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ቀጥል"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ar/strings.xml b/v17/leanback/res/values-ar/strings.xml
index c216199..84bba5f 100644
--- a/v17/leanback/res/values-ar/strings.xml
+++ b/v17/leanback/res/values-ar/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"تعطيل الجودة العالية"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"تمكين الترجمة المصاحبة"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"تعطيل الترجمة المصاحبة"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"إدخال صورة في وضع الصورة"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"إنهاء"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"متابعة"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-az-rAZ/strings.xml b/v17/leanback/res/values-az-rAZ/strings.xml
index 9d9de73..15ddc65 100644
--- a/v17/leanback/res/values-az-rAZ/strings.xml
+++ b/v17/leanback/res/values-az-rAZ/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Yüksək keyfiyyəti deaktiv edin"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Qapalı çəkilişi aktiv edin"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Qapalı çəkilişi deaktiv edin"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Şəkil içində Şəkil Rejiminə daxil olun"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Bitir"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Davam edin"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-b+sr+Latn/strings.xml b/v17/leanback/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..79afeb3
--- /dev/null
+++ b/v17/leanback/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2014 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="orb_search_action" msgid="5651268540267663887">"Radnja pretrage"</string>
+ <string name="lb_search_bar_hint" msgid="8325490927970116252">"Pretražite"</string>
+ <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Govorite da biste pretraživali"</string>
+ <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Pretražite <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite da biste pretražili <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
+ <string name="lb_playback_controls_play" msgid="731953341987346903">"Pusti"</string>
+ <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pauziraj"</string>
+ <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Premotaj unapred"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Premotaj unapred %1$dX"</string>
+ <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Premotaj unazad"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Premotaj unazad %1$dX"</string>
+ <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Preskoči sledeću"</string>
+ <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Preskoči prethodnu"</string>
+ <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Još radnji"</string>
+ <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Opozovi izbor palca nagore"</string>
+ <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Izaberi palac nagore"</string>
+ <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Opozovi izbor palca nadole"</string>
+ <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Izaberi palac nadole"</string>
+ <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Ne ponavljaj nijednu"</string>
+ <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Ponovi sve"</string>
+ <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Ponovi jednu"</string>
+ <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Omogući nasumičnu reprodukciju"</string>
+ <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Onemogući nasumičnu reprodukciju"</string>
+ <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Omogući visok kvalitet"</string>
+ <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Onemogući visok kvalitet"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Omogući titlove"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Onemogući titlove"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Uđi u režim Slika u slici"</string>
+ <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dovrši"</string>
+ <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Nastavi"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAPOČNITE"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Dalje"</string>
+</resources>
diff --git a/v17/leanback/res/values-bg/strings.xml b/v17/leanback/res/values-bg/strings.xml
index 4be5a2c..89114dc 100644
--- a/v17/leanback/res/values-bg/strings.xml
+++ b/v17/leanback/res/values-bg/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Деактивиране на високото качество"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Активиране на субтитрите"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Деактивиране на субтитрите"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Вход в режима „Картина в картина“"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Край"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Напред"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-bn-rBD/strings.xml b/v17/leanback/res/values-bn-rBD/strings.xml
index 82b7d57..d73964e 100644
--- a/v17/leanback/res/values-bn-rBD/strings.xml
+++ b/v17/leanback/res/values-bn-rBD/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"উচ্চ গুণমান অক্ষম করুন"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"সাবটাইটেল সক্ষম করুন"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"সাবটাইটেল অক্ষম করুন"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ছবি মোডে ছবি লগান"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"শেষ করুন"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"চালিয়ে যান"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-bs-rBA/strings.xml b/v17/leanback/res/values-bs-rBA/strings.xml
new file mode 100644
index 0000000..5349d50
--- /dev/null
+++ b/v17/leanback/res/values-bs-rBA/strings.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2014 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for orb_search_action (5651268540267663887) -->
+ <skip />
+ <!-- no translation found for lb_search_bar_hint (8325490927970116252) -->
+ <skip />
+ <!-- no translation found for lb_search_bar_hint_speech (5511270823320183816) -->
+ <skip />
+ <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+ <skip />
+ <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+ <skip />
+ <!-- no translation found for lb_control_display_fast_forward_multiplier (4541442045214207774) -->
+ <skip />
+ <!-- no translation found for lb_control_display_rewind_multiplier (3097220783222910245) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_fast_forward_multiplier (1058753672110224526) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_rewind_multiplier (1640629531440849942) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
+ <skip />
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Uđi u način rada Slika u slici"</string>
+ <!-- no translation found for lb_guidedaction_finish_title (4015190340667946245) -->
+ <skip />
+ <!-- no translation found for lb_guidedaction_continue_title (8842094924543063706) -->
+ <skip />
+ <!-- no translation found for lb_date_separator (2440386660906697298) -->
+ <skip />
+ <!-- no translation found for lb_time_separator (2763247350845477227) -->
+ <skip />
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAPOČNITE"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Sljedeća"</string>
+</resources>
diff --git a/v17/leanback/res/values-ca/strings.xml b/v17/leanback/res/values-ca/strings.xml
index f0e0661..ca2867a 100644
--- a/v17/leanback/res/values-ca/strings.xml
+++ b/v17/leanback/res/values-ca/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desactiva l\'alta qualitat"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activa els subtítols tancats"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desactiva els subtítols tancats"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entra al mode d\'imatge en imatge"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalitza"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continua"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-cs/strings.xml b/v17/leanback/res/values-cs/strings.xml
index 1dd0bcc..9bb0026 100644
--- a/v17/leanback/res/values-cs/strings.xml
+++ b/v17/leanback/res/values-cs/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Vypnout vysokou kvalitu"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Zapnout titulky"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Vypnout titulky"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Přejít do režimu Obraz v obraze"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dokončit"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Pokračovat"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-da/strings.xml b/v17/leanback/res/values-da/strings.xml
index 454df06..63304a3 100644
--- a/v17/leanback/res/values-da/strings.xml
+++ b/v17/leanback/res/values-da/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Deaktiver høj kvalitet"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivér undertekster"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Deaktiver undertekster"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Tilføj billedet i billedtilstand"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Afslut"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsæt"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-de/strings.xml b/v17/leanback/res/values-de/strings.xml
index e7b95ac..17d05ee 100644
--- a/v17/leanback/res/values-de/strings.xml
+++ b/v17/leanback/res/values-de/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Hohe Qualität deaktivieren"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Untertitel aktivieren"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Untertitel deaktivieren"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Bild-in-Bild-Modus aktivieren"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Fertigstellen"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Weiter"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-el/strings.xml b/v17/leanback/res/values-el/strings.xml
index 659bed4..ea120b1 100644
--- a/v17/leanback/res/values-el/strings.xml
+++ b/v17/leanback/res/values-el/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Απενεργοποίηση Υψηλής ποιότητας"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ενεργοποίηση υποτίτλων"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Απενεργοποίηση υποτίτλων"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Εισαγωγή εικόνας στη Λειτουργία εικόνας"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Τέλος"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Συνέχεια"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-en-rAU/strings.xml b/v17/leanback/res/values-en-rAU/strings.xml
index 9b1778f..5b5fd2e 100644
--- a/v17/leanback/res/values-en-rAU/strings.xml
+++ b/v17/leanback/res/values-en-rAU/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disable High Quality"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Enable Closed Captioning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disable Closed Captioning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Enter Picture In Picture Mode"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finish"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continue"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-en-rGB/strings.xml b/v17/leanback/res/values-en-rGB/strings.xml
index 9b1778f..5b5fd2e 100644
--- a/v17/leanback/res/values-en-rGB/strings.xml
+++ b/v17/leanback/res/values-en-rGB/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disable High Quality"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Enable Closed Captioning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disable Closed Captioning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Enter Picture In Picture Mode"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finish"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continue"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-en-rIN/strings.xml b/v17/leanback/res/values-en-rIN/strings.xml
index 9b1778f..5b5fd2e 100644
--- a/v17/leanback/res/values-en-rIN/strings.xml
+++ b/v17/leanback/res/values-en-rIN/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disable High Quality"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Enable Closed Captioning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disable Closed Captioning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Enter Picture In Picture Mode"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finish"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continue"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-es-rUS/strings.xml b/v17/leanback/res/values-es-rUS/strings.xml
index 1574470..6cdd4b4 100644
--- a/v17/leanback/res/values-es-rUS/strings.xml
+++ b/v17/leanback/res/values-es-rUS/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Inhabilitar calidad alta"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Habilitar subtítulos"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Inhabilitar subtítulos"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activar el modo Imagen en imagen"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizar"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-es/strings.xml b/v17/leanback/res/values-es/strings.xml
index 6420a61..09d10f2 100644
--- a/v17/leanback/res/values-es/strings.xml
+++ b/v17/leanback/res/values-es/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Inhabilitar alta calidad"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Habilitar subtítulos"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Inhabilitar subtítulos"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activar modo Imagen en imagen"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizar"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-et-rEE/strings.xml b/v17/leanback/res/values-et-rEE/strings.xml
index a223081..016bf42 100644
--- a/v17/leanback/res/values-et-rEE/strings.xml
+++ b/v17/leanback/res/values-et-rEE/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Keela kõrgkvaliteetne taasesitus"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Luba subtiitrid"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Keela subtiitrid"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Sisene režiimi Pilt pildis"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Lõpeta"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jätka"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-eu-rES/strings.xml b/v17/leanback/res/values-eu-rES/strings.xml
index dfec00a..f081a12 100644
--- a/v17/leanback/res/values-eu-rES/strings.xml
+++ b/v17/leanback/res/values-eu-rES/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desgaitu kalitate handiko erreprodukzioa"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Gaitu azpitituluak"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desgaitu azpitituluak"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Aktibatu \"Argazkia argazkian\" modua"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Amaitu"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jarraitu"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-fa/strings.xml b/v17/leanback/res/values-fa/strings.xml
index c4cc00e..1d3e007 100644
--- a/v17/leanback/res/values-fa/strings.xml
+++ b/v17/leanback/res/values-fa/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"غیرفعال کردن کیفیت بالا"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"فعال کردن زیرنویس"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"غیرفعال کردن زیرنویس"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"وارد حالت تصویر در تصویر شوید"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"پایان"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ادامه"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-fi/strings.xml b/v17/leanback/res/values-fi/strings.xml
index 89cc6e4..747b10c 100644
--- a/v17/leanback/res/values-fi/strings.xml
+++ b/v17/leanback/res/values-fi/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Poista korkea laatu käytöstä"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ota tekstitys käyttöön"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Poista tekstitys käytöstä"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vaihda kuva kuvassa ‑tilaan"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Valmis"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jatka"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-fr-rCA/strings.xml b/v17/leanback/res/values-fr-rCA/strings.xml
index fc4ecbf..5da936f 100644
--- a/v17/leanback/res/values-fr-rCA/strings.xml
+++ b/v17/leanback/res/values-fr-rCA/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Désactiver la lecture haute qualité"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activer le sous-titrage"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Désactiver le sous-titrage"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activer le mode Incrustation d\'image"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Terminer"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuer"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-fr/strings.xml b/v17/leanback/res/values-fr/strings.xml
index 34c915a..57a649d 100644
--- a/v17/leanback/res/values-fr/strings.xml
+++ b/v17/leanback/res/values-fr/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Désactiver la haute qualité"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activer les sous-titres"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Désactiver les sous-titres"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activer le mode PIP"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Terminer"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuer"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-gl-rES/strings.xml b/v17/leanback/res/values-gl-rES/strings.xml
index c73bdc6..eebba2a 100644
--- a/v17/leanback/res/values-gl-rES/strings.xml
+++ b/v17/leanback/res/values-gl-rES/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desactivar alta calidade"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activar subtítulos"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desactivar subtítulos"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activar o modo Imaxe superposta"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizar"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-gu-rIN/strings.xml b/v17/leanback/res/values-gu-rIN/strings.xml
index 5070456..df25bbe 100644
--- a/v17/leanback/res/values-gu-rIN/strings.xml
+++ b/v17/leanback/res/values-gu-rIN/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ઉચ્ચ ગુણવત્તા અક્ષમ કરો"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ઉપશીર્ષક સક્ષમ કરો"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"વિગતવાર ઉપશીર્ષકોને અક્ષમ કરો"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ચિત્ર મોડમાં ચિત્ર દાખલ કરો"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"સમાપ્ત કરો"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ચાલુ રાખો"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-hi/strings.xml b/v17/leanback/res/values-hi/strings.xml
index 71e1b06..9432182 100644
--- a/v17/leanback/res/values-hi/strings.xml
+++ b/v17/leanback/res/values-hi/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"उच्च गुणवत्ता अक्षम करें"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"उपशीर्षक सक्षम करें"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"उपशीर्षक अक्षम करें"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"चित्र मोड में चित्र डालें"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"समाप्त करें"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"जारी रखें"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-hr/strings.xml b/v17/leanback/res/values-hr/strings.xml
index e5c77da..6c8fde0 100644
--- a/v17/leanback/res/values-hr/strings.xml
+++ b/v17/leanback/res/values-hr/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Onemogući visoku kvalitetu"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Omogući titlove"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Onemogući titlove"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Unos slike u načinu slike"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Završi"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Nastavi"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-hu/strings.xml b/v17/leanback/res/values-hu/strings.xml
index 89b28b5..9662419 100644
--- a/v17/leanback/res/values-hu/strings.xml
+++ b/v17/leanback/res/values-hu/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Jó minőségű lejátszás letiltása"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Feliratok engedélyezése"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Feliratok letiltása"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Kép a képben mód indítása"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Befejezés"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Folytatás"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-hy-rAM/strings.xml b/v17/leanback/res/values-hy-rAM/strings.xml
index 2be3c57..2ea16d1 100644
--- a/v17/leanback/res/values-hy-rAM/strings.xml
+++ b/v17/leanback/res/values-hy-rAM/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Անջատել բարձր որակը"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Միացնել խորագրերը"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Անջատել խորագրերը"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Մուտք «Նկար նկարի մեջ» ռեժիմ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Վերջ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Շարունակել"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-in/strings.xml b/v17/leanback/res/values-in/strings.xml
index f66a247..14c2438 100644
--- a/v17/leanback/res/values-in/strings.xml
+++ b/v17/leanback/res/values-in/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Nonaktifkan Kualitas Tinggi"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktifkan Pembuatan Teks"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Nonaktifkan Pembuatan Teks"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Masukkan Foto Dalam Mode Foto"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Selesai"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Lanjutkan"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-is-rIS/strings.xml b/v17/leanback/res/values-is-rIS/strings.xml
index 03cb9f0..ec83043 100644
--- a/v17/leanback/res/values-is-rIS/strings.xml
+++ b/v17/leanback/res/values-is-rIS/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Slökkva á miklum gæðum"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Kveikja á skjátextum"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Slökkva á skjátextum"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Skoða mynd í myndsniði"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Ljúka"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Halda áfram"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-it/strings.xml b/v17/leanback/res/values-it/strings.xml
index 2318948..ffc0788 100644
--- a/v17/leanback/res/values-it/strings.xml
+++ b/v17/leanback/res/values-it/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disattiva alta qualità"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Attiva sottotitoli"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disattiva sottotitoli"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Attiva modalità Picture-in-picture"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Fine"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continua"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-iw/strings.xml b/v17/leanback/res/values-iw/strings.xml
index a390964..986a6db 100644
--- a/v17/leanback/res/values-iw/strings.xml
+++ b/v17/leanback/res/values-iw/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"השבת איכות גבוהה"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"הפעל כתוביות"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"השבת כתוביות"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"הזן את התמונה במצב תמונה"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"סיום"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"המשך"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ja/strings.xml b/v17/leanback/res/values-ja/strings.xml
index 74d8d3e..9914220 100644
--- a/v17/leanback/res/values-ja/strings.xml
+++ b/v17/leanback/res/values-ja/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"高品質を無効にする"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"字幕を有効にする"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"字幕を無効にする"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"PIP モードに移動"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完了"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"続行"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ka-rGE/strings.xml b/v17/leanback/res/values-ka-rGE/strings.xml
index ef03a42..4d52ce9 100644
--- a/v17/leanback/res/values-ka-rGE/strings.xml
+++ b/v17/leanback/res/values-ka-rGE/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"მაღალი ხარისხის გამორთვა"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"დახურული წარწერების ჩართვა"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"დახურული წარწერების გაუქმება"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"რეჟიმზე „სურათი სურათში“ გადასვლა"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"დასრულება"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"გაგრძელება"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-kk-rKZ/strings.xml b/v17/leanback/res/values-kk-rKZ/strings.xml
index 9bea95b..850382e 100644
--- a/v17/leanback/res/values-kk-rKZ/strings.xml
+++ b/v17/leanback/res/values-kk-rKZ/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Жоғары сапаны өшіру"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Жасырын титрлерді қосу"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Жасырын титрлерді өшіру"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Сурет ішіндегі сурет режиміне кіру"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Аяқтау"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Жалғастыру"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-km-rKH/strings.xml b/v17/leanback/res/values-km-rKH/strings.xml
index 7bcfcdd..d67aabb 100644
--- a/v17/leanback/res/values-km-rKH/strings.xml
+++ b/v17/leanback/res/values-km-rKH/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"បិទគុណភាពខ្ពស់"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"បើកការដាក់ចំណងដែលបានបិទ"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"បិទការដាក់ចំណងដែលបានបិទ"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"បញ្ចូលរូបភាពនៅក្នុងរបៀបរូបភាព"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"បញ្ចប់"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"បន្ត"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-kn-rIN/strings.xml b/v17/leanback/res/values-kn-rIN/strings.xml
index 7660eea..692b0a6 100644
--- a/v17/leanback/res/values-kn-rIN/strings.xml
+++ b/v17/leanback/res/values-kn-rIN/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ಹೆಚ್ಚು ಗುಣಮಟ್ಟವನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ಚಿತ್ರವನ್ನು ಚಿತ್ರ ಮೋಡ್ನಲ್ಲಿ ಪ್ರವೇಶಿಸಿ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ಪೂರ್ಣಗೊಳಿಸು"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ಮುಂದುವರಿಸು"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ko/strings.xml b/v17/leanback/res/values-ko/strings.xml
index 63e265c..89d961b 100644
--- a/v17/leanback/res/values-ko/strings.xml
+++ b/v17/leanback/res/values-ko/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"고화질 사용 중지"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"자막 사용 설정"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"자막 사용 중지"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"사진 모드에서 사진 입력"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"완료"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"계속"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ky-rKG/strings.xml b/v17/leanback/res/values-ky-rKG/strings.xml
index 70b3f5e..0dcd67c 100644
--- a/v17/leanback/res/values-ky-rKG/strings.xml
+++ b/v17/leanback/res/values-ky-rKG/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Жогорку сапатты өчүрүү"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Жабык субтитрлерди иштетүү"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Жабык субтитрлерди өчүрүү"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Сүрөт режиминде сүрөт киргизүү"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Бүтүрүү"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Улантуу"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-lo-rLA/strings.xml b/v17/leanback/res/values-lo-rLA/strings.xml
index 0e5c840..27ba937 100644
--- a/v17/leanback/res/values-lo-rLA/strings.xml
+++ b/v17/leanback/res/values-lo-rLA/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ປິດນຳໃຊ້ການຫຼິ້ນດ້ວຍຄຸນນະພາບສູງ"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ເປີດນຳໃຊ້ຄຳບັນຍາຍແບບປິດ"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ປິດນຳໃຊ້ຄຳບັນຍາຍແບບປິດ"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ປ້ອນຮູບພາບໃນໂໝດຮູບພາບ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ສໍາເລັດ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ສືບຕໍ່"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-lt/strings.xml b/v17/leanback/res/values-lt/strings.xml
index 0183026..0ce4deb 100644
--- a/v17/leanback/res/values-lt/strings.xml
+++ b/v17/leanback/res/values-lt/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Išjungti aukštą kokybę"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Įgalinti subtitrus"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Išjungti subtitrus"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Įjungti vaizdo vaizde režimą"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Baigti"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Tęsti"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-lv/strings.xml b/v17/leanback/res/values-lv/strings.xml
index d453eac..5abc6e6 100644
--- a/v17/leanback/res/values-lv/strings.xml
+++ b/v17/leanback/res/values-lv/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Atspējot augstas kvalitātes vienumu atskaņošanu"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Iespējot slēgtos parakstus"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Atspējot slēgtos parakstus"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Aktivizēt režīmu Attēls attēlā"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Pabeigt"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Turpināt"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-mk-rMK/strings.xml b/v17/leanback/res/values-mk-rMK/strings.xml
index b3f76e0..3f482f6 100644
--- a/v17/leanback/res/values-mk-rMK/strings.xml
+++ b/v17/leanback/res/values-mk-rMK/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Оневозможи висок квалитет"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Овозможи затворено објаснување"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Оневозможи затворено објаснување"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Влези во режимот „Слика во слика“"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Заврши"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Продолжи"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-ml-rIN/strings.xml b/v17/leanback/res/values-ml-rIN/strings.xml
index d90e4e9..5c24bd8 100644
--- a/v17/leanback/res/values-ml-rIN/strings.xml
+++ b/v17/leanback/res/values-ml-rIN/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ഉയർന്ന നിലവാരം പ്രവർത്തനരഹിതമാക്കുക"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"അടച്ച അടിക്കുറിപ്പ് നൽകൽ പ്രവർത്തനക്ഷമമാക്കുക"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"അടച്ച അടിക്കുറിപ്പ് നൽകൽ പ്രവർത്തനരഹിതമാക്കുക"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"\'ചിത്രത്തിനുള്ളിൽ ചിത്രം\' മോഡിലേക്ക് പ്രവേശിക്കുക"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"പൂര്ത്തിയാക്കുക"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"തുടരുക"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-mn-rMN/strings.xml b/v17/leanback/res/values-mn-rMN/strings.xml
index f0acfeb..8910e54 100644
--- a/v17/leanback/res/values-mn-rMN/strings.xml
+++ b/v17/leanback/res/values-mn-rMN/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Өндөр чанарыг идэвхгүйжүүлэх"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Текст тайлбарыг идэвхжүүлэх"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Текст тайлбарыг идэвхгүйжүүлэх"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Зургийн горимд зураг оруулна уу"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Дуусгах"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Үргэлжлүүлэх"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-mr-rIN/strings.xml b/v17/leanback/res/values-mr-rIN/strings.xml
index 15222be..03160f4 100644
--- a/v17/leanback/res/values-mr-rIN/strings.xml
+++ b/v17/leanback/res/values-mr-rIN/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"उच्च गुणवत्ता अक्षम करा"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"उपशीर्षके सक्षम करा"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"उपशीर्षके अक्षम करा"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"चित्र मोडमध्ये चित्र प्रविष्ट करा"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"समाप्त"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"सुरू ठेवा"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ms-rMY/strings.xml b/v17/leanback/res/values-ms-rMY/strings.xml
index d2a73c6..c61d914 100644
--- a/v17/leanback/res/values-ms-rMY/strings.xml
+++ b/v17/leanback/res/values-ms-rMY/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Lumpuhkan Kualiti Tinggi"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Dayakan Kapsyen Tertutup"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Lumpuhkan Kapsyen Tertutup"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Masukkan Gambar Dalam Mod Gambar"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Selesai"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Teruskan"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-my-rMM/strings.xml b/v17/leanback/res/values-my-rMM/strings.xml
index df3ef0c..f80c236 100644
--- a/v17/leanback/res/values-my-rMM/strings.xml
+++ b/v17/leanback/res/values-my-rMM/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"အရည်အသွေးကောင်းအား ပိတ်ထားရန်"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"စာတမ်းထိုး ဖွင့်ရန်"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"စာတမ်းထိုးအား ပိတ်ထားရန်"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ဓာတ်ပုံမုဒ်တွင် ဓာတ်ပုံထည့်ပါ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ပြီးပြီ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ဆက်လုပ်ရန်"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-nb/strings.xml b/v17/leanback/res/values-nb/strings.xml
index dc0b3c4..ebf9870 100644
--- a/v17/leanback/res/values-nb/strings.xml
+++ b/v17/leanback/res/values-nb/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Deaktiver høy kvalitet"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivér teksting"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Deaktiver teksting"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Slå på modusen Bilde-i-bilde"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Fullfør"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsett"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-ne-rNP/strings.xml b/v17/leanback/res/values-ne-rNP/strings.xml
index 83d3e53..538325b 100644
--- a/v17/leanback/res/values-ne-rNP/strings.xml
+++ b/v17/leanback/res/values-ne-rNP/strings.xml
@@ -48,6 +48,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"उच्च गुणस्तर असक्षम"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"बन्द क्याप्सनहरु सक्षम"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"बन्द क्याप्सनहरु असक्षम"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"चित्रलाई चित्र मोडमा प्रविष्ट गर्नुहोस्"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"समाप्त गर्नुहोस्"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"जारी राख्नुहोस्"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-nl/strings.xml b/v17/leanback/res/values-nl/strings.xml
index 0eceed4..2379624 100644
--- a/v17/leanback/res/values-nl/strings.xml
+++ b/v17/leanback/res/values-nl/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Hoge kwaliteit uitschakelen"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ondertiteling inschakelen"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Ondertiteling uitschakelen"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Beeld-in-beeld-modus openen"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Voltooien"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Doorgaan"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"-"</string>
diff --git a/v17/leanback/res/values-pa-rIN/strings.xml b/v17/leanback/res/values-pa-rIN/strings.xml
index db3b36b..80677a2 100644
--- a/v17/leanback/res/values-pa-rIN/strings.xml
+++ b/v17/leanback/res/values-pa-rIN/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ਉੱਚ ਗੁਣਵੱਤਾ ਨੂੰ ਅਸਮਰੱਥ ਬਣਾਓ"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ਬੰਦ ਕੈਪਸ਼ਨਿੰਗ ਸਮਰੱਥ ਬਣਾਓ"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ਬੰਦ ਕੈਪਸ਼ਨਿੰਗ ਅਸਮਰੱਥ ਬਣਾਓ"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ਤਸਵੀਰ ਮੋਡ ਵਿੱਚ ਤਸਵੀਰ ਦਾਖਲ ਕਰੋ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ਖ਼ਤਮ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ਜਾਰੀ ਰੱਖੋ"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-pl/strings.xml b/v17/leanback/res/values-pl/strings.xml
index 5a5d42d..1a685fb 100644
--- a/v17/leanback/res/values-pl/strings.xml
+++ b/v17/leanback/res/values-pl/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Wyłącz wysoką jakość"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Włącz napisy"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Wyłącz napisy"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Włącz tryb obrazu w obrazie"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Zakończ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Dalej"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-pt-rBR/strings.xml b/v17/leanback/res/values-pt-rBR/strings.xml
index 96e4399..4418a44 100644
--- a/v17/leanback/res/values-pt-rBR/strings.xml
+++ b/v17/leanback/res/values-pt-rBR/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desativar alta qualidade"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ativar closed captioning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desativar closed captioning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entrar no modo Picture in Picture"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Concluir"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-pt-rPT/strings.xml b/v17/leanback/res/values-pt-rPT/strings.xml
index aae336f..7ddbd7e 100644
--- a/v17/leanback/res/values-pt-rPT/strings.xml
+++ b/v17/leanback/res/values-pt-rPT/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desativar alta qualidade"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ativar legendas"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desativar legendas"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entrar no modo Imagem na imagem"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Concluir"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-pt/strings.xml b/v17/leanback/res/values-pt/strings.xml
index 96e4399..4418a44 100644
--- a/v17/leanback/res/values-pt/strings.xml
+++ b/v17/leanback/res/values-pt/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desativar alta qualidade"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ativar closed captioning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desativar closed captioning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entrar no modo Picture in Picture"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Concluir"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ro/strings.xml b/v17/leanback/res/values-ro/strings.xml
index a270839..47a4cfa 100644
--- a/v17/leanback/res/values-ro/strings.xml
+++ b/v17/leanback/res/values-ro/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Dezactivează calitatea înaltă"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activează subtitrările"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Dezactivează subtitrările"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activați modul Picture-in-Picture"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizați"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuați"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ru/strings.xml b/v17/leanback/res/values-ru/strings.xml
index ba7dd82..88a0c27 100644
--- a/v17/leanback/res/values-ru/strings.xml
+++ b/v17/leanback/res/values-ru/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Отключить высокое качество."</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Включить субтитры."</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Отключить субтитры."</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Включить режим \"Картинка в картинке\"."</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Готово"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Далее"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-si-rLK/strings.xml b/v17/leanback/res/values-si-rLK/strings.xml
index 267ed12..1af8590 100644
--- a/v17/leanback/res/values-si-rLK/strings.xml
+++ b/v17/leanback/res/values-si-rLK/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"උපරිම ගුණත්වය අබල කරන ලදි"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"වැසුණු ශිර්ෂ කිරීම සබල කරන ලදි"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"වැසුණු ශිර්ෂ කිරීම අබල කරන ලදි"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"පින්තූරය-තුළ-පින්තූරය ප්රකාරයට ඇතුළු වන්න"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"අවසානය"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"දිගටම කර ගෙන යන්න"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sk/strings.xml b/v17/leanback/res/values-sk/strings.xml
index 6107eeb..8556c91 100644
--- a/v17/leanback/res/values-sk/strings.xml
+++ b/v17/leanback/res/values-sk/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Zakázať médiá vo vysokej kvalite"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Zapnúť skryté titulky"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Vypnúť skryté titulky"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vložiť obrázok v režime obrázka"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dokončiť"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Pokračovať"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sl/strings.xml b/v17/leanback/res/values-sl/strings.xml
index bfa620d..738b2ab 100644
--- a/v17/leanback/res/values-sl/strings.xml
+++ b/v17/leanback/res/values-sl/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Onemogoči visoko kakovost"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Omogoči podnapise"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Onemogoči podnapise"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vklop načina za sliko v sliki"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dokončaj"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Naprej"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sq-rAL/strings.xml b/v17/leanback/res/values-sq-rAL/strings.xml
index 8f453d5..73d6e26 100644
--- a/v17/leanback/res/values-sq-rAL/strings.xml
+++ b/v17/leanback/res/values-sq-rAL/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Çaktivizo \"Cilësinë e lartë\""</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivizo titrat"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Çaktivizo titrat me sekuencë kohore"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Fut një fotografi në modalitetin e fotografisë"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Përfundo"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Vazhdo"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sr/strings.xml b/v17/leanback/res/values-sr/strings.xml
index a63a28f..96c34b2 100644
--- a/v17/leanback/res/values-sr/strings.xml
+++ b/v17/leanback/res/values-sr/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Онемогући висок квалитет"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Омогући титлове"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Онемогући титлове"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Уђи у режим Слика у слици"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Доврши"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Настави"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sv/strings.xml b/v17/leanback/res/values-sv/strings.xml
index 34452c2..d6d9245 100644
--- a/v17/leanback/res/values-sv/strings.xml
+++ b/v17/leanback/res/values-sv/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Inaktivera hög kvalitet"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivera textning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Inaktivera textning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Ange läget Bild-i-bild"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Slutför"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsätt"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sw/strings.xml b/v17/leanback/res/values-sw/strings.xml
index ddf7cc0..c770586 100644
--- a/v17/leanback/res/values-sw/strings.xml
+++ b/v17/leanback/res/values-sw/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Zima Ubora wa Juu"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Washa manukuu"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Zima manukuu"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Weka Picha Katika Hali ya Picha"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Kamilisha"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Endelea"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ta-rIN/strings.xml b/v17/leanback/res/values-ta-rIN/strings.xml
index a5e14ea..8f64ca4 100644
--- a/v17/leanback/res/values-ta-rIN/strings.xml
+++ b/v17/leanback/res/values-ta-rIN/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"உயர் தரத்தை முடக்கு"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"விரிவான வசனங்களை இயக்கு"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"விரிவான வசனங்களை முடக்கு"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"பிக்ச்சர் இன் பிக்ச்சர் பயன்முறைக்குச் செல்"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"முடி"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"தொடர்க"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-te-rIN/strings.xml b/v17/leanback/res/values-te-rIN/strings.xml
index bf9f96b..13856be 100644
--- a/v17/leanback/res/values-te-rIN/strings.xml
+++ b/v17/leanback/res/values-te-rIN/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"అధిక నాణ్యతను నిలిపివేయి"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"సంవృత శీర్షికలను ప్రారంభించు"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"సంవృత శీర్షికలను నిలిపివేయి"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"చిత్రంలో చిత్రం మోడ్లోకి ప్రవేశించండి"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ముగించు"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"కొనసాగించు"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-th/strings.xml b/v17/leanback/res/values-th/strings.xml
index c239b7a..dcd027d 100644
--- a/v17/leanback/res/values-th/strings.xml
+++ b/v17/leanback/res/values-th/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ปิดใช้คุณภาพสูง"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"เปิดใช้คำบรรยาย"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ปิดใช้คำบรรยาย"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"เข้าสู่โหมดการแสดงผลหลายแหล่งพร้อมกัน"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"เสร็จสิ้น"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ต่อไป"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-tl/strings.xml b/v17/leanback/res/values-tl/strings.xml
index d56150e..4715c92 100644
--- a/v17/leanback/res/values-tl/strings.xml
+++ b/v17/leanback/res/values-tl/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"I-disable ang Mataas na Kalidad"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"I-enable ang Paglalagay ng Subtitle"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"I-disable ang Paglalagay ng Subtitle"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Pumasok sa Picture In Picture Mode"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Tapusin"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Magpatuloy"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-tr/strings.xml b/v17/leanback/res/values-tr/strings.xml
index eaf7950..f61ca42 100644
--- a/v17/leanback/res/values-tr/strings.xml
+++ b/v17/leanback/res/values-tr/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Yüksek Kalitede Oynatmayı Devre Dışı Bırak"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Altyazıları Etkinleştir"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Altyazıları Devre Dışı Bırak"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Resim İçinde Resim Moduna Geç"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Son"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Devam"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-uk/strings.xml b/v17/leanback/res/values-uk/strings.xml
index f539de5..52b4655 100644
--- a/v17/leanback/res/values-uk/strings.xml
+++ b/v17/leanback/res/values-uk/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Вимкнути високу якість"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Увімкнути субтитри"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Вимкнути субтитри"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Перейти в режим \"Картинка в картинці\""</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Закінчити"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Продовжити"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-ur-rPK/strings.xml b/v17/leanback/res/values-ur-rPK/strings.xml
index 706157e..95abc14 100644
--- a/v17/leanback/res/values-ur-rPK/strings.xml
+++ b/v17/leanback/res/values-ur-rPK/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"اعلی معیار کو غیر فعال کریں"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"سب ٹائٹلز کو فعال کریں"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"سب ٹائٹلز کو غیر فعال کریں"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"\'تصویر میں تصویر موڈ\' میں داخل ہوں"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"مکمل کریں"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"جاری رکھیں"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-uz-rUZ/strings.xml b/v17/leanback/res/values-uz-rUZ/strings.xml
index a608570..99668e1 100644
--- a/v17/leanback/res/values-uz-rUZ/strings.xml
+++ b/v17/leanback/res/values-uz-rUZ/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Yuqori sifatni o‘chirib qo‘yish"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Taglavhalarni yoqish"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Taglavhalarni o‘chirib qo‘yish"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Tasvir ichida tasvir rejimiga kirish"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Tugatish"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Davom etish"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-vi/strings.xml b/v17/leanback/res/values-vi/strings.xml
index 5576b47..8c543e7 100644
--- a/v17/leanback/res/values-vi/strings.xml
+++ b/v17/leanback/res/values-vi/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Tắt chế độ chất lượng cao"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Bật phụ đề"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Tắt phụ đề"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vào ảnh ở chế độ ảnh"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Hoàn tất"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Tiếp tục"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-zh-rCN/strings.xml b/v17/leanback/res/values-zh-rCN/strings.xml
index 86fa1d5..8d5da49 100644
--- a/v17/leanback/res/values-zh-rCN/strings.xml
+++ b/v17/leanback/res/values-zh-rCN/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"关闭高画质模式"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"开启字幕"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"关闭字幕"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"进入画中画模式"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完成"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"继续"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-zh-rHK/strings.xml b/v17/leanback/res/values-zh-rHK/strings.xml
index 7709e9a..30cf3df 100644
--- a/v17/leanback/res/values-zh-rHK/strings.xml
+++ b/v17/leanback/res/values-zh-rHK/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"停用高畫質"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"啟用字幕"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"停用字幕"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"進入「畫中畫模式」"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完成"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"繼續"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-zh-rTW/strings.xml b/v17/leanback/res/values-zh-rTW/strings.xml
index de53c63..9334ea0 100644
--- a/v17/leanback/res/values-zh-rTW/strings.xml
+++ b/v17/leanback/res/values-zh-rTW/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"停用高品質播放"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"啟用字幕"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"停用字幕"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"進入子母畫面模式"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完成"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"繼續"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-zu/strings.xml b/v17/leanback/res/values-zu/strings.xml
index aefea61..6590d69 100644
--- a/v17/leanback/res/values-zu/strings.xml
+++ b/v17/leanback/res/values-zu/strings.xml
@@ -46,6 +46,7 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Khubaza ikhwalithi ephezulu"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Nika amandla imibhalo engezansi"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Khubaza imihbalo engezansi"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Ngena isithombe kumodi yesithombe"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Qeda"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Qhubeka"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
index cde7a2d..cc99dde 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -166,6 +166,7 @@
<attr name="shuffle" format="reference"/>
<attr name="high_quality" format="reference"/>
<attr name="closed_captioning" format="reference"/>
+ <attr name="picture_in_picture" format="reference"/>
</declare-styleable>
<declare-styleable name="lbSlide">
diff --git a/v17/leanback/res/values/ids.xml b/v17/leanback/res/values/ids.xml
index 98ac1fd..ab7f568 100644
--- a/v17/leanback/res/values/ids.xml
+++ b/v17/leanback/res/values/ids.xml
@@ -34,5 +34,6 @@
<item type="id" name="lb_control_shuffle" />
<item type="id" name="lb_control_high_quality" />
<item type="id" name="lb_control_closed_captioning" />
+ <item type="id" name="lb_control_picture_in_picture" />
</resources>
diff --git a/v17/leanback/res/values/strings.xml b/v17/leanback/res/values/strings.xml
index 212b11d..62c09f8 100644
--- a/v17/leanback/res/values/strings.xml
+++ b/v17/leanback/res/values/strings.xml
@@ -74,6 +74,8 @@
<string name="lb_playback_controls_closed_captioning_enable">Enable Closed Captioning</string>
<!-- Talkback label for the control button to disable closed captioning -->
<string name="lb_playback_controls_closed_captioning_disable">Disable Closed Captioning</string>
+ <!-- Talkback label for the control button to enter picture in picture mode -->
+ <string name="lb_playback_controls_picture_in_picture">Enter Picture In Picture Mode</string>
<!-- Title of standard Finish action for GuidedStepFragment -->
<string name="lb_guidedaction_finish_title">Finish</string>
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index 8a47870..3d49ae4 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -371,6 +371,7 @@
<item name="shuffle">@drawable/lb_ic_shuffle</item>
<item name="high_quality">@drawable/lb_ic_hq</item>
<item name="closed_captioning">@drawable/lb_ic_cc</item>
+ <item name="picture_in_picture">@drawable/lb_ic_pip</item>
</style>
<!-- Style for the main container view in a GuidanceStylist's default layout. -->
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
index 46e91c8..861c9c8 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
@@ -295,6 +295,17 @@
}
}
+ /**
+ * Fades out the playback overlay immediately.
+ */
+ public void fadeOut() {
+ if (!mFadingEnabled) {
+ return;
+ }
+ mHandler.removeMessages(START_FADE_OUT);
+ fade(false);
+ }
+
private boolean areControlsHidden() {
return mFadingStatus == IDLE && mBgAlpha == 0;
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
index 58433ef..a2306a3 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
@@ -297,6 +297,17 @@
}
}
+ /**
+ * Fades out the playback overlay immediately.
+ */
+ public void fadeOut() {
+ if (!mFadingEnabled) {
+ return;
+ }
+ mHandler.removeMessages(START_FADE_OUT);
+ fade(false);
+ }
+
private boolean areControlsHidden() {
return mFadingStatus == IDLE && mBgAlpha == 0;
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
index 60327f2..b1cf2f9 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
@@ -321,6 +321,23 @@
}
/**
+ * An action displaying an icon for picture-in-picture.
+ */
+ public static class PictureInPictureAction extends Action {
+ /**
+ * Constructor
+ * @param context Context used for loading resources.
+ */
+ public PictureInPictureAction(Context context) {
+ super(R.id.lb_control_picture_in_picture);
+ setIcon(getStyledDrawable(context,
+ R.styleable.lbPlaybackControlsActionIcons_picture_in_picture));
+ setLabel1(context.getString(R.string.lb_playback_controls_picture_in_picture));
+ addKeyCode(KeyEvent.KEYCODE_WINDOW);
+ }
+ }
+
+ /**
* An action displaying an icon for "more actions".
*/
public static class MoreActions extends Action {
diff --git a/v4/Android.mk b/v4/Android.mk
index 42b3ba2..75bda5e 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -231,6 +231,18 @@
# -----------------------------------------------------------------------
+# A helper sub-library that makes direct use of V24 APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-api24
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, api24)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api23
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+support_module_src_files += $(LOCAL_SRC_FILES)
+
+# -----------------------------------------------------------------------
+
# Here is the final static library that apps can link against.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v4
@@ -238,7 +250,7 @@
LOCAL_AIDL_INCLUDES := frameworks/support/v4/java
LOCAL_SRC_FILES := $(call all-java-files-under, java) \
$(call all-Iaidl-files-under, java)
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api23
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api24
include $(BUILD_STATIC_JAVA_LIBRARY)
support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v4/NOTICES.md b/v4/NOTICES.md
new file mode 100644
index 0000000..8665d5d
--- /dev/null
+++ b/v4/NOTICES.md
@@ -0,0 +1,12 @@
+# Change Log
+
+## [23.0.1](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v4/) (2015-09-24)
+
+**Breakage and deprecation notices:**
+
+- ExploreByTouchHelper
+ - Several public methods that are only meant to be called by app developers (and not internally by
+ the helper itself) became final. Any code that depends on overriding these methods should be
+ moved elsewhere.
+ - The concept of keyboard and accessibility focus have been clarified. As a result, the
+ getFocusedVirtualView() method has been deprecated and will be removed in a subsequent release.
diff --git a/v4/api/current.txt b/v4/api/current.txt
index ce5e0e5..c47b624 100644
--- a/v4/api/current.txt
+++ b/v4/api/current.txt
@@ -299,10 +299,12 @@
method public void noteStateNotSaved();
method public android.view.View onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet);
method public void reportLoaderStart();
- method public void restoreAllState(android.os.Parcelable, java.util.List<android.support.v4.app.Fragment>);
+ method public deprecated void restoreAllState(android.os.Parcelable, java.util.List<android.support.v4.app.Fragment>);
+ method public void restoreAllState(android.os.Parcelable, android.support.v4.app.FragmentManagerNonConfig);
method public void restoreLoaderNonConfig(android.support.v4.util.SimpleArrayMap<java.lang.String, android.support.v4.app.LoaderManager>);
method public android.support.v4.util.SimpleArrayMap<java.lang.String, android.support.v4.app.LoaderManager> retainLoaderNonConfig();
- method public java.util.List<android.support.v4.app.Fragment> retainNonConfig();
+ method public android.support.v4.app.FragmentManagerNonConfig retainNestedNonConfig();
+ method public deprecated java.util.List<android.support.v4.app.Fragment> retainNonConfig();
method public android.os.Parcelable saveAllState();
}
@@ -361,6 +363,9 @@
method public abstract void onBackStackChanged();
}
+ public class FragmentManagerNonConfig {
+ }
+
public abstract class FragmentPagerAdapter extends android.support.v4.view.PagerAdapter {
ctor public FragmentPagerAdapter(android.support.v4.app.FragmentManager);
method public abstract android.support.v4.app.Fragment getItem(int);
@@ -393,6 +398,8 @@
method public abstract android.support.v4.app.FragmentTransaction attach(android.support.v4.app.Fragment);
method public abstract int commit();
method public abstract int commitAllowingStateLoss();
+ method public abstract void commitNow();
+ method public abstract void commitNowAllowingStateLoss();
method public abstract android.support.v4.app.FragmentTransaction detach(android.support.v4.app.Fragment);
method public abstract android.support.v4.app.FragmentTransaction disallowAddToBackStack();
method public abstract android.support.v4.app.FragmentTransaction hide(android.support.v4.app.Fragment);
@@ -636,6 +643,7 @@
method public android.support.v4.app.NotificationCompat.Builder setSubText(java.lang.CharSequence);
method public android.support.v4.app.NotificationCompat.Builder setTicker(java.lang.CharSequence);
method public android.support.v4.app.NotificationCompat.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+ method public android.support.v4.app.NotificationCompat.Builder setTopic(android.support.v4.app.NotificationCompat.Topic);
method public android.support.v4.app.NotificationCompat.Builder setUsesChronometer(boolean);
method public android.support.v4.app.NotificationCompat.Builder setVibrate(long[]);
method public android.support.v4.app.NotificationCompat.Builder setVisibility(int);
@@ -692,6 +700,12 @@
method public void setBuilder(android.support.v4.app.NotificationCompat.Builder);
}
+ public static class NotificationCompat.Topic {
+ ctor public NotificationCompat.Topic(java.lang.String, java.lang.CharSequence);
+ method public java.lang.String getId();
+ method public java.lang.CharSequence getLabel();
+ }
+
public static final class NotificationCompat.WearableExtender implements android.support.v4.app.NotificationCompat.Extender {
ctor public NotificationCompat.WearableExtender();
ctor public NotificationCompat.WearableExtender(android.app.Notification);
@@ -920,6 +934,7 @@
public class ContextCompat {
ctor public ContextCompat();
method public static int checkSelfPermission(android.content.Context, java.lang.String);
+ method public static android.content.Context createDeviceEncryptedStorageContext(android.content.Context);
method public static java.io.File getCodeCacheDir(android.content.Context);
method public static final int getColor(android.content.Context, int);
method public static final android.content.res.ColorStateList getColorStateList(android.content.Context, int);
@@ -928,6 +943,7 @@
method public static java.io.File[] getExternalFilesDirs(android.content.Context, java.lang.String);
method public final java.io.File getNoBackupFilesDir(android.content.Context);
method public static java.io.File[] getObbDirs(android.content.Context);
+ method public static boolean isDeviceEncryptedStorage(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);
}
@@ -1284,6 +1300,9 @@
ctor public MediaBrowserServiceCompat.BrowserRoot(java.lang.String, android.os.Bundle);
method public android.os.Bundle getExtras();
method public java.lang.String getRootId();
+ field public static final java.lang.String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+ field public static final java.lang.String EXTRA_RECENT = "android.service.media.extra.RECENT";
+ field public static final java.lang.String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
}
public static class MediaBrowserServiceCompat.Result {
@@ -1747,7 +1766,9 @@
method public static void incrementOperationCount(int);
method public static void incrementOperationCount(int, int);
method public static void setThreadStatsTag(int);
+ method public static void tagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
method public static void tagSocket(java.net.Socket) throws java.net.SocketException;
+ method public static void untagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
method public static void untagSocket(java.net.Socket) throws java.net.SocketException;
}
@@ -1759,6 +1780,10 @@
method public static android.os.AsyncTask<Params, Progress, Result> executeParallel(android.os.AsyncTask<Params, Progress, Result>, Params...);
}
+ public class BuildCompat {
+ method public static boolean isAtLeastN();
+ }
+
public final class CancellationSignal {
ctor public CancellationSignal();
method public void cancel();
@@ -1796,6 +1821,11 @@
method public static void endSection();
}
+ public class UserManagerCompat {
+ ctor public UserManagerCompat();
+ method public static boolean isUserUnlocked(android.content.Context);
+ }
+
}
package android.support.v4.print {
@@ -2109,6 +2139,15 @@
method public abstract void onActionProviderVisibilityChanged(boolean);
}
+ public final class AsyncLayoutInflater {
+ ctor public AsyncLayoutInflater(android.content.Context);
+ method public void inflate(int, android.view.ViewGroup, android.support.v4.view.AsyncLayoutInflater.OnInflateFinishedListener);
+ }
+
+ public static abstract interface AsyncLayoutInflater.OnInflateFinishedListener {
+ method public abstract void onInflateFinished(android.view.View, int, android.view.ViewGroup);
+ }
+
public final class GestureDetectorCompat {
ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener);
ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener, android.os.Handler);
@@ -2157,6 +2196,7 @@
method public static java.lang.Object getKeyDispatcherState(android.view.View);
method public static boolean hasModifiers(android.view.KeyEvent, int);
method public static boolean hasNoModifiers(android.view.KeyEvent);
+ method public static boolean isCtrlPressed(android.view.KeyEvent);
method public static boolean isTracking(android.view.KeyEvent);
method public static boolean metaStateHasModifiers(int, int);
method public static boolean metaStateHasNoModifiers(int);
@@ -2217,11 +2257,13 @@
method public static int getActionMasked(android.view.MotionEvent);
method public static float getAxisValue(android.view.MotionEvent, int);
method public static float getAxisValue(android.view.MotionEvent, int, int);
+ method public static int getButtonState(android.view.MotionEvent);
method public static int getPointerCount(android.view.MotionEvent);
method public static int getPointerId(android.view.MotionEvent, int);
method public static int getSource(android.view.MotionEvent);
method public static float getX(android.view.MotionEvent, int);
method public static float getY(android.view.MotionEvent, int);
+ method public static boolean isFromSource(android.view.MotionEvent, int);
field public static final int ACTION_HOVER_ENTER = 9; // 0x9
field public static final int ACTION_HOVER_EXIT = 10; // 0xa
field public static final int ACTION_HOVER_MOVE = 7; // 0x7
@@ -2256,6 +2298,8 @@
field public static final int AXIS_LTRIGGER = 17; // 0x11
field public static final int AXIS_ORIENTATION = 8; // 0x8
field public static final int AXIS_PRESSURE = 2; // 0x2
+ field public static final int AXIS_RELATIVE_X = 27; // 0x1b
+ field public static final int AXIS_RELATIVE_Y = 28; // 0x1c
field public static final int AXIS_RTRIGGER = 18; // 0x12
field public static final int AXIS_RUDDER = 20; // 0x14
field public static final int AXIS_RX = 12; // 0xc
@@ -2273,6 +2317,7 @@
field public static final int AXIS_X = 0; // 0x0
field public static final int AXIS_Y = 1; // 0x1
field public static final int AXIS_Z = 11; // 0xb
+ field public static final int BUTTON_PRIMARY = 1; // 0x1
}
public abstract interface NestedScrollingChild {
@@ -2372,6 +2417,35 @@
method public void setTextSpacing(int);
}
+ public final class PointerIconCompat {
+ method public static android.support.v4.view.PointerIconCompat createCustomIcon(android.graphics.Bitmap, float, float);
+ method public static android.support.v4.view.PointerIconCompat getSystemIcon(android.content.Context, int);
+ method public static android.support.v4.view.PointerIconCompat loadCustomIcon(android.content.res.Resources, int);
+ field public static final int STYLE_ALIAS = 1010; // 0x3f2
+ field public static final int STYLE_ALL_SCROLL = 1013; // 0x3f5
+ field public static final int STYLE_ARROW = 1000; // 0x3e8
+ field public static final int STYLE_CELL = 1006; // 0x3ee
+ field public static final int STYLE_CONTEXT_MENU = 1001; // 0x3e9
+ field public static final int STYLE_COPY = 1011; // 0x3f3
+ field public static final int STYLE_CROSSHAIR = 1007; // 0x3ef
+ field public static final int STYLE_DEFAULT = 1000; // 0x3e8
+ field public static final int STYLE_GRAB = 1020; // 0x3fc
+ field public static final int STYLE_GRABBING = 1021; // 0x3fd
+ field public static final int STYLE_HAND = 1002; // 0x3ea
+ field public static final int STYLE_HELP = 1003; // 0x3eb
+ field public static final int STYLE_HORIZONTAL_DOUBLE_ARROW = 1014; // 0x3f6
+ field public static final int STYLE_NO_DROP = 1012; // 0x3f4
+ field public static final int STYLE_NULL = 0; // 0x0
+ field public static final int STYLE_TEXT = 1008; // 0x3f0
+ field public static final int STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; // 0x3f9
+ field public static final int STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; // 0x3f8
+ field public static final int STYLE_VERTICAL_DOUBLE_ARROW = 1015; // 0x3f7
+ field public static final int STYLE_VERTICAL_TEXT = 1009; // 0x3f1
+ field public static final int STYLE_WAIT = 1004; // 0x3ec
+ field public static final int STYLE_ZOOM_IN = 1018; // 0x3fa
+ field public static final int STYLE_ZOOM_OUT = 1019; // 0x3fb
+ }
+
public final class ScaleGestureDetectorCompat {
method public static boolean isQuickScaleEnabled(java.lang.Object);
method public static void setQuickScaleEnabled(java.lang.Object, boolean);
@@ -2398,7 +2472,8 @@
method public static float getYVelocity(android.view.VelocityTracker, int);
}
- public final class ViewCompat {
+ public class ViewCompat {
+ ctor protected ViewCompat();
method public static android.support.v4.view.ViewPropertyAnimatorCompat animate(android.view.View);
method public static boolean canScrollHorizontally(android.view.View, int);
method public static boolean canScrollVertically(android.view.View, int);
@@ -2451,6 +2526,7 @@
method public static boolean hasNestedScrollingParent(android.view.View);
method public static boolean hasOnClickListeners(android.view.View);
method public static boolean hasOverlappingRendering(android.view.View);
+ method public static boolean hasPointerCapture(android.view.View);
method public static boolean hasTransientState(android.view.View);
method public static boolean isAttachedToWindow(android.view.View);
method public static boolean isLaidOut(android.view.View);
@@ -2469,6 +2545,7 @@
method public static void postInvalidateOnAnimation(android.view.View, int, int, int, int);
method public static void postOnAnimation(android.view.View, java.lang.Runnable);
method public static void postOnAnimationDelayed(android.view.View, java.lang.Runnable, long);
+ method public static void releasePointerCapture(android.view.View);
method public static void requestApplyInsets(android.view.View);
method public static int resolveSizeAndState(int, int, int);
method public static void setAccessibilityDelegate(android.view.View, android.support.v4.view.AccessibilityDelegateCompat);
@@ -2493,6 +2570,8 @@
method public static void setPaddingRelative(android.view.View, int, int, int, int);
method public static void setPivotX(android.view.View, float);
method public static void setPivotY(android.view.View, float);
+ method public static void setPointerCapture(android.view.View);
+ method public static void setPointerIcon(android.view.View, android.support.v4.view.PointerIconCompat);
method public static void setRotation(android.view.View, float);
method public static void setRotationX(android.view.View, float);
method public static void setRotationY(android.view.View, float);
@@ -2796,6 +2875,7 @@
method public java.lang.CharSequence getPackageName();
method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getParent();
method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat getRangeInfo();
+ method public java.lang.CharSequence getRoleDescription();
method public java.lang.CharSequence getText();
method public int getTextSelectionEnd();
method public int getTextSelectionStart();
@@ -2864,6 +2944,7 @@
method public void setParent(android.view.View, int);
method public void setPassword(boolean);
method public void setRangeInfo(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat);
+ method public void setRoleDescription(java.lang.CharSequence);
method public void setScrollable(boolean);
method public void setSelected(boolean);
method public void setSource(android.view.View);
@@ -3241,17 +3322,26 @@
public abstract class ExploreByTouchHelper extends android.support.v4.view.AccessibilityDelegateCompat {
ctor public ExploreByTouchHelper(android.view.View);
- method public boolean dispatchHoverEvent(android.view.MotionEvent);
- method public int getFocusedVirtualView();
+ method public final boolean clearKeyboardFocusForVirtualView(int);
+ method public final boolean dispatchHoverEvent(android.view.MotionEvent);
+ method public final boolean dispatchKeyEvent(android.view.KeyEvent);
+ method public final int getAccessibilityFocusedVirtualViewId();
+ method public deprecated int getFocusedVirtualView();
+ method public final int getKeyboardFocusedVirtualViewId();
method protected abstract int getVirtualViewAt(float, float);
method protected abstract void getVisibleVirtualViews(java.util.List<java.lang.Integer>);
- method public void invalidateRoot();
- method public void invalidateVirtualView(int);
+ method public final void invalidateRoot();
+ method public final void invalidateVirtualView(int);
+ method public final void invalidateVirtualView(int, int);
+ method public final void onFocusChanged(boolean, int, android.graphics.Rect);
method protected abstract boolean onPerformActionForVirtualView(int, int, android.os.Bundle);
- method protected abstract void onPopulateEventForVirtualView(int, android.view.accessibility.AccessibilityEvent);
- method public void onPopulateNodeForHost(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
+ method protected void onPopulateEventForHost(android.view.accessibility.AccessibilityEvent);
+ method protected void onPopulateEventForVirtualView(int, android.view.accessibility.AccessibilityEvent);
+ method protected void onPopulateNodeForHost(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
method protected abstract void onPopulateNodeForVirtualView(int, android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
- method public boolean sendEventForVirtualView(int, int);
+ method protected void onVirtualViewKeyboardFocusChanged(int, boolean);
+ method public final boolean requestKeyboardFocusForVirtualView(int);
+ method public final boolean sendEventForVirtualView(int, int);
field public static final int HOST_ID = -1; // 0xffffffff
field public static final int INVALID_ID = -2147483648; // 0x80000000
}
diff --git a/v4/api/removed.txt b/v4/api/removed.txt
index e69de29..217aba3 100644
--- a/v4/api/removed.txt
+++ b/v4/api/removed.txt
@@ -0,0 +1,9 @@
+package android.support.v4.os {
+
+ public class UserManagerCompat {
+ method public static deprecated boolean isUserRunningAndLocked(android.content.Context);
+ method public static deprecated boolean isUserRunningAndUnlocked(android.content.Context);
+ }
+
+}
+
diff --git a/v4/api20/android/support/v4/print/PrintHelperApi20.java b/v4/api20/android/support/v4/print/PrintHelperApi20.java
new file mode 100644
index 0000000..ce62106
--- /dev/null
+++ b/v4/api20/android/support/v4/print/PrintHelperApi20.java
@@ -0,0 +1,33 @@
+/*
+ * 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.print;
+
+import android.content.Context;
+
+/**
+ * Api20 specific PrintManager API implementation.
+ */
+class PrintHelperApi20 extends PrintHelperKitkat {
+ PrintHelperApi20(Context context) {
+ super(context);
+
+ /**
+ * There is a bug in the PrintActivity that causes it to ignore the orientation
+ */
+ mPrintActivityRespectsOrientation = false;
+ }
+}
\ No newline at end of file
diff --git a/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
index ddcff2e..37c4b97 100644
--- a/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
+++ b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
@@ -92,6 +92,36 @@
return transitionSet;
}
+ private static void excludeViews(Transition transition, Transition fromTransition,
+ ArrayList<View> views, boolean exclude) {
+ if (transition != null) {
+ final int viewCount = fromTransition == null ? 0 : views.size();
+ for (int i = 0; i < viewCount; i++) {
+ transition.excludeTarget(views.get(i), exclude);
+ }
+ }
+ }
+
+ /**
+ * Exclude (or remove the exclude) of shared element views from the enter and exit transitions.
+ *
+ * @param enterTransitionObj The enter transition
+ * @param exitTransitionObj The exit transition
+ * @param sharedElementTransitionObj The shared element transition
+ * @param views The shared element target views.
+ * @param exclude <code>true</code> to exclude or <code>false</code> to remove the excluded
+ * views.
+ */
+ public static void excludeSharedElementViews(Object enterTransitionObj,
+ Object exitTransitionObj, Object sharedElementTransitionObj, ArrayList<View> views,
+ boolean exclude) {
+ Transition enterTransition = (Transition) enterTransitionObj;
+ Transition exitTransition = (Transition) exitTransitionObj;
+ Transition sharedElementTransition = (Transition) sharedElementTransitionObj;
+ excludeViews(enterTransition, sharedElementTransition, views, exclude);
+ excludeViews(exitTransition, sharedElementTransition, views, exclude);
+ }
+
/**
* Prepares the enter transition by adding a non-existent view to the transition's target list
* and setting it epicenter callback. By adding a non-existent view to the target list,
@@ -104,35 +134,42 @@
* capturing the final state of the Transition.</p>
*/
public static void addTransitionTargets(Object enterTransitionObject,
- Object sharedElementTransitionObject, final View container,
+ Object sharedElementTransitionObject, Object exitTransitionObject, final View container,
final ViewRetriever inFragment, final View nonExistentView,
EpicenterView epicenterView, final Map<String, String> nameOverrides,
- final ArrayList<View> enteringViews, final Map<String, View> namedViews,
- final Map<String, View> renamedViews, final ArrayList<View> sharedElementTargets) {
+ final ArrayList<View> enteringViews, final ArrayList<View> exitingViews,
+ final Map<String, View> namedViews, final Map<String, View> renamedViews,
+ final ArrayList<View> sharedElementTargets) {
+ final Transition enterTransition = (Transition) enterTransitionObject;
+ final Transition exitTransition = (Transition) exitTransitionObject;
+ final Transition sharedElementTransition = (Transition) sharedElementTransitionObject;
+ excludeViews(enterTransition, exitTransition, exitingViews, true);
if (enterTransitionObject != null || sharedElementTransitionObject != null) {
- final Transition enterTransition = (Transition) enterTransitionObject;
if (enterTransition != null) {
enterTransition.addTarget(nonExistentView);
}
if (sharedElementTransitionObject != null) {
- setSharedElementTargets(sharedElementTransitionObject, nonExistentView,
+ setSharedElementTargets(sharedElementTransition, nonExistentView,
namedViews, sharedElementTargets);
+ excludeViews(enterTransition, sharedElementTransition, sharedElementTargets, true);
+ excludeViews(exitTransition, sharedElementTransition, sharedElementTargets, true);
}
- if (inFragment != null) {
- container.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- public boolean onPreDraw() {
- container.getViewTreeObserver().removeOnPreDrawListener(this);
- if (enterTransition != null) {
- enterTransition.removeTarget(nonExistentView);
- }
+ container.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ public boolean onPreDraw() {
+ container.getViewTreeObserver().removeOnPreDrawListener(this);
+ if (enterTransition != null) {
+ enterTransition.removeTarget(nonExistentView);
+ }
+ if (inFragment != null) {
View fragmentView = inFragment.getView();
if (fragmentView != null) {
if (!nameOverrides.isEmpty()) {
findNamedViews(renamedViews, fragmentView);
renamedViews.keySet().retainAll(nameOverrides.values());
- for (Map.Entry<String, String> entry : nameOverrides.entrySet()) {
+ for (Map.Entry<String, String> entry : nameOverrides
+ .entrySet()) {
String to = entry.getValue();
View view = renamedViews.get(to);
if (view != null) {
@@ -148,10 +185,12 @@
addTargets(enterTransition, enteringViews);
}
}
- return true;
}
- });
- }
+ excludeViews(exitTransition, enterTransition, enteringViews, true);
+
+ return true;
+ }
+ });
setSharedElementEpicenter(enterTransition, epicenterView);
}
}
@@ -355,9 +394,15 @@
sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
if (enterTransition != null) {
removeTargets(enterTransition, enteringViews);
+ excludeViews(enterTransition, exitTransition, exitingViews, false);
+ excludeViews(enterTransition, sharedElementTransition, sharedElementTargets,
+ false);
}
if (exitTransition != null) {
removeTargets(exitTransition, exitingViews);
+ excludeViews(exitTransition, enterTransition, enteringViews, false);
+ excludeViews(exitTransition, sharedElementTransition, sharedElementTargets,
+ false);
}
if (sharedElementTransition != null) {
removeTargets(sharedElementTransition, sharedElementTargets);
diff --git a/v4/api24/android/support/v4/app/NotificationCompatApi24.java b/v4/api24/android/support/v4/app/NotificationCompatApi24.java
new file mode 100644
index 0000000..464f399
--- /dev/null
+++ b/v4/api24/android/support/v4/app/NotificationCompatApi24.java
@@ -0,0 +1,106 @@
+/**
+ * Copyright (C) 2015 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.app;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+
+class NotificationCompatApi24 {
+
+ public static class Builder implements NotificationBuilderWithBuilderAccessor,
+ NotificationBuilderWithActions {
+ private Notification.Builder b;
+
+ public Builder(Context context, Notification n,
+ CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+ RemoteViews tickerView, int number,
+ PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
+ int progressMax, int progress, boolean progressIndeterminate, boolean showWhen,
+ boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
+ String category, ArrayList<String> people, Bundle extras, int color,
+ int visibility, Notification publicVersion, String groupKey, boolean groupSummary,
+ String sortKey, NotificationCompatBase.Topic topic) {
+ b = new Notification.Builder(context)
+ .setWhen(n.when)
+ .setShowWhen(showWhen)
+ .setSmallIcon(n.icon, n.iconLevel)
+ .setContent(n.contentView)
+ .setTicker(n.tickerText, tickerView)
+ .setSound(n.sound, n.audioStreamType)
+ .setVibrate(n.vibrate)
+ .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+ .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+ .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+ .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+ .setDefaults(n.defaults)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setSubText(subText)
+ .setContentInfo(contentInfo)
+ .setContentIntent(contentIntent)
+ .setDeleteIntent(n.deleteIntent)
+ .setFullScreenIntent(fullScreenIntent,
+ (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+ .setLargeIcon(largeIcon)
+ .setNumber(number)
+ .setUsesChronometer(useChronometer)
+ .setPriority(priority)
+ .setProgress(progressMax, progress, progressIndeterminate)
+ .setLocalOnly(localOnly)
+ .setExtras(extras)
+ .setGroup(groupKey)
+ .setGroupSummary(groupSummary)
+ .setSortKey(sortKey)
+ .setCategory(category)
+ .setColor(color)
+ .setVisibility(visibility)
+ .setPublicVersion(publicVersion)
+ .setTopic(new Notification.Topic(topic.getId(), topic.getLabel()));
+ for (String person: people) {
+ b.addPerson(person);
+ }
+ }
+
+ @Override
+ public void addAction(NotificationCompatBase.Action action) {
+ NotificationCompatApi20.addAction(b, action);
+ }
+
+ @Override
+ public Notification.Builder getBuilder() {
+ return b;
+ }
+
+ @Override
+ public Notification build() {
+ return b.build();
+ }
+ }
+
+ public static NotificationCompatBase.Topic getTopic(Notification notif,
+ NotificationCompatBase.Topic.Factory factory) {
+ Notification.Topic topic = notif.getTopic();
+ return factory.build(topic.getId(), topic.getLabel());
+ }
+}
diff --git a/v4/api24/android/support/v4/content/ContextCompatApi24.java b/v4/api24/android/support/v4/content/ContextCompatApi24.java
new file mode 100644
index 0000000..cc48e67
--- /dev/null
+++ b/v4/api24/android/support/v4/content/ContextCompatApi24.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 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.content;
+
+import android.content.Context;
+
+/** {@hide} */
+public class ContextCompatApi24 {
+ public static Context createDeviceEncryptedStorageContext(Context context) {
+ return context.createDeviceEncryptedStorageContext();
+ }
+
+ public static boolean isDeviceEncryptedStorage(Context context) {
+ return context.isDeviceEncryptedStorage();
+ }
+}
diff --git a/v4/api24/android/support/v4/net/TrafficStatsCompatApi24.java b/v4/api24/android/support/v4/net/TrafficStatsCompatApi24.java
new file mode 100644
index 0000000..f525dea
--- /dev/null
+++ b/v4/api24/android/support/v4/net/TrafficStatsCompatApi24.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 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.net;
+
+import android.net.TrafficStats;
+
+import java.net.DatagramSocket;
+import java.net.SocketException;
+
+/** {@hide} */
+public class TrafficStatsCompatApi24 {
+ public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStats.tagDatagramSocket(socket);
+ }
+
+ public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStats.untagDatagramSocket(socket);
+ }
+}
diff --git a/v4/api24/android/support/v4/os/UserManagerCompatApi24.java b/v4/api24/android/support/v4/os/UserManagerCompatApi24.java
new file mode 100644
index 0000000..7067e01
--- /dev/null
+++ b/v4/api24/android/support/v4/os/UserManagerCompatApi24.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.os.UserManager;
+
+/** {@hide} */
+public class UserManagerCompatApi24 {
+ public static boolean isUserUnlocked(Context context) {
+ return context.getSystemService(UserManager.class).isUserUnlocked();
+ }
+}
diff --git a/v4/api24/android/support/v4/print/PrintHelperApi24.java b/v4/api24/android/support/v4/print/PrintHelperApi24.java
new file mode 100644
index 0000000..8f1b0c9
--- /dev/null
+++ b/v4/api24/android/support/v4/print/PrintHelperApi24.java
@@ -0,0 +1,30 @@
+/*
+ * 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.print;
+
+import android.content.Context;
+
+/**
+ * Api24 specific PrintManager API implementation.
+ */
+class PrintHelperApi24 extends PrintHelperApi20 {
+ PrintHelperApi24(Context context) {
+ super(context);
+
+ mPrintActivityRespectsOrientation = true;
+ }
+}
\ No newline at end of file
diff --git a/v4/api24/android/support/v4/view/PointerIconCompatApi24.java b/v4/api24/android/support/v4/view/PointerIconCompatApi24.java
new file mode 100644
index 0000000..c9f4377
--- /dev/null
+++ b/v4/api24/android/support/v4/view/PointerIconCompatApi24.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.v4.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.view.PointerIcon;
+
+class PointerIconCompatApi24 {
+ public static Object getSystemIcon(Context context, int style) {
+ return PointerIcon.getSystemIcon(context, style);
+ }
+
+ public static Object createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ return PointerIcon.createCustomIcon(bitmap, hotSpotX, hotSpotY);
+ }
+
+ public static Object loadCustomIcon(Resources resources, int resourceId) {
+ return PointerIcon.loadCustomIcon(resources, resourceId);
+ }
+}
diff --git a/v4/api24/android/support/v4/view/ViewCompatApi24.java b/v4/api24/android/support/v4/view/ViewCompatApi24.java
new file mode 100644
index 0000000..7e92c53
--- /dev/null
+++ b/v4/api24/android/support/v4/view/ViewCompatApi24.java
@@ -0,0 +1,38 @@
+/**
+ * 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.view;
+
+import android.view.PointerIcon;
+import android.view.View;
+
+class ViewCompatApi24 {
+ public static void setPointerCapture(View view) {
+ view.setPointerCapture();
+ }
+
+ public static boolean hasPointerCapture(View view) {
+ return view.hasPointerCapture();
+ }
+
+ public static void releasePointerCapture(View view) {
+ view.releasePointerCapture();
+ }
+
+ public static void setPointerIcon(View view, Object pointerIcon) {
+ view.setPointerIcon((PointerIcon)pointerIcon);
+ }
+}
diff --git a/v4/build.gradle b/v4/build.gradle
index 17461a1..f997154 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -30,8 +30,8 @@
def api20SS = createApiSourceset('api20', 'api20', '20', kitkatSS)
def api21SS = createApiSourceset('api21', 'api21', '21', api20SS)
def api22SS = createApiSourceset('api22', 'api22', '22', api21SS)
-def api23SS = createApiSourceset('api23', 'api23', 'current', api22SS)
-
+def api23SS = createApiSourceset('api23', 'api23', '23', api22SS)
+def api24SS = createApiSourceset('api24', 'api24', 'current', api23SS)
def createApiSourceset(String name, String folder, String apiLevel, SourceSet previousSource) {
def sourceSet = sourceSets.create(name)
diff --git a/v4/donut/android/support/v4/app/NotificationCompatBase.java b/v4/donut/android/support/v4/app/NotificationCompatBase.java
index 7324f26..1571010 100644
--- a/v4/donut/android/support/v4/app/NotificationCompatBase.java
+++ b/v4/donut/android/support/v4/app/NotificationCompatBase.java
@@ -57,6 +57,15 @@
}
}
+ public static abstract class Topic {
+ public abstract String getId();
+ public abstract CharSequence getLabel();
+
+ public interface Factory {
+ Topic build(String id, CharSequence topic);
+ }
+ }
+
public static Notification add(Notification notification, Context context,
CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
diff --git a/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java b/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java
index f1bd117..6cd185b 100644
--- a/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java
+++ b/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java
@@ -33,4 +33,8 @@
public static boolean metaStateHasNoModifiers(int metaState) {
return KeyEvent.metaStateHasNoModifiers(metaState);
}
+
+ public static boolean isCtrlPressed(KeyEvent event) {
+ return event.isCtrlPressed();
+ }
}
diff --git a/v4/ics/android/support/v4/net/DatagramSocketWrapper.java b/v4/ics/android/support/v4/net/DatagramSocketWrapper.java
new file mode 100644
index 0000000..87dfc00
--- /dev/null
+++ b/v4/ics/android/support/v4/net/DatagramSocketWrapper.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 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.net;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketImpl;
+
+/** {@hide} */
+class DatagramSocketWrapper extends Socket {
+ public DatagramSocketWrapper(DatagramSocket socket) throws SocketException {
+ super(new DatagramSocketImplWrapper(socket));
+ }
+
+ /**
+ * Empty implementation which can expose a fd.
+ */
+ private static class DatagramSocketImplWrapper extends SocketImpl {
+ public DatagramSocketImplWrapper(DatagramSocket socket) throws SocketException {
+ super();
+ localport = socket.getLocalPort();
+ fd = ParcelFileDescriptor.fromDatagramSocket(socket).getFileDescriptor();
+ }
+
+ @Override
+ protected void accept(SocketImpl newSocket) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected int available() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void bind(InetAddress address, int port) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void close() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void connect(String host, int port) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void connect(InetAddress address, int port) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void create(boolean isStreaming) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected InputStream getInputStream() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected OutputStream getOutputStream() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void listen(int backlog) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void connect(SocketAddress remoteAddr, int timeout) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void sendUrgentData(int value) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object getOption(int optID) throws SocketException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setOption(int optID, Object val) throws SocketException {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java b/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java
index 0f63fce..4d2b600 100644
--- a/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java
+++ b/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java
@@ -18,6 +18,7 @@
import android.net.TrafficStats;
+import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
@@ -52,4 +53,12 @@
public static void untagSocket(Socket socket) throws SocketException {
TrafficStats.untagSocket(socket);
}
+
+ public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStats.tagSocket(new DatagramSocketWrapper(socket));
+ }
+
+ public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStats.untagSocket(new DatagramSocketWrapper(socket));
+ }
}
diff --git a/v4/ics/android/support/v4/view/MotionEventCompatICS.java b/v4/ics/android/support/v4/view/MotionEventCompatICS.java
new file mode 100644
index 0000000..e7979de
--- /dev/null
+++ b/v4/ics/android/support/v4/view/MotionEventCompatICS.java
@@ -0,0 +1,25 @@
+/*
+ * 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.view;
+
+import android.view.MotionEvent;
+
+class MotionEventCompatICS {
+ public static int getButtonState(MotionEvent event) {
+ return event.getButtonState();
+ }
+}
diff --git a/v4/java/android/support/v4/app/ActivityCompat.java b/v4/java/android/support/v4/app/ActivityCompat.java
index 0926d49..4e699d4 100644
--- a/v4/java/android/support/v4/app/ActivityCompat.java
+++ b/v4/java/android/support/v4/app/ActivityCompat.java
@@ -300,6 +300,12 @@
* When checking whether you have a permission you should use {@link
* #checkSelfPermission(android.content.Context, String)}.
* </p>
+ * <p>
+ * Calling this API for permissions already granted to your app would show UI
+ * to the user to decided whether the app can still hold these permissions. This
+ * can be useful if the way your app uses the data guarded by the permissions
+ * changes significantly.
+ * </p>
*
* @param activity The target activity.
* @param permissions The requested permissions.
diff --git a/v4/java/android/support/v4/app/BackStackRecord.java b/v4/java/android/support/v4/app/BackStackRecord.java
index 9117bd3..a40ac47 100644
--- a/v4/java/android/support/v4/app/BackStackRecord.java
+++ b/v4/java/android/support/v4/app/BackStackRecord.java
@@ -616,6 +616,18 @@
public int commitAllowingStateLoss() {
return commitInternal(true);
}
+
+ @Override
+ public void commitNow() {
+ disallowAddToBackStack();
+ mManager.execSingleAction(this, false);
+ }
+
+ @Override
+ public void commitNowAllowingStateLoss() {
+ disallowAddToBackStack();
+ mManager.execSingleAction(this, true);
+ }
int commitInternal(boolean allowStateLoss) {
if (mCommitted) throw new IllegalStateException("commit already called");
@@ -1187,7 +1199,8 @@
callback.onSharedElementStart(names, views, null);
}
prepareSharedElementTransition(state, sceneRoot, sharedElementTransition,
- inFragment, outFragment, isBack, sharedElementTargets);
+ inFragment, outFragment, isBack, sharedElementTargets, enterTransition,
+ exitTransition);
}
}
if (enterTransition == null && sharedElementTransition == null &&
@@ -1234,9 +1247,9 @@
if (transition != null) {
FragmentTransitionCompat21.addTransitionTargets(enterTransition,
- sharedElementTransition, sceneRoot, viewRetriever, state.nonExistentView,
- state.enteringEpicenterView, state.nameOverrides, enteringViews,
- namedViews, renamedViews, sharedElementTargets);
+ sharedElementTransition, exitTransition, sceneRoot, viewRetriever,
+ state.nonExistentView, state.enteringEpicenterView, state.nameOverrides,
+ enteringViews, exitingViews, namedViews, renamedViews, sharedElementTargets);
excludeHiddenFragmentsAfterEnter(sceneRoot, state, containerId, transition);
// We want to exclude hidden views later, so we need a non-null list in the
@@ -1258,16 +1271,22 @@
private void prepareSharedElementTransition(final TransitionState state, final View sceneRoot,
final Object sharedElementTransition, final Fragment inFragment,
final Fragment outFragment, final boolean isBack,
- final ArrayList<View> sharedElementTargets) {
- sceneRoot.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+ final ArrayList<View> sharedElementTargets, final Object enterTransition,
+ final Object exitTransition) {
+ if (sharedElementTransition != null) {
+ sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
- if (sharedElementTransition != null) {
+ // Remove the exclude for the shared elements from the exiting fragment.
FragmentTransitionCompat21.removeTargets(sharedElementTransition,
sharedElementTargets);
+ // keep the nonExistentView as excluded so the list doesn't get emptied
+ sharedElementTargets.remove(state.nonExistentView);
+ FragmentTransitionCompat21.excludeSharedElementViews(enterTransition,
+ exitTransition, sharedElementTransition, sharedElementTargets, false);
sharedElementTargets.clear();
ArrayMap<String, View> namedViews = mapSharedElementsIn(
@@ -1279,11 +1298,14 @@
callSharedElementEnd(state, inFragment, outFragment, isBack,
namedViews);
- }
- return true;
- }
- });
+ // Exclude the shared elements from the entering fragment.
+ FragmentTransitionCompat21.excludeSharedElementViews(enterTransition,
+ exitTransition, sharedElementTransition, sharedElementTargets, true);
+ return true;
+ }
+ });
+ }
}
private void callSharedElementEnd(TransitionState state, Fragment inFragment,
diff --git a/v4/java/android/support/v4/app/Fragment.java b/v4/java/android/support/v4/app/Fragment.java
index af99b1c..58adce6 100644
--- a/v4/java/android/support/v4/app/Fragment.java
+++ b/v4/java/android/support/v4/app/Fragment.java
@@ -59,11 +59,12 @@
final boolean mRetainInstance;
final boolean mDetached;
final Bundle mArguments;
+ final boolean mHidden;
Bundle mSavedFragmentState;
Fragment mInstance;
-
+
public FragmentState(Fragment frag) {
mClassName = frag.getClass().getName();
mIndex = frag.mIndex;
@@ -74,6 +75,7 @@
mRetainInstance = frag.mRetainInstance;
mDetached = frag.mDetached;
mArguments = frag.mArguments;
+ mHidden = frag.mHidden;
}
public FragmentState(Parcel in) {
@@ -86,38 +88,39 @@
mRetainInstance = in.readInt() != 0;
mDetached = in.readInt() != 0;
mArguments = in.readBundle();
+ mHidden = in.readInt() != 0;
mSavedFragmentState = in.readBundle();
}
- public Fragment instantiate(FragmentHostCallback host, Fragment parent) {
- if (mInstance != null) {
- return mInstance;
+ public Fragment instantiate(FragmentHostCallback host, Fragment parent,
+ FragmentManagerNonConfig childNonConfig) {
+ if (mInstance == null) {
+ final Context context = host.getContext();
+ if (mArguments != null) {
+ mArguments.setClassLoader(context.getClassLoader());
+ }
+
+ mInstance = Fragment.instantiate(context, mClassName, mArguments);
+
+ if (mSavedFragmentState != null) {
+ mSavedFragmentState.setClassLoader(context.getClassLoader());
+ mInstance.mSavedFragmentState = mSavedFragmentState;
+ }
+ mInstance.setIndex(mIndex, parent);
+ mInstance.mFromLayout = mFromLayout;
+ mInstance.mRestored = true;
+ mInstance.mFragmentId = mFragmentId;
+ mInstance.mContainerId = mContainerId;
+ mInstance.mTag = mTag;
+ mInstance.mRetainInstance = mRetainInstance;
+ mInstance.mDetached = mDetached;
+ mInstance.mHidden = mHidden;
+ mInstance.mFragmentManager = host.mFragmentManager;
+
+ if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
+ "Instantiated fragment " + mInstance);
}
-
- final Context context = host.getContext();
- if (mArguments != null) {
- mArguments.setClassLoader(context.getClassLoader());
- }
-
- mInstance = Fragment.instantiate(context, mClassName, mArguments);
-
- if (mSavedFragmentState != null) {
- mSavedFragmentState.setClassLoader(context.getClassLoader());
- mInstance.mSavedFragmentState = mSavedFragmentState;
- }
- mInstance.setIndex(mIndex, parent);
- mInstance.mFromLayout = mFromLayout;
- mInstance.mRestored = true;
- mInstance.mFragmentId = mFragmentId;
- mInstance.mContainerId = mContainerId;
- mInstance.mTag = mTag;
- mInstance.mRetainInstance = mRetainInstance;
- mInstance.mDetached = mDetached;
- mInstance.mFragmentManager = host.mFragmentManager;
-
- if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
- "Instantiated fragment " + mInstance);
-
+ mInstance.mChildNonConfig = childNonConfig;
return mInstance;
}
@@ -135,6 +138,7 @@
dest.writeInt(mRetainInstance ? 1 : 0);
dest.writeInt(mDetached ? 1 : 0);
dest.writeBundle(mArguments);
+ dest.writeInt(mHidden? 1 : 0);
dest.writeBundle(mSavedFragmentState);
}
@@ -240,6 +244,10 @@
// Private fragment manager for child fragments inside of this one.
FragmentManagerImpl mChildFragmentManager;
+ // For use when restoring fragment state and descendant fragments are retained.
+ // This state is set by FragmentState.instantiate and cleared in onCreate.
+ FragmentManagerNonConfig mChildNonConfig;
+
// If this Fragment is contained in another Fragment, this is that container.
Fragment mParentFragment;
@@ -813,10 +821,6 @@
* </ul>
*/
public void setRetainInstance(boolean retain) {
- if (retain && mParentFragment != null) {
- throw new IllegalStateException(
- "Can't retain fragements that are nested in other fragments");
- }
mRetainInstance = retain;
}
@@ -994,6 +998,12 @@
* android.content.Context#checkSelfPermission(String)}.
* </p>
* <p>
+ * Calling this API for permissions already granted to your app would show UI
+ * to the user to decided whether the app can still hold these permissions. This
+ * can be useful if the way your app uses the data guarded by the permissions
+ * changes significantly.
+ * </p>
+ * <p>
* A sample permissions request looks like this:
* </p>
* <code><pre><p>
@@ -1193,12 +1203,27 @@
* on things like the activity's content view hierarchy being initialized
* at this point. If you want to do work once the activity itself is
* created, see {@link #onActivityCreated(Bundle)}.
+ *
+ * <p>Any restored child fragments will be created before the base
+ * <code>Fragment.onCreate</code> method returns.</p>
*
* @param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
public void onCreate(@Nullable Bundle savedInstanceState) {
mCalled = true;
+ if (savedInstanceState != null) {
+ Parcelable p = savedInstanceState.getParcelable(
+ FragmentActivity.FRAGMENTS_TAG);
+ if (p != null) {
+ if (mChildFragmentManager == null) {
+ instantiateChildFragmentManager();
+ }
+ mChildFragmentManager.restoreAllState(p, mChildNonConfig);
+ mChildNonConfig = null;
+ mChildFragmentManager.dispatchCreate();
+ }
+ }
}
/**
@@ -1953,17 +1978,6 @@
throw new SuperNotCalledException("Fragment " + this
+ " did not call through to super.onCreate()");
}
- if (savedInstanceState != null) {
- Parcelable p = savedInstanceState.getParcelable(
- FragmentActivity.FRAGMENTS_TAG);
- if (p != null) {
- if (mChildFragmentManager == null) {
- instantiateChildFragmentManager();
- }
- mChildFragmentManager.restoreAllState(p, null);
- mChildFragmentManager.dispatchCreate();
- }
- }
}
View performCreateView(LayoutInflater inflater, ViewGroup container,
diff --git a/v4/java/android/support/v4/app/FragmentActivity.java b/v4/java/android/support/v4/app/FragmentActivity.java
index 5bde380..c6e8b36 100644
--- a/v4/java/android/support/v4/app/FragmentActivity.java
+++ b/v4/java/android/support/v4/app/FragmentActivity.java
@@ -143,7 +143,7 @@
static final class NonConfigurationInstances {
Object custom;
- List<Fragment> fragments;
+ FragmentManagerNonConfig fragments;
SimpleArrayMap<String, LoaderManager> loaders;
}
@@ -543,7 +543,7 @@
Object custom = onRetainCustomNonConfigurationInstance();
- List<Fragment> fragments = mFragments.retainNonConfig();
+ FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
SimpleArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
if (fragments == null && loaders == null && custom == null) {
diff --git a/v4/java/android/support/v4/app/FragmentController.java b/v4/java/android/support/v4/app/FragmentController.java
index 4a80fcc..60beed9 100644
--- a/v4/java/android/support/v4/app/FragmentController.java
+++ b/v4/java/android/support/v4/app/FragmentController.java
@@ -139,16 +139,41 @@
* instances retained across configuration changes.
*
* @see #retainNonConfig()
+ *
+ * @deprecated use {@link #restoreAllState(Parcelable, FragmentManagerNonConfig)}
*/
public void restoreAllState(Parcelable state, List<Fragment> nonConfigList) {
- mHost.mFragmentManager.restoreAllState(state, nonConfigList);
+ mHost.mFragmentManager.restoreAllState(state,
+ new FragmentManagerNonConfig(nonConfigList, null));
+ }
+
+ /**
+ * Restores the saved state for all Fragments. The given FragmentManagerNonConfig are Fragment
+ * instances retained across configuration changes, including nested fragments
+ *
+ * @see #retainNestedNonConfig()
+ */
+ public void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
+ mHost.mFragmentManager.restoreAllState(state, nonConfig);
}
/**
* Returns a list of Fragments that have opted to retain their instance across
* configuration changes.
+ *
+ * @deprecated use {@link #retainNestedNonConfig()} to also track retained
+ * nested child fragments
*/
public List<Fragment> retainNonConfig() {
+ FragmentManagerNonConfig nonconf = mHost.mFragmentManager.retainNonConfig();
+ return nonconf != null ? nonconf.getFragments() : null;
+ }
+
+ /**
+ * Returns a nested tree of Fragments that have opted to retain their instance across
+ * configuration changes.
+ */
+ public FragmentManagerNonConfig retainNestedNonConfig() {
return mHost.mFragmentManager.retainNonConfig();
}
diff --git a/v4/java/android/support/v4/app/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index b1b988e..3622f84 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -1581,16 +1581,36 @@
}
}
+ public void execSingleAction(Runnable action, boolean allowStateLoss) {
+ if (mExecutingActions) {
+ throw new IllegalStateException("FragmentManager is already executing transactions");
+ }
+
+ if (Looper.myLooper() != mHost.getHandler().getLooper()) {
+ throw new IllegalStateException("Must be called from main thread of fragment host");
+ }
+
+ if (allowStateLoss) {
+ checkStateLoss();
+ }
+
+ mExecutingActions = true;
+ action.run();
+ mExecutingActions = false;
+
+ doPendingDeferredStart();
+ }
+
/**
* Only call from main thread!
*/
public boolean execPendingActions() {
if (mExecutingActions) {
- throw new IllegalStateException("Recursive entry to executePendingTransactions");
+ throw new IllegalStateException("FragmentManager is already executing transactions");
}
if (Looper.myLooper() != mHost.getHandler().getLooper()) {
- throw new IllegalStateException("Must be called from main thread of process");
+ throw new IllegalStateException("Must be called from main thread of fragment host");
}
boolean didSomething = false;
@@ -1621,9 +1641,15 @@
didSomething = true;
}
+ doPendingDeferredStart();
+
+ return didSomething;
+ }
+
+ void doPendingDeferredStart() {
if (mHavePendingDeferredStart) {
boolean loadersRunning = false;
- for (int i=0; i<mActive.size(); i++) {
+ for (int i = 0; i < mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null && f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
@@ -1634,7 +1660,6 @@
startPendingDeferredFragments();
}
}
- return didSomething;
}
void reportBackStackChanged() {
@@ -1727,23 +1752,46 @@
return true;
}
- ArrayList<Fragment> retainNonConfig() {
+ FragmentManagerNonConfig retainNonConfig() {
ArrayList<Fragment> fragments = null;
+ ArrayList<FragmentManagerNonConfig> childFragments = null;
if (mActive != null) {
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
- if (f != null && f.mRetainInstance) {
- if (fragments == null) {
- fragments = new ArrayList<Fragment>();
+ if (f != null) {
+ if (f.mRetainInstance) {
+ if (fragments == null) {
+ fragments = new ArrayList<Fragment>();
+ }
+ fragments.add(f);
+ f.mRetaining = true;
+ f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
+ if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
}
- fragments.add(f);
- f.mRetaining = true;
- f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
- if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
+ boolean addedChild = false;
+ if (f.mChildFragmentManager != null) {
+ FragmentManagerNonConfig child = f.mChildFragmentManager.retainNonConfig();
+ if (child != null) {
+ if (childFragments == null) {
+ childFragments = new ArrayList<FragmentManagerNonConfig>();
+ for (int j = 0; j < i; j++) {
+ childFragments.add(null);
+ }
+ }
+ childFragments.add(child);
+ addedChild = true;
+ }
+ }
+ if (childFragments != null && !addedChild) {
+ childFragments.add(null);
+ }
}
}
}
- return fragments;
+ if (fragments == null && childFragments == null) {
+ return null;
+ }
+ return new FragmentManagerNonConfig(fragments, childFragments);
}
void saveFragmentViewState(Fragment f) {
@@ -1911,18 +1959,23 @@
return fms;
}
- void restoreAllState(Parcelable state, List<Fragment> nonConfig) {
+ void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
// If there is no saved state at all, then there can not be
// any nonConfig fragments either, so that is that.
if (state == null) return;
FragmentManagerState fms = (FragmentManagerState)state;
if (fms.mActive == null) return;
-
+
+ List<FragmentManagerNonConfig> childNonConfigs = null;
+
// First re-attach any non-config instances we are retaining back
// to their saved state, so we don't try to instantiate them again.
if (nonConfig != null) {
- for (int i=0; i<nonConfig.size(); i++) {
- Fragment f = nonConfig.get(i);
+ List<Fragment> nonConfigFragments = nonConfig.getFragments();
+ childNonConfigs = nonConfig.getChildNonConfigs();
+ final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0;
+ for (int i = 0; i < count; i++) {
+ Fragment f = nonConfigFragments.get(i);
if (DEBUG) Log.v(TAG, "restoreAllState: re-attaching retained " + f);
FragmentState fs = fms.mActive[f.mIndex];
fs.mInstance = f;
@@ -1942,14 +1995,18 @@
// Build the full list of active fragments, instantiating them from
// their saved state.
- mActive = new ArrayList<Fragment>(fms.mActive.length);
+ mActive = new ArrayList<>(fms.mActive.length);
if (mAvailIndices != null) {
mAvailIndices.clear();
}
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
- Fragment f = fs.instantiate(mHost, mParent);
+ FragmentManagerNonConfig childNonConfig = null;
+ if (childNonConfigs != null && i < childNonConfigs.size()) {
+ childNonConfig = childNonConfigs.get(i);
+ }
+ Fragment f = fs.instantiate(mHost, mParent, childNonConfig);
if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
mActive.add(f);
// Now that the fragment is instantiated (or came from being
@@ -1968,8 +2025,10 @@
// Update the target of all retained fragments.
if (nonConfig != null) {
- for (int i=0; i<nonConfig.size(); i++) {
- Fragment f = nonConfig.get(i);
+ List<Fragment> nonConfigFragments = nonConfig.getFragments();
+ final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0;
+ for (int i = 0; i < count; i++) {
+ Fragment f = nonConfigFragments.get(i);
if (f.mTargetIndex >= 0) {
if (f.mTargetIndex < mActive.size()) {
f.mTarget = mActive.get(f.mTargetIndex);
diff --git a/v4/java/android/support/v4/app/FragmentManagerNonConfig.java b/v4/java/android/support/v4/app/FragmentManagerNonConfig.java
new file mode 100644
index 0000000..832426a
--- /dev/null
+++ b/v4/java/android/support/v4/app/FragmentManagerNonConfig.java
@@ -0,0 +1,56 @@
+/*
+ * 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.app;
+
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * FragmentManagerNonConfig stores the retained instance fragments across
+ * activity recreation events.
+ *
+ * <p>Apps should treat objects of this type as opaque, returned by
+ * and passed to the state save and restore process for fragments in
+ * {@link FragmentController#retainNonConfig()} and
+ * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
+ */
+public class FragmentManagerNonConfig {
+ private final List<Fragment> mFragments;
+ private final List<FragmentManagerNonConfig> mChildNonConfigs;
+
+ FragmentManagerNonConfig(List<Fragment> fragments,
+ List<FragmentManagerNonConfig> childNonConfigs) {
+ mFragments = fragments;
+ mChildNonConfigs = childNonConfigs;
+ }
+
+ /**
+ * @return the retained instance fragments returned by a FragmentManager
+ */
+ List<Fragment> getFragments() {
+ return mFragments;
+ }
+
+ /**
+ * @return the FragmentManagerNonConfigs from any applicable fragment's child FragmentManager
+ */
+ List<FragmentManagerNonConfig> getChildNonConfigs() {
+ return mChildNonConfigs;
+ }
+}
diff --git a/v4/java/android/support/v4/app/FragmentPagerAdapter.java b/v4/java/android/support/v4/app/FragmentPagerAdapter.java
index 01ca411..69c72e9 100644
--- a/v4/java/android/support/v4/app/FragmentPagerAdapter.java
+++ b/v4/java/android/support/v4/app/FragmentPagerAdapter.java
@@ -136,9 +136,8 @@
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
- mCurTransaction.commitAllowingStateLoss();
+ mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
- mFragmentManager.executePendingTransactions();
}
}
diff --git a/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java b/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
index ad9072b..7591a39 100644
--- a/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
+++ b/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
@@ -159,9 +159,8 @@
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
- mCurTransaction.commitAllowingStateLoss();
+ mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
- mFragmentManager.executePendingTransactions();
}
}
diff --git a/v4/java/android/support/v4/app/FragmentTransaction.java b/v4/java/android/support/v4/app/FragmentTransaction.java
index 48f2088..4dbc791 100644
--- a/v4/java/android/support/v4/app/FragmentTransaction.java
+++ b/v4/java/android/support/v4/app/FragmentTransaction.java
@@ -162,7 +162,7 @@
public static final int TRANSIT_EXIT_MASK = 0x2000;
/** @hide */
- @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE})
+ @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE, TRANSIT_FRAGMENT_FADE})
@Retention(RetentionPolicy.SOURCE)
private @interface Transit {}
@@ -212,7 +212,7 @@
/**
* Select a standard transition animation for this transaction. May be
* one of {@link #TRANSIT_NONE}, {@link #TRANSIT_FRAGMENT_OPEN},
- * or {@link #TRANSIT_FRAGMENT_CLOSE}
+ * {@link #TRANSIT_FRAGMENT_CLOSE}, or {@link #TRANSIT_FRAGMENT_FADE}.
*/
public abstract FragmentTransaction setTransition(@Transit int transit);
@@ -303,4 +303,45 @@
* to change unexpectedly on the user.
*/
public abstract int commitAllowingStateLoss();
+
+ /**
+ * Commits this transaction synchronously. Any added fragments will be
+ * initialized and brought completely to the lifecycle state of their host
+ * and any removed fragments will be torn down accordingly before this
+ * call returns. Committing a transaction in this way allows fragments
+ * to be added as dedicated, encapsulated components that monitor the
+ * lifecycle state of their host while providing firmer ordering guarantees
+ * around when those fragments are fully initialized and ready. Fragments
+ * that manage views will have those views created and attached.
+ *
+ * <p>Calling <code>commitNow</code> is preferable to calling
+ * {@link #commit()} followed by {@link FragmentManager#executePendingTransactions()}
+ * as the latter will have the side effect of attempting to commit <em>all</em>
+ * currently pending transactions whether that is the desired behavior
+ * or not.</p>
+ *
+ * <p>Transactions committed in this way may not be added to the
+ * FragmentManager's back stack, as doing so would break other expected
+ * ordering guarantees for other asynchronously committed transactions.
+ * This method will throw {@link IllegalStateException} if the transaction
+ * previously requested to be added to the back stack with
+ * {@link #addToBackStack(String)}.</p>
+ *
+ * <p class="note">A transaction can only be committed with this method
+ * prior to its containing activity saving its state. If the commit is
+ * attempted after that point, an exception will be thrown. This is
+ * because the state after the commit can be lost if the activity needs to
+ * be restored from its state. See {@link #commitAllowingStateLoss()} for
+ * situations where it may be okay to lose the commit.</p>
+ */
+ public abstract void commitNow();
+
+ /**
+ * Like {@link #commitNow} but allows the commit to be executed after an
+ * activity's state is saved. This is dangerous because the commit can
+ * be lost if the activity needs to later be restored from its state, so
+ * this should only be used for cases where it is okay for the UI state
+ * to change unexpectedly on the user.
+ */
+ public abstract void commitNowAllowingStateLoss();
}
diff --git a/v4/java/android/support/v4/app/NotificationCompat.java b/v4/java/android/support/v4/app/NotificationCompat.java
index 35e7d30..3e73b4e 100644
--- a/v4/java/android/support/v4/app/NotificationCompat.java
+++ b/v4/java/android/support/v4/app/NotificationCompat.java
@@ -459,6 +459,7 @@
NotificationCompatBase.UnreadConversation getUnreadConversationFromBundle(
Bundle b, NotificationCompatBase.UnreadConversation.Factory factory,
RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory);
+ public NotificationCompatBase.Topic getTopic(Notification n);
}
/**
@@ -542,6 +543,11 @@
}
@Override
+ public NotificationCompatBase.Topic getTopic(Notification n) {
+ return null;
+ }
+
+ @Override
public NotificationCompatBase.UnreadConversation getUnreadConversationFromBundle(
Bundle b, NotificationCompatBase.UnreadConversation.Factory factory,
RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
@@ -787,6 +793,27 @@
}
}
+ static class NotificationCompatImplApi24 extends NotificationCompatImplApi21 {
+ @Override
+ public Notification build(Builder b, BuilderExtender extender) {
+ NotificationCompatApi24.Builder builder = new NotificationCompatApi24.Builder(
+ b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
+ b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
+ b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mShowWhen,
+ b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mCategory,
+ b.mPeople, b.mExtras, b.mColor, b.mVisibility, b.mPublicVersion,
+ b.mGroupKey, b.mGroupSummary, b.mSortKey, b.mTopic);
+ addActionsToBuilder(builder, b.mActions);
+ addStyleToBuilderJellybean(builder, b.mStyle);
+ return extender.build(b, builder);
+ }
+
+ @Override
+ public NotificationCompatBase.Topic getTopic(Notification notification) {
+ return NotificationCompatApi24.getTopic(notification, Topic.FACTORY);
+ }
+ }
+
private static void addActionsToBuilder(NotificationBuilderWithActions builder,
ArrayList<Action> actions) {
for (Action action : actions) {
@@ -825,7 +852,9 @@
}
static {
- if (Build.VERSION.SDK_INT >= 21) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ IMPL = new NotificationCompatImplApi24();
+ } else if (Build.VERSION.SDK_INT >= 21) {
IMPL = new NotificationCompatImplApi21();
} else if (Build.VERSION.SDK_INT >= 20) {
IMPL = new NotificationCompatImplApi20();
@@ -914,6 +943,7 @@
int mColor = COLOR_DEFAULT;
int mVisibility = VISIBILITY_PRIVATE;
Notification mPublicVersion;
+ Topic mTopic;
/** @hide */
public Notification mNotification = new Notification();
@@ -1534,6 +1564,11 @@
return this;
}
+ public Builder setTopic(Topic topic) {
+ mTopic = topic;
+ return this;
+ }
+
/**
* Apply an extender to this notification builder. Extenders may be used to add
* metadata or change options on this builder.
@@ -1575,6 +1610,33 @@
}
}
+ public static class Topic extends NotificationCompatBase.Topic {
+ private final String id;
+ private final CharSequence label;
+
+ public Topic(String id, CharSequence label) {
+ this.id = id;
+ this.label = label;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public CharSequence getLabel() {
+ return label;
+ }
+
+ /** @hide */
+ public static final Factory FACTORY = new Factory() {
+ @Override
+ public Topic build(String id, CharSequence label) {
+ return new Topic(id, label);
+ }
+
+ };
+ }
+
/**
* An object that can apply a rich notification style to a {@link Notification.Builder}
* object.
@@ -3020,7 +3082,7 @@
/**
* Gets the accent color.
*
- * @see setColor
+ * @see #setColor
*/
@ColorInt
public int getColor() {
diff --git a/v4/java/android/support/v4/content/ContextCompat.java b/v4/java/android/support/v4/content/ContextCompat.java
index 16ec1da..05b25d2 100644
--- a/v4/java/android/support/v4/content/ContextCompat.java
+++ b/v4/java/android/support/v4/content/ContextCompat.java
@@ -26,6 +26,7 @@
import android.os.Environment;
import android.os.Process;
import android.support.annotation.NonNull;
+import android.support.v4.os.BuildCompat;
import android.support.v4.os.EnvironmentCompat;
import android.util.Log;
@@ -451,4 +452,51 @@
}
return file;
}
+
+ /**
+ * Return a new Context object for the current Context but whose storage
+ * APIs are backed by device-encrypted storage.
+ * <p>
+ * Data stored in device-encrypted storage is typically encrypted with a key
+ * tied to the physical device, and it can be accessed when the device has
+ * booted successfully, both <em>before and after</em> the user has
+ * authenticated with their credentials (such as a lock pattern or PIN).
+ * Because device-encrypted data is available before user authentication,
+ * you should carefully consider what data you store using this Context.
+ * <p>
+ * If the underlying device does not have the ability to store
+ * device-encrypted and credential-encrypted data using different keys, then
+ * both storage areas will become available at the same time. They remain
+ * two distinct storage areas, and only the window of availability changes.
+ * <p>
+ * Each call to this method returns a new instance of a Context object;
+ * Context objects are not shared, however common state (ClassLoader, other
+ * Resources for the same configuration) may be so the Context itself can be
+ * fairly lightweight.
+ *
+ * @return new Context or {@code null} if device-encrypted storage is not
+ * supported or available on this device.
+ * @see ContextCompat#isDeviceEncryptedStorage(Context)
+ */
+ public static Context createDeviceEncryptedStorageContext(Context context) {
+ if (BuildCompat.isAtLeastN()) {
+ return ContextCompatApi24.createDeviceEncryptedStorageContext(context);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Indicates if the storage APIs of this Context are backed by
+ * device-encrypted storage.
+ *
+ * @see ContextCompat#createDeviceEncryptedStorageContext(Context)
+ */
+ public static boolean isDeviceEncryptedStorage(Context context) {
+ if (BuildCompat.isAtLeastN()) {
+ return ContextCompatApi24.isDeviceEncryptedStorage(context);
+ } else {
+ return false;
+ }
+ }
}
diff --git a/v4/java/android/support/v4/content/FileProvider.java b/v4/java/android/support/v4/content/FileProvider.java
index 515ce1c..9d6d803 100644
--- a/v4/java/android/support/v4/content/FileProvider.java
+++ b/v4/java/android/support/v4/content/FileProvider.java
@@ -365,7 +365,7 @@
*
* @param context A {@link Context} for the current component.
* @param authority The authority of a {@link FileProvider} defined in a
- * {@code <provider>} element in your app's manifest.
+ * {@code <provider>} element in your app's manifest.
* @param file A {@link File} pointing to the filename for which you want a
* <code>content</code> {@link Uri}.
* @return A content URI for the file.
@@ -547,7 +547,7 @@
/**
* Parse and return {@link PathStrategy} for given authority as defined in
- * {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
+ * {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
*
* @see #getPathStrategy(Context, String)
*/
diff --git a/v4/java/android/support/v4/content/res/TypedArrayUtils.java b/v4/java/android/support/v4/content/res/TypedArrayUtils.java
index 79e4ac8..0cc5885 100644
--- a/v4/java/android/support/v4/content/res/TypedArrayUtils.java
+++ b/v4/java/android/support/v4/content/res/TypedArrayUtils.java
@@ -15,10 +15,12 @@
*/
package android.support.v4.content.res;
+import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.AnyRes;
import android.support.annotation.StyleableRes;
+import android.util.TypedValue;
/**
* Compat methods for accessing TypedArray values.
@@ -70,4 +72,13 @@
}
return val;
}
+
+ public static int getAttr(Context context, int attr, int fallbackAttr) {
+ TypedValue value = new TypedValue();
+ context.getTheme().resolveAttribute(attr, value, true);
+ if (value.resourceId != 0) {
+ return attr;
+ }
+ return fallbackAttr;
+ }
}
diff --git a/v4/java/android/support/v4/media/MediaBrowserCompat.java b/v4/java/android/support/v4/media/MediaBrowserCompat.java
index e4b309a..996b1d1 100644
--- a/v4/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserCompat.java
@@ -89,6 +89,9 @@
* to the media browse service when connecting and retrieving the root id
* for browsing, or null if none. The contents of this bundle may affect
* the information returned when browsing.
+ * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_RECENT
+ * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_OFFLINE
+ * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_SUGGESTED
*/
public MediaBrowserCompat(Context context, ComponentName serviceComponent,
ConnectionCallback callback, Bundle rootHints) {
diff --git a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
index e9d6311..618aa1c 100644
--- a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -621,6 +621,9 @@
* root id for browsing, or null if none. The contents of this
* bundle may affect the information returned when browsing.
* @return The {@link BrowserRoot} for accessing this app's content or null.
+ * @see BrowserRoot#EXTRA_RECENT
+ * @see BrowserRoot#EXTRA_OFFLINE
+ * @see BrowserRoot#EXTRA_SUGGESTED
*/
public abstract @Nullable BrowserRoot onGetRoot(@NonNull String clientPackageName,
int clientUid, @Nullable Bundle rootHints);
@@ -944,7 +947,6 @@
*
* @see #EXTRA_OFFLINE
* @see #EXTRA_SUGGESTED
- * @hide
*/
public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
@@ -962,7 +964,6 @@
*
* @see #EXTRA_RECENT
* @see #EXTRA_SUGGESTED
- * @hide
*/
public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
@@ -972,7 +973,8 @@
*
* <p>When creating a media browser for a given media browser service, this key can be
* supplied as a root hint for retrieving the media items suggested by the media browser
- * service.
+ * service. The list of media items passed in {@link android.support.v4.media.MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded(String, List)}
+ * is considered ordered by relevance, first being the top suggestion.
* If the media browser service can provide such media items, the implementation must return
* the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
*
@@ -980,7 +982,6 @@
*
* @see #EXTRA_RECENT
* @see #EXTRA_OFFLINE
- * @hide
*/
public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
diff --git a/v4/java/android/support/v4/net/TrafficStatsCompat.java b/v4/java/android/support/v4/net/TrafficStatsCompat.java
index ea495b1..57b8a7e 100644
--- a/v4/java/android/support/v4/net/TrafficStatsCompat.java
+++ b/v4/java/android/support/v4/net/TrafficStatsCompat.java
@@ -18,6 +18,7 @@
import android.os.Build;
+import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
@@ -35,6 +36,8 @@
void setThreadStatsTag(int tag);
void tagSocket(Socket socket) throws SocketException;
void untagSocket(Socket socket) throws SocketException;
+ void tagDatagramSocket(DatagramSocket socket) throws SocketException;
+ void untagDatagramSocket(DatagramSocket socket) throws SocketException;
}
static class BaseTrafficStatsCompatImpl implements TrafficStatsCompatImpl {
@@ -79,6 +82,14 @@
@Override
public void untagSocket(Socket socket) {
}
+
+ @Override
+ public void tagDatagramSocket(DatagramSocket socket) {
+ }
+
+ @Override
+ public void untagDatagramSocket(DatagramSocket socket) {
+ }
}
static class IcsTrafficStatsCompatImpl implements TrafficStatsCompatImpl {
@@ -116,12 +127,36 @@
public void untagSocket(Socket socket) throws SocketException {
TrafficStatsCompatIcs.untagSocket(socket);
}
+
+ @Override
+ public void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStatsCompatIcs.tagDatagramSocket(socket);
+ }
+
+ @Override
+ public void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStatsCompatIcs.untagDatagramSocket(socket);
+ }
+ }
+
+ static class Api24TrafficStatsCompatImpl extends IcsTrafficStatsCompatImpl {
+ @Override
+ public void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStatsCompatApi24.tagDatagramSocket(socket);
+ }
+
+ @Override
+ public void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStatsCompatApi24.untagDatagramSocket(socket);
+ }
}
private static final TrafficStatsCompatImpl IMPL;
static {
- if (Build.VERSION.SDK_INT >= 14) {
+ if ("N".equals(Build.VERSION.CODENAME)) {
+ IMPL = new Api24TrafficStatsCompatImpl();
+ } else if (Build.VERSION.SDK_INT >= 14) {
IMPL = new IcsTrafficStatsCompatImpl();
} else {
IMPL = new BaseTrafficStatsCompatImpl();
@@ -201,5 +236,25 @@
IMPL.untagSocket(socket);
}
+ /**
+ * Tag the given {@link DatagramSocket} with any statistics parameters
+ * active for the current thread. Subsequent calls always replace any
+ * existing parameters. When finished, call
+ * {@link #untagDatagramSocket(DatagramSocket)} to remove statistics
+ * parameters.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ IMPL.tagDatagramSocket(socket);
+ }
+
+ /**
+ * Remove any statistics parameters from the given {@link DatagramSocket}.
+ */
+ public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ IMPL.untagDatagramSocket(socket);
+ }
+
private TrafficStatsCompat() {}
}
diff --git a/v4/java/android/support/v4/os/BuildCompat.java b/v4/java/android/support/v4/os/BuildCompat.java
new file mode 100644
index 0000000..513e153
--- /dev/null
+++ b/v4/java/android/support/v4/os/BuildCompat.java
@@ -0,0 +1,40 @@
+/*
+ * 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.Build.VERSION;
+
+/**
+ * BuildCompat contains additional platform version checking methods for
+ * testing compatibility with new features.
+ */
+public class BuildCompat {
+ private BuildCompat() {
+ }
+
+ /**
+ * Check if the device is running on the Android N release or newer.
+ * This method is suitable for use with preview SDKs and associated
+ * prerelease device builds.
+ *
+ * @return true if N APIs are available for use
+ */
+ public static boolean isAtLeastN() {
+ return "N".equals(VERSION.CODENAME);
+ }
+}
diff --git a/v4/java/android/support/v4/os/UserManagerCompat.java b/v4/java/android/support/v4/os/UserManagerCompat.java
new file mode 100644
index 0000000..436bfc8
--- /dev/null
+++ b/v4/java/android/support/v4/os/UserManagerCompat.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+
+/**
+ * Helper for accessing features in {@link android.os.UserManager}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class UserManagerCompat {
+ /**
+ * Return whether the calling user is running in a "locked" state. A user is
+ * unlocked only after they've entered their credentials (such as a lock
+ * pattern or PIN), and credential-encrypted private app data storage is
+ * available.
+ *
+ * @removed
+ */
+ @Deprecated
+ public static boolean isUserRunningAndLocked(Context context) {
+ return !isUserUnlocked(context);
+ }
+
+ /**
+ * Return whether the calling user is running in an "unlocked" state. A user
+ * is unlocked only after they've entered their credentials (such as a lock
+ * pattern or PIN), and credential-encrypted private app data storage is
+ * available.
+ *
+ * @removed
+ */
+ @Deprecated
+ public static boolean isUserRunningAndUnlocked(Context context) {
+ return isUserUnlocked(context);
+ }
+
+ /**
+ * Return whether the calling user is running in an "unlocked" state. A user
+ * is unlocked only after they've entered their credentials (such as a lock
+ * pattern or PIN), and credential-encrypted private app data storage is
+ * available.
+ */
+ public static boolean isUserUnlocked(Context context) {
+ if (BuildCompat.isAtLeastN()) {
+ return UserManagerCompatApi24.isUserUnlocked(context);
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/v4/java/android/support/v4/print/PrintHelper.java b/v4/java/android/support/v4/print/PrintHelper.java
index 23429ec..7fcf4ab 100644
--- a/v4/java/android/support/v4/print/PrintHelper.java
+++ b/v4/java/android/support/v4/print/PrintHelper.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
+
import android.net.Uri;
import java.io.FileNotFoundException;
@@ -151,13 +152,14 @@
}
/**
- * Implementation used on KitKat (and above)
+ * Generic implementation for KitKat to Api24
*/
- private static final class PrintHelperKitkatImpl implements PrintHelperVersionImpl {
- private final PrintHelperKitkat mPrintHelper;
+ private static class PrintHelperImpl<RealHelper extends PrintHelperKitkat>
+ implements PrintHelperVersionImpl {
+ private final RealHelper mPrintHelper;
- PrintHelperKitkatImpl(Context context) {
- mPrintHelper = new PrintHelperKitkat(context);
+ protected PrintHelperImpl(RealHelper helper) {
+ mPrintHelper = helper;
}
@Override
@@ -193,9 +195,9 @@
@Override
public void printBitmap(String jobName, Bitmap bitmap,
final OnPrintFinishCallback callback) {
- PrintHelperKitkat.OnPrintFinishCallback delegateCallback = null;
+ RealHelper.OnPrintFinishCallback delegateCallback = null;
if (callback != null) {
- delegateCallback = new PrintHelperKitkat.OnPrintFinishCallback() {
+ delegateCallback = new RealHelper.OnPrintFinishCallback() {
@Override
public void onFinish() {
callback.onFinish();
@@ -208,9 +210,9 @@
@Override
public void printBitmap(String jobName, Uri imageFile,
final OnPrintFinishCallback callback) throws FileNotFoundException {
- PrintHelperKitkat.OnPrintFinishCallback delegateCallback = null;
+ RealHelper.OnPrintFinishCallback delegateCallback = null;
if (callback != null) {
- delegateCallback = new PrintHelperKitkat.OnPrintFinishCallback() {
+ delegateCallback = new RealHelper.OnPrintFinishCallback() {
@Override
public void onFinish() {
callback.onFinish();
@@ -222,6 +224,33 @@
}
/**
+ * Implementation used on KitKat
+ */
+ private static final class PrintHelperKitkatImpl extends PrintHelperImpl<PrintHelperKitkat> {
+ PrintHelperKitkatImpl(Context context) {
+ super(new PrintHelperKitkat(context));
+ }
+ }
+
+ /**
+ * Implementation used on Api20 to Api23
+ */
+ private static final class PrintHelperApi20Impl extends PrintHelperImpl<PrintHelperApi20> {
+ PrintHelperApi20Impl(Context context) {
+ super(new PrintHelperApi20(context));
+ }
+ }
+
+ /**
+ * Implementation used on Api24 and above
+ */
+ private static final class PrintHelperApi24Impl extends PrintHelperImpl<PrintHelperApi24> {
+ PrintHelperApi24Impl(Context context) {
+ super(new PrintHelperApi24(context));
+ }
+ }
+
+ /**
* Returns the PrintHelper that can be used to print images.
*
* @param context A context for accessing system resources.
@@ -229,7 +258,13 @@
*/
public PrintHelper(Context context) {
if (systemSupportsPrint()) {
- mImpl = new PrintHelperKitkatImpl(context);
+ if (Build.VERSION.CODENAME.equals("N") ) {
+ mImpl = new PrintHelperApi24Impl(context);
+ } else if (Build.VERSION.SDK_INT >= 20) {
+ mImpl = new PrintHelperApi20Impl(context);
+ } else {
+ mImpl = new PrintHelperKitkatImpl(context);
+ }
} else {
mImpl = new PrintHelperStubImpl();
}
diff --git a/v4/java/android/support/v4/util/SimpleArrayMap.java b/v4/java/android/support/v4/util/SimpleArrayMap.java
index 1b1577a..c7d4b5d 100644
--- a/v4/java/android/support/v4/util/SimpleArrayMap.java
+++ b/v4/java/android/support/v4/util/SimpleArrayMap.java
@@ -522,17 +522,42 @@
/**
* {@inheritDoc}
*
- * <p>This implementation returns false if the object is not a map, or
- * if the maps have different sizes. Otherwise, for each key in this map,
- * values of both maps are compared. If the values for any key are not
- * equal, the method returns false, otherwise it returns true.
+ * <p>This implementation returns false if the object is not a Map or
+ * SimpleArrayMap, or if the maps have different sizes. Otherwise, for each
+ * key in this map, values of both maps are compared. If the values for any
+ * key are not equal, the method returns false, otherwise it returns true.
*/
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
- if (object instanceof Map) {
+ if (object instanceof SimpleArrayMap) {
+ SimpleArrayMap<?, ?> map = (SimpleArrayMap<?, ?>) object;
+ if (size() != map.size()) {
+ return false;
+ }
+
+ try {
+ for (int i=0; i<mSize; i++) {
+ K key = keyAt(i);
+ V mine = valueAt(i);
+ Object theirs = map.get(key);
+ if (mine == null) {
+ if (theirs != null || !map.containsKey(key)) {
+ return false;
+ }
+ } else if (!mine.equals(theirs)) {
+ return false;
+ }
+ }
+ } catch (NullPointerException ignored) {
+ return false;
+ } catch (ClassCastException ignored) {
+ return false;
+ }
+ return true;
+ } else if (object instanceof Map) {
Map<?, ?> map = (Map<?, ?>) object;
if (size() != map.size()) {
return false;
diff --git a/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java b/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java
index 0ea755e..4d0281e 100644
--- a/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java
+++ b/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java
@@ -27,6 +27,18 @@
/**
* Helper for accessing {@link View.AccessibilityDelegate} introduced after
* API level 4 in a backwards compatible fashion.
+ * <p>
+ * <strong>Note:</strong> On platform versions prior to
+ * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+ * views in the {@code android.widget.*} package are called <i>before</i>
+ * host methods. This prevents certain properties such as class name from
+ * being modified by overriding
+ * {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)},
+ * as any changes will be overwritten by the host class.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+ * methods are called <i>after</i> host methods, which all properties to be
+ * modified without being overwritten by the host class.
*/
public class AccessibilityDelegateCompat {
diff --git a/v4/java/android/support/v4/view/AsyncLayoutInflater.java b/v4/java/android/support/v4/view/AsyncLayoutInflater.java
new file mode 100644
index 0000000..6e88f59
--- /dev/null
+++ b/v4/java/android/support/v4/view/AsyncLayoutInflater.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2015 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.view;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+import android.support.v4.util.Pools.SynchronizedPool;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.concurrent.ArrayBlockingQueue;
+
+/**
+ * <p>Helper class for inflating layouts asynchronously. To use, construct
+ * an instance of {@link AsyncLayoutInflater} on the UI thread and call
+ * {@link #inflate(int, ViewGroup, OnInflateFinishedListener)}. The
+ * {@link OnInflateFinishedListener} will be invoked on the UI thread
+ * when the inflate request has completed.
+ *
+ * <p>This is intended for parts of the UI that are created lazily or in
+ * response to user interactions. This allows the UI thread to continue
+ * to be responsive & animate while the relatively heavy inflate
+ * is being performed.
+ *
+ * <p>For a layout to be inflated asynchronously it needs to have a parent
+ * whose {@link ViewGroup#generateLayoutParams(AttributeSet)} is thread-safe
+ * and all the Views being constructed as part of inflation must not create
+ * any {@link Handler}s or otherwise call {@link Looper#myLooper()}. If the
+ * layout that is trying to be inflated cannot be constructed
+ * asynchronously for whatever reason, {@link AsyncLayoutInflater} will
+ * automatically fall back to inflating on the UI thread.
+ *
+ * <p>NOTE that the inflated View hierarchy is NOT added to the parent. It is
+ * equivalent to calling {@link LayoutInflater#inflate(int, ViewGroup, boolean)}
+ * with attachToRoot set to false. Callers will likely want to call
+ * {@link ViewGroup#addView(View)} in the {@link OnInflateFinishedListener}
+ * callback at a minimum.
+ *
+ * <p>This inflater does not support setting a {@link LayoutInflater.Factory}
+ * nor {@link LayoutInflater.Factory2}. Similarly it does not support inflating
+ * layouts that contain fragments.
+ */
+public final class AsyncLayoutInflater {
+ private static final String TAG = "AsyncLayoutInflater";
+
+ private LayoutInflater mInflater;
+ private Handler mHandler;
+ private InflateThread mInflateThread;
+
+ public AsyncLayoutInflater(@NonNull Context context) {
+ mInflater = new BasicInflater(context);
+ mHandler = new Handler(mHandlerCallback);
+ mInflateThread = InflateThread.getInstance();
+ }
+
+ @UiThread
+ public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
+ @NonNull OnInflateFinishedListener callback) {
+ if (callback == null) {
+ throw new NullPointerException("callback argument may not be null!");
+ }
+ InflateRequest request = mInflateThread.obtainRequest();
+ request.inflater = this;
+ request.resid = resid;
+ request.parent = parent;
+ request.callback = callback;
+ mInflateThread.enqueue(request);
+ }
+
+ private Callback mHandlerCallback = new Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ InflateRequest request = (InflateRequest) msg.obj;
+ if (request.view == null) {
+ request.view = mInflater.inflate(
+ request.resid, request.parent, false);
+ }
+ request.callback.onInflateFinished(
+ request.view, request.resid, request.parent);
+ mInflateThread.releaseRequest(request);
+ return true;
+ }
+ };
+
+ public interface OnInflateFinishedListener {
+ public void onInflateFinished(View view, int resid, ViewGroup parent);
+ }
+
+ private static class InflateRequest {
+ AsyncLayoutInflater inflater;
+ ViewGroup parent;
+ int resid;
+ View view;
+ OnInflateFinishedListener callback;
+ }
+
+ private static class BasicInflater extends LayoutInflater {
+ private static final String[] sClassPrefixList = {
+ "android.widget.",
+ "android.webkit.",
+ "android.app."
+ };
+
+ public BasicInflater(Context context) {
+ super(context);
+ }
+
+ @Override
+ public LayoutInflater cloneInContext(Context newContext) {
+ return new BasicInflater(newContext);
+ }
+
+ @Override
+ protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+ for (String prefix : sClassPrefixList) {
+ try {
+ View view = createView(name, prefix, attrs);
+ if (view != null) {
+ return view;
+ }
+ } catch (ClassNotFoundException e) {
+ // In this case we want to let the base class take a crack
+ // at it.
+ }
+ }
+
+ return super.onCreateView(name, attrs);
+ }
+ }
+
+ private static class InflateThread extends Thread {
+ private static final InflateThread sInstance;
+ static {
+ sInstance = new InflateThread();
+ sInstance.start();
+ }
+
+ public static InflateThread getInstance() {
+ return sInstance;
+ }
+
+ private ArrayBlockingQueue<InflateRequest> mQueue
+ = new ArrayBlockingQueue<>(10);
+ private SynchronizedPool<InflateRequest> mRequestPool
+ = new SynchronizedPool<>(10);
+
+ @Override
+ public void run() {
+ while (true) {
+ InflateRequest request;
+ try {
+ request = mQueue.take();
+ } catch (InterruptedException ex) {
+ // Odd, just continue
+ Log.w(TAG, ex);
+ continue;
+ }
+
+ try {
+ request.view = request.inflater.mInflater.inflate(
+ request.resid, request.parent, false);
+ } catch (RuntimeException ex) {
+ // Probably a Looper failure, retry on the UI thread
+ Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI thread",
+ ex);
+ }
+ Message.obtain(request.inflater.mHandler, 0, request)
+ .sendToTarget();
+ }
+ }
+
+ public InflateRequest obtainRequest() {
+ InflateRequest obj = mRequestPool.acquire();
+ if (obj == null) {
+ obj = new InflateRequest();
+ }
+ return obj;
+ }
+
+ public void releaseRequest(InflateRequest obj) {
+ obj.callback = null;
+ obj.inflater = null;
+ obj.parent = null;
+ obj.resid = 0;
+ obj.view = null;
+ mRequestPool.release(obj);
+ }
+
+ public void enqueue(InflateRequest request) {
+ try {
+ mQueue.put(request);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(
+ "Failed to enqueue async inflate request", e);
+ }
+ }
+ }
+}
diff --git a/v4/java/android/support/v4/view/KeyEventCompat.java b/v4/java/android/support/v4/view/KeyEventCompat.java
index 02b6622..ee5a914 100644
--- a/v4/java/android/support/v4/view/KeyEventCompat.java
+++ b/v4/java/android/support/v4/view/KeyEventCompat.java
@@ -28,14 +28,14 @@
* Interface for the full API.
*/
interface KeyEventVersionImpl {
- public int normalizeMetaState(int metaState);
- public boolean metaStateHasModifiers(int metaState, int modifiers);
- public boolean metaStateHasNoModifiers(int metaState);
- public void startTracking(KeyEvent event);
- public boolean isTracking(KeyEvent event);
- public Object getKeyDispatcherState(View view);
- public boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state,
- Object target);
+ int normalizeMetaState(int metaState);
+ boolean metaStateHasModifiers(int metaState, int modifiers);
+ boolean metaStateHasNoModifiers(int metaState);
+ void startTracking(KeyEvent event);
+ boolean isTracking(KeyEvent event);
+ Object getKeyDispatcherState(View view);
+ boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state, Object target);
+ boolean isCtrlPressed(KeyEvent event);
}
/**
@@ -113,6 +113,11 @@
Object target) {
return event.dispatch(receiver);
}
+
+ @Override
+ public boolean isCtrlPressed(KeyEvent event) {
+ return false;
+ }
}
static class EclairKeyEventVersionImpl extends BaseKeyEventVersionImpl {
@@ -156,6 +161,11 @@
public boolean metaStateHasNoModifiers(int metaState) {
return KeyEventCompatHoneycomb.metaStateHasNoModifiers(metaState);
}
+
+ @Override
+ public boolean isCtrlPressed(KeyEvent event) {
+ return KeyEventCompatHoneycomb.isCtrlPressed(event);
+ }
}
/**
@@ -209,5 +219,9 @@
return IMPL.dispatch(event, receiver, state, target);
}
+ public static boolean isCtrlPressed(KeyEvent event) {
+ return IMPL.isCtrlPressed(event);
+ }
+
private KeyEventCompat() {}
}
diff --git a/v4/java/android/support/v4/view/MotionEventCompat.java b/v4/java/android/support/v4/view/MotionEventCompat.java
index 964a669..bb93ab2 100644
--- a/v4/java/android/support/v4/view/MotionEventCompat.java
+++ b/v4/java/android/support/v4/view/MotionEventCompat.java
@@ -36,6 +36,7 @@
public int getSource(MotionEvent event);
float getAxisValue(MotionEvent event, int axis);
float getAxisValue(MotionEvent event, int axis, int pointerIndex);
+ int getButtonState(MotionEvent event);
}
/**
@@ -91,6 +92,11 @@
public float getAxisValue(MotionEvent event, int axis, int pointerIndex) {
return 0;
}
+
+ @Override
+ public int getButtonState(MotionEvent event) {
+ return 0;
+ }
}
/**
@@ -145,12 +151,25 @@
}
}
+
+ /**
+ * Interface implementation for devices with at least v14 APIs.
+ */
+ private static class ICSMotionEventVersionImpl extends GingerbreadMotionEventVersionImpl {
+ @Override
+ public int getButtonState(MotionEvent event) {
+ return MotionEventCompatICS.getButtonState(event);
+ }
+ }
+
/**
* Select the correct implementation to use for the current platform.
*/
static final MotionEventVersionImpl IMPL;
static {
- if (Build.VERSION.SDK_INT >= 12) {
+ if (Build.VERSION.SDK_INT >= 14) {
+ IMPL = new ICSMotionEventVersionImpl();
+ } else if (Build.VERSION.SDK_INT >= 12) {
IMPL = new HoneycombMr1MotionEventVersionImpl();
} else if (Build.VERSION.SDK_INT >= 9) {
IMPL = new GingerbreadMotionEventVersionImpl();
@@ -339,6 +358,16 @@
public static final int AXIS_TILT = 25;
/**
+ * Synonym for {@link MotionEvent#AXIS_RELATIVE_X}.
+ */
+ public static final int AXIS_RELATIVE_X = 27;
+
+ /**
+ * Synonym for {@link MotionEvent#AXIS_RELATIVE_Y}.
+ */
+ public static final int AXIS_RELATIVE_Y = 28;
+
+ /**
* Synonym for {@link MotionEvent#AXIS_GENERIC_1}.
*/
public static final int AXIS_GENERIC_1 = 32;
@@ -419,6 +448,11 @@
public static final int AXIS_GENERIC_16 = 47;
/**
+ * Synonym for {@link MotionEvent#BUTTON_PRIMARY}.
+ */
+ public static final int BUTTON_PRIMARY = 1;
+
+ /**
* Call {@link MotionEvent#getAction}, returning only the {@link #ACTION_MASK}
* portion.
*/
@@ -489,6 +523,15 @@
}
/**
+ * Determines whether the event is from the given source.
+ * @param source The input source to check against.
+ * @return Whether the event is from the given source.
+ */
+ public static boolean isFromSource(MotionEvent event, int source) {
+ return (getSource(event) & source) == source;
+ }
+
+ /**
* Get axis value for the first pointer index (may be an
* arbitrary pointer identifier).
*
@@ -517,5 +560,14 @@
return IMPL.getAxisValue(event, axis, pointerIndex);
}
+ /**
+ *
+ * @param event
+ * @return
+ */
+ public static int getButtonState(MotionEvent event) {
+ return IMPL.getButtonState(event);
+ }
+
private MotionEventCompat() {}
}
diff --git a/v4/java/android/support/v4/view/PointerIconCompat.java b/v4/java/android/support/v4/view/PointerIconCompat.java
new file mode 100644
index 0000000..2e78ffa
--- /dev/null
+++ b/v4/java/android/support/v4/view/PointerIconCompat.java
@@ -0,0 +1,220 @@
+/**
+ * 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.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.support.v4.os.BuildCompat;
+
+/**
+ * Helper for accessing features in {@link android.view.PointerIcon} introduced after API
+ * level 4 in a backwards compatible fashion.
+ */
+public final class PointerIconCompat {
+ /** Synonym for {@link android.view.PointerIcon#STYLE_NULL} */
+ public static final int STYLE_NULL = 0;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_ARROW} */
+ public static final int STYLE_ARROW = 1000;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_CONTEXT_MENU} */
+ public static final int STYLE_CONTEXT_MENU = 1001;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_HAND} */
+ public static final int STYLE_HAND = 1002;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_HELP} */
+ public static final int STYLE_HELP = 1003;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_WAIT} */
+ public static final int STYLE_WAIT = 1004;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_CELL} */
+ public static final int STYLE_CELL = 1006;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_CROSSHAIR} */
+ public static final int STYLE_CROSSHAIR = 1007;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_TEXT} */
+ public static final int STYLE_TEXT = 1008;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_VERTICAL_TEXT} */
+ public static final int STYLE_VERTICAL_TEXT = 1009;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_ALIAS} */
+ public static final int STYLE_ALIAS = 1010;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_COPY} */
+ public static final int STYLE_COPY = 1011;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_NO_DROP} */
+ public static final int STYLE_NO_DROP = 1012;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_ALL_SCROLL} */
+ public static final int STYLE_ALL_SCROLL = 1013;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_HORIZONTAL_DOUBLE_ARROW} */
+ public static final int STYLE_HORIZONTAL_DOUBLE_ARROW = 1014;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_VERTICAL_DOUBLE_ARROW} */
+ public static final int STYLE_VERTICAL_DOUBLE_ARROW = 1015;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW} */
+ public static final int STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW} */
+ public static final int STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_ZOOM_IN} */
+ public static final int STYLE_ZOOM_IN = 1018;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_ZOOM_OUT} */
+ public static final int STYLE_ZOOM_OUT = 1019;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_GRAB} */
+ public static final int STYLE_GRAB = 1020;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_GRABBING} */
+ public static final int STYLE_GRABBING = 1021;
+
+ /** Synonym for {@link android.view.PointerIcon#STYLE_DEFAULT} */
+ public static final int STYLE_DEFAULT = STYLE_ARROW;
+
+
+ private Object mPointerIcon;
+
+ private PointerIconCompat(Object pointerIcon) {
+ mPointerIcon = pointerIcon;
+ }
+
+ private static PointerIconCompat create(Object pointerIcon) {
+ return new PointerIconCompat(pointerIcon);
+ }
+
+ /**
+ * @hide
+ */
+ public Object getPointerIcon() {
+ return mPointerIcon;
+ }
+
+ interface PointerIconCompatImpl {
+ Object getSystemIcon(Context context, int style);
+ Object createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY);
+ Object loadCustomIcon(Resources resources, int resourceId);
+ }
+
+ static class BasePointerIconCompatImpl implements PointerIconCompatImpl {
+ @Override
+ public Object getSystemIcon(Context context, int style) {
+ return null;
+ }
+
+ @Override
+ public Object createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ return null;
+ }
+
+ @Override
+ public Object loadCustomIcon(Resources resources, int resourceId) {
+ return null;
+ }
+ }
+
+ static class Api24PointerIconCompatImpl extends BasePointerIconCompatImpl {
+ @Override
+ public Object getSystemIcon(Context context, int style) {
+ return PointerIconCompatApi24.getSystemIcon(context, style);
+ }
+
+ @Override
+ public Object createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ return PointerIconCompatApi24.createCustomIcon(bitmap, hotSpotX, hotSpotY);
+ }
+
+ @Override
+ public Object loadCustomIcon(Resources resources, int resourceId) {
+ return PointerIconCompatApi24.loadCustomIcon(resources, resourceId);
+ }
+ }
+
+ static final PointerIconCompatImpl IMPL;
+ static {
+ if (BuildCompat.isAtLeastN()) {
+ IMPL = new Api24PointerIconCompatImpl();
+ } else {
+ IMPL = new BasePointerIconCompatImpl();
+ }
+ }
+
+ /**
+ * Gets a system pointer icon for the given style.
+ * If style is not recognized, returns the default pointer icon.
+ *
+ * @param context The context.
+ * @param style The pointer icon style.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ */
+ public static PointerIconCompat getSystemIcon(Context context, int style) {
+ return create(IMPL.getSystemIcon(context, style));
+ }
+
+ /**
+ * Creates a custom pointer from the given bitmap and hotspot information.
+ *
+ * @param bitmap The bitmap for the icon.
+ * @param hotSpotX The X offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getWidth()) range.
+ * @param hotSpotY The Y offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getHeight()) range.
+ * @return A pointer icon for this bitmap.
+ *
+ * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
+ * parameters are invalid.
+ */
+ public static PointerIconCompat createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ return create(IMPL.createCustomIcon(bitmap, hotSpotX, hotSpotY));
+ }
+
+ /**
+ * Loads a custom pointer icon from an XML resource.
+ * <p>
+ * The XML resource should have the following form:
+ * <code>
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:bitmap="@drawable/my_pointer_bitmap"
+ * android:hotSpotX="24"
+ * android:hotSpotY="24" />
+ * </code>
+ * </p>
+ *
+ * @param resources The resources object.
+ * @param resourceId The resource id.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if resources is null.
+ * @throws Resources.NotFoundException if the resource was not found or the drawable
+ * linked in the resource was not found.
+ */
+ public static PointerIconCompat loadCustomIcon(Resources resources, int resourceId) {
+ return create(IMPL.loadCustomIcon(resources, resourceId));
+ }
+}
diff --git a/v4/java/android/support/v4/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index 49500f8..8ef49a9 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -28,6 +28,7 @@
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.v4.os.BuildCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
import android.util.Log;
@@ -49,9 +50,24 @@
* Helper for accessing features in {@link View} introduced after API
* level 4 in a backwards compatible fashion.
*/
-public final class ViewCompat {
+public class ViewCompat {
private static final String TAG = "ViewCompat";
+ /** @hide */
+ @IntDef({View.FOCUS_LEFT, View.FOCUS_UP, View.FOCUS_RIGHT, View.FOCUS_DOWN,
+ View.FOCUS_FORWARD, View.FOCUS_BACKWARD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusDirection {}
+
+ /** @hide */
+ @IntDef({View.FOCUS_LEFT, View.FOCUS_UP, View.FOCUS_RIGHT, View.FOCUS_DOWN})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusRealDirection {}
+
+ /** @hide */
+ @IntDef({View.FOCUS_FORWARD, View.FOCUS_BACKWARD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusRelativeDirection {}
/** @hide */
@IntDef({OVER_SCROLL_ALWAYS, OVER_SCROLL_IF_CONTENT_SCROLLS, OVER_SCROLL_NEVER})
@@ -292,36 +308,36 @@
/**
* Scroll indicator direction for the top edge of the view.
*
- * @see #setScrollIndicators(int)
- * @see #setScrollIndicators(int, int)
- * @see #getScrollIndicators()
+ * @see #setScrollIndicators(View, int)
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_TOP = 0x1;
/**
* Scroll indicator direction for the bottom edge of the view.
*
- * @see #setScrollIndicators(int)
- * @see #setScrollIndicators(int, int)
- * @see #getScrollIndicators()
+ * @see #setScrollIndicators(View, int)
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_BOTTOM = 0x2;
/**
* Scroll indicator direction for the left edge of the view.
*
- * @see #setScrollIndicators(int)
- * @see #setScrollIndicators(int, int)
- * @see #getScrollIndicators()
+ * @see #setScrollIndicators(View, int)
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_LEFT = 0x4;
/**
* Scroll indicator direction for the right edge of the view.
*
- * @see #setScrollIndicators(int)
- * @see #setScrollIndicators(int, int)
- * @see #getScrollIndicators()
+ * @see #setScrollIndicators(View, int)
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_RIGHT = 0x8;
@@ -337,93 +353,93 @@
/**
* Scroll indicator direction for the ending edge of the view.
*
- * @see #setScrollIndicators(int)
- * @see #setScrollIndicators(int, int)
- * @see #getScrollIndicators()
+ * @see #setScrollIndicators(View, int)
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_END = 0x20;
interface ViewCompatImpl {
- public boolean canScrollHorizontally(View v, int direction);
- public boolean canScrollVertically(View v, int direction);
- public int getOverScrollMode(View v);
- public void setOverScrollMode(View v, int mode);
- public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event);
- public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event);
- public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info);
- public void setAccessibilityDelegate(View v, @Nullable AccessibilityDelegateCompat delegate);
- public boolean hasAccessibilityDelegate(View v);
- public boolean hasTransientState(View view);
- public void setHasTransientState(View view, boolean hasTransientState);
- public void postInvalidateOnAnimation(View view);
- public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom);
- public void postOnAnimation(View view, Runnable action);
- public void postOnAnimationDelayed(View view, Runnable action, long delayMillis);
- public int getImportantForAccessibility(View view);
- public void setImportantForAccessibility(View view, int mode);
- public boolean isImportantForAccessibility(View view);
- public boolean performAccessibilityAction(View view, int action, Bundle arguments);
- public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view);
- public float getAlpha(View view);
- public void setLayerType(View view, int layerType, Paint paint);
- public int getLayerType(View view);
- public int getLabelFor(View view);
- public void setLabelFor(View view, int id);
- public void setLayerPaint(View view, Paint paint);
- public int getLayoutDirection(View view);
- public void setLayoutDirection(View view, int layoutDirection);
- public ViewParent getParentForAccessibility(View view);
- public boolean isOpaque(View view);
- public int resolveSizeAndState(int size, int measureSpec, int childMeasuredState);
- public int getMeasuredWidthAndState(View view);
- public int getMeasuredHeightAndState(View view);
- public int getMeasuredState(View view);
- public int getAccessibilityLiveRegion(View view);
- public void setAccessibilityLiveRegion(View view, int mode);
- public int getPaddingStart(View view);
- public int getPaddingEnd(View view);
- public void setPaddingRelative(View view, int start, int top, int end, int bottom);
- public void dispatchStartTemporaryDetach(View view);
- public void dispatchFinishTemporaryDetach(View view);
- public float getX(View view);
- public float getY(View view);
- public float getRotation(View view);
- public float getRotationX(View view);
- public float getRotationY(View view);
- public float getScaleX(View view);
- public float getScaleY(View view);
- public float getTranslationX(View view);
- public float getTranslationY(View view);
- public int getMinimumWidth(View view);
- public int getMinimumHeight(View view);
- public ViewPropertyAnimatorCompat animate(View view);
- public void setRotation(View view, float value);
- public void setRotationX(View view, float value);
- public void setRotationY(View view, float value);
- public void setScaleX(View view, float value);
- public void setScaleY(View view, float value);
- public void setTranslationX(View view, float value);
- public void setTranslationY(View view, float value);
- public void setX(View view, float value);
- public void setY(View view, float value);
- public void setAlpha(View view, float value);
- public void setPivotX(View view, float value);
- public void setPivotY(View view, float value);
- public float getPivotX(View view);
- public float getPivotY(View view);
- public void setElevation(View view, float elevation);
- public float getElevation(View view);
- public void setTranslationZ(View view, float translationZ);
- public float getTranslationZ(View view);
- public void setClipBounds(View view, Rect clipBounds);
- public Rect getClipBounds(View view);
- public void setTransitionName(View view, String transitionName);
- public String getTransitionName(View view);
- public int getWindowSystemUiVisibility(View view);
- public void requestApplyInsets(View view);
- public void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled);
- public boolean getFitsSystemWindows(View view);
- public boolean hasOverlappingRendering(View view);
+ boolean canScrollHorizontally(View v, int direction);
+ boolean canScrollVertically(View v, int direction);
+ int getOverScrollMode(View v);
+ void setOverScrollMode(View v, int mode);
+ void onInitializeAccessibilityEvent(View v, AccessibilityEvent event);
+ void onPopulateAccessibilityEvent(View v, AccessibilityEvent event);
+ void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info);
+ void setAccessibilityDelegate(View v, @Nullable AccessibilityDelegateCompat delegate);
+ boolean hasAccessibilityDelegate(View v);
+ boolean hasTransientState(View view);
+ void setHasTransientState(View view, boolean hasTransientState);
+ void postInvalidateOnAnimation(View view);
+ void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom);
+ void postOnAnimation(View view, Runnable action);
+ void postOnAnimationDelayed(View view, Runnable action, long delayMillis);
+ int getImportantForAccessibility(View view);
+ void setImportantForAccessibility(View view, int mode);
+ boolean isImportantForAccessibility(View view);
+ boolean performAccessibilityAction(View view, int action, Bundle arguments);
+ AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view);
+ float getAlpha(View view);
+ void setLayerType(View view, int layerType, Paint paint);
+ int getLayerType(View view);
+ int getLabelFor(View view);
+ void setLabelFor(View view, int id);
+ void setLayerPaint(View view, Paint paint);
+ int getLayoutDirection(View view);
+ void setLayoutDirection(View view, int layoutDirection);
+ ViewParent getParentForAccessibility(View view);
+ boolean isOpaque(View view);
+ int resolveSizeAndState(int size, int measureSpec, int childMeasuredState);
+ int getMeasuredWidthAndState(View view);
+ int getMeasuredHeightAndState(View view);
+ int getMeasuredState(View view);
+ int getAccessibilityLiveRegion(View view);
+ void setAccessibilityLiveRegion(View view, int mode);
+ int getPaddingStart(View view);
+ int getPaddingEnd(View view);
+ void setPaddingRelative(View view, int start, int top, int end, int bottom);
+ void dispatchStartTemporaryDetach(View view);
+ void dispatchFinishTemporaryDetach(View view);
+ float getX(View view);
+ float getY(View view);
+ float getRotation(View view);
+ float getRotationX(View view);
+ float getRotationY(View view);
+ float getScaleX(View view);
+ float getScaleY(View view);
+ float getTranslationX(View view);
+ float getTranslationY(View view);
+ int getMinimumWidth(View view);
+ int getMinimumHeight(View view);
+ ViewPropertyAnimatorCompat animate(View view);
+ void setRotation(View view, float value);
+ void setRotationX(View view, float value);
+ void setRotationY(View view, float value);
+ void setScaleX(View view, float value);
+ void setScaleY(View view, float value);
+ void setTranslationX(View view, float value);
+ void setTranslationY(View view, float value);
+ void setX(View view, float value);
+ void setY(View view, float value);
+ void setAlpha(View view, float value);
+ void setPivotX(View view, float value);
+ void setPivotY(View view, float value);
+ float getPivotX(View view);
+ float getPivotY(View view);
+ void setElevation(View view, float elevation);
+ float getElevation(View view);
+ void setTranslationZ(View view, float translationZ);
+ float getTranslationZ(View view);
+ void setClipBounds(View view, Rect clipBounds);
+ Rect getClipBounds(View view);
+ void setTransitionName(View view, String transitionName);
+ String getTransitionName(View view);
+ int getWindowSystemUiVisibility(View view);
+ void requestApplyInsets(View view);
+ void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled);
+ boolean getFitsSystemWindows(View view);
+ boolean hasOverlappingRendering(View view);
void setFitsSystemWindows(View view, boolean fitSystemWindows);
void jumpDrawablesToCurrentState(View v);
void setOnApplyWindowInsetsListener(View view, OnApplyWindowInsetsListener listener);
@@ -449,14 +465,18 @@
boolean dispatchNestedPreFling(View view, float velocityX, float velocityY);
boolean isLaidOut(View view);
int combineMeasuredStates(int curState, int newState);
- public float getZ(View view);
- public boolean isAttachedToWindow(View view);
- public boolean hasOnClickListeners(View view);
- public void setScrollIndicators(View view, int indicators);
- public void setScrollIndicators(View view, int indicators, int mask);
- public int getScrollIndicators(View view);
- public void offsetTopAndBottom(View view, int offset);
- public void offsetLeftAndRight(View view, int offset);
+ float getZ(View view);
+ boolean isAttachedToWindow(View view);
+ boolean hasOnClickListeners(View view);
+ void setScrollIndicators(View view, int indicators);
+ void setScrollIndicators(View view, int indicators, int mask);
+ int getScrollIndicators(View view);
+ void offsetTopAndBottom(View view, int offset);
+ void offsetLeftAndRight(View view, int offset);
+ void setPointerCapture(View view);
+ boolean hasPointerCapture(View view);
+ void releasePointerCapture(View view);
+ void setPointerIcon(View view, PointerIconCompat pointerIcon);
}
static class BaseViewCompatImpl implements ViewCompatImpl {
@@ -1066,6 +1086,26 @@
public void offsetTopAndBottom(View view, int offset) {
ViewCompatBase.offsetTopAndBottom(view, offset);
}
+
+ @Override
+ public void setPointerCapture(View view) {
+ // no-op
+ }
+
+ @Override
+ public boolean hasPointerCapture(View view) {
+ return false;
+ }
+
+ @Override
+ public void releasePointerCapture(View view) {
+ // no-op
+ }
+
+ @Override
+ public void setPointerIcon(View view, PointerIconCompat pointerIcon) {
+ // no-op
+ }
}
static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl {
@@ -1318,8 +1358,7 @@
@Override
public ViewPropertyAnimatorCompat animate(View view) {
if (mViewPropertyAnimatorCompatMap == null) {
- mViewPropertyAnimatorCompatMap =
- new WeakHashMap<View, ViewPropertyAnimatorCompat>();
+ mViewPropertyAnimatorCompatMap = new WeakHashMap<>();
}
ViewPropertyAnimatorCompat vpa = mViewPropertyAnimatorCompatMap.get(view);
if (vpa == null) {
@@ -1677,10 +1716,34 @@
}
}
+ static class Api24ViewCompatImpl extends MarshmallowViewCompatImpl {
+ @Override
+ public void setPointerCapture(View view) {
+ ViewCompatApi24.setPointerCapture(view);
+ }
+
+ @Override
+ public boolean hasPointerCapture(View view) {
+ return ViewCompatApi24.hasPointerCapture(view);
+ }
+
+ @Override
+ public void releasePointerCapture(View view) {
+ ViewCompatApi24.releasePointerCapture(view);
+ }
+
+ @Override
+ public void setPointerIcon(View view, PointerIconCompat pointerIconCompat) {
+ ViewCompatApi24.setPointerIcon(view, pointerIconCompat.getPointerIcon());
+ }
+ }
+
static final ViewCompatImpl IMPL;
static {
final int version = android.os.Build.VERSION.SDK_INT;
- if (version >= 23) {
+ if (BuildCompat.isAtLeastN()) {
+ IMPL = new Api24ViewCompatImpl();
+ } else if (version >= 23) {
IMPL = new MarshmallowViewCompatImpl();
} else if (version >= 21) {
IMPL = new LollipopViewCompatImpl();
@@ -1738,6 +1801,7 @@
*/
@OverScroll
public static int getOverScrollMode(View v) {
+ //noinspection ResourceType
return IMPL.getOverScrollMode(v);
}
@@ -1774,10 +1838,9 @@
* event.getText().add(selectedDateUtterance);
* }</pre>
* <p>
- * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
- * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
- * {@link android.view.View.AccessibilityDelegate#onPopulateAccessibilityEvent(View,
- * AccessibilityEvent)}
+ * If an {@link AccessibilityDelegateCompat} has been specified via calling
+ * {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)} its
+ * {@link AccessibilityDelegateCompat#onPopulateAccessibilityEvent(View, AccessibilityEvent)}
* is responsible for handling this call.
* </p>
* <p class="note"><strong>Note:</strong> Always call the super implementation before adding
@@ -1807,15 +1870,10 @@
* event.setPassword(true);
* }</pre>
* <p>
- * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
- * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
- * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityEvent(View,
- * AccessibilityEvent)}
+ * If an {@link AccessibilityDelegateCompat} has been specified via calling
+ * {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)}, its
+ * {@link AccessibilityDelegateCompat#onInitializeAccessibilityEvent(View, AccessibilityEvent)}
* is responsible for handling this call.
- * </p>
- * <p class="note"><strong>Note:</strong> Always call the super implementation before adding
- * information to the event, in case the default implementation has basic information to add.
- * </p>
*
* @param v The View against which to invoke the method.
* @param event The event to initialize.
@@ -1828,33 +1886,27 @@
}
/**
- * Initializes an {@link android.view.accessibility.AccessibilityNodeInfo} with information
+ * Initializes an {@link AccessibilityNodeInfoCompat} with information
* about this view. The base implementation sets:
* <ul>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setParent(View)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInParent(Rect)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInScreen(Rect)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setPackageName(CharSequence)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClassName(CharSequence)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setContentDescription(CharSequence)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setEnabled(boolean)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClickable(boolean)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocusable(boolean)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocused(boolean)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setLongClickable(boolean)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setSelected(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setParent(View)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setBoundsInParent(Rect)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setBoundsInScreen(Rect)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setPackageName(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setClassName(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setContentDescription(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setEnabled(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setClickable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setFocusable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setFocused(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setLongClickable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setSelected(boolean)},</li>
* </ul>
* <p>
- * Subclasses should override this method, call the super implementation,
- * and set additional attributes.
- * </p>
- * <p>
- * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
- * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
- * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View,
- * android.view.accessibility.AccessibilityNodeInfo)}
- * is responsible for handling this call.
- * </p>
+ * If an {@link AccessibilityDelegateCompat} has been specified via calling
+ * {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)}, its
+ * {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)}
+ * method is responsible for handling this call.
*
* @param v The View against which to invoke the method.
* @param info The instance to initialize.
@@ -1864,15 +1916,27 @@
}
/**
- * Sets a delegate for implementing accessibility support via compositon as
- * opposed to inheritance. The delegate's primary use is for implementing
- * backwards compatible widgets. For more details see
- * {@link android.view.View.AccessibilityDelegate}.
+ * Sets a delegate for implementing accessibility support via composition
+ * (as opposed to inheritance). For more details, see
+ * {@link AccessibilityDelegateCompat}.
+ * <p>
+ * On platform versions prior to API 14, this method is a no-op.
+ * <p>
+ * <strong>Note:</strong> On platform versions prior to
+ * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+ * views in the {@code android.widget.*} package are called <i>before</i>
+ * host methods. This prevents certain properties such as class name from
+ * being modified by overriding
+ * {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)},
+ * as any changes will be overwritten by the host class.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+ * methods are called <i>after</i> host methods, which all properties to be
+ * modified without being overwritten by the host class.
*
- * @param v The View against which to invoke the method.
- * @param delegate The delegate instance.
- *
- * @see android.view.View.AccessibilityDelegate
+ * @param delegate the object to which accessibility method calls should be
+ * delegated
+ * @see AccessibilityDelegateCompat
*/
public static void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) {
IMPL.setAccessibilityDelegate(v, delegate);
@@ -1988,6 +2052,7 @@
*/
@ImportantForAccessibility
public static int getImportantForAccessibility(View view) {
+ //noinspection ResourceType
return IMPL.getImportantForAccessibility(view);
}
@@ -2095,13 +2160,6 @@
* {@link #LAYER_TYPE_SOFTWARE software} and {@link #LAYER_TYPE_HARDWARE hardware}
* for more information on when and how to use layers.</p>
*
- * @param layerType The ype of layer to use with this view, must be one of
- * {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
- * {@link #LAYER_TYPE_HARDWARE}
- * @param paint The paint used to compose the layer. This argument is optional
- * and can be null. It is ignored when the layer type is
- * {@link #LAYER_TYPE_NONE}
- *
* @param view View to set the layer type for
* @param layerType The type of layer to use with this view, must be one of
* {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
@@ -2132,6 +2190,7 @@
*/
@LayerType
public static int getLayerType(View view) {
+ //noinspection ResourceType
return IMPL.getLayerType(view);
}
@@ -2203,6 +2262,7 @@
*/
@ResolvedLayoutDirectionMode
public static int getLayoutDirection(View view) {
+ //noinspection ResourceType
return IMPL.getLayoutDirection(view);
}
@@ -2329,6 +2389,7 @@
*/
@AccessibilityLiveRegion
public static int getAccessibilityLiveRegion(View view) {
+ //noinspection ResourceType
return IMPL.getAccessibilityLiveRegion(view);
}
@@ -3301,5 +3362,55 @@
return IMPL.getScrollIndicators(view);
}
- private ViewCompat() {}
+ /**
+ * Request capturing further mouse events.
+ *
+ * When the view captures, the mouse pointer icon will disappear and will not change its
+ * position. Further mouse events will come to the capturing view, and the mouse movements
+ * will can be detected through {@link MotionEventCompat#AXIS_RELATIVE_X} and
+ * {@link MotionEventCompat#AXIS_RELATIVE_Y}. Non-mouse events (touchscreen, or stylus) will
+ * not be affected.
+ *
+ * The capture will be released through {@link #releasePointerCapture(View)}, or will be lost
+ * automatically when the view or containing window disappear.
+ *
+ * @see #releasePointerCapture(View)
+ * @see #hasPointerCapture(View)
+ */
+ public static void setPointerCapture(@NonNull View view) {
+ IMPL.setPointerCapture(view);
+ }
+
+ /**
+ * Checks the capture status of mouse events.
+ *
+ * @return true if the view has the capture.
+ * @see #setPointerCapture(View)
+ * @see #hasPointerCapture(View)
+ */
+ public static boolean hasPointerCapture(@NonNull View view) {
+ return IMPL.hasPointerCapture(view);
+ }
+
+ /**
+ * Release the current capture of mouse events.
+ *
+ * If the view does not have the capture, it will do nothing.
+ * @see #setPointerCapture(View)
+ * @see #hasPointerCapture(View)
+ */
+ public static void releasePointerCapture(@NonNull View view) {
+ IMPL.releasePointerCapture(view);
+ }
+
+
+ /**
+ * Set the pointer icon for the current view.
+ * @param pointerIcon A PointerIconCompat instance which will be shown when the mouse hovers.
+ */
+ public static void setPointerIcon(@NonNull View view, PointerIconCompat pointerIcon) {
+ IMPL.setPointerIcon(view, pointerIcon);
+ }
+
+ protected ViewCompat() {}
}
diff --git a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
index 2752ebc..b894ad8 100644
--- a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.Nullable;
import android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat;
import android.support.v4.view.ViewCompat;
import android.text.InputType;
@@ -611,6 +612,8 @@
public boolean isMultiLine(Object info);
public void setMultiLine(Object info, boolean multiLine);
public boolean refresh(Object info);
+ public CharSequence getRoleDescription(Object info);
+ public void setRoleDescription(Object info, CharSequence roleDescription);
}
static class AccessibilityNodeInfoStubImpl implements AccessibilityNodeInfoImpl {
@@ -1215,6 +1218,15 @@
public boolean refresh(Object info) {
return false;
}
+
+ @Override
+ public CharSequence getRoleDescription(Object info) {
+ return null;
+ }
+
+ @Override
+ public void setRoleDescription(Object info, CharSequence roleDescription) {
+ }
}
static class AccessibilityNodeInfoIcsImpl extends AccessibilityNodeInfoStubImpl {
@@ -1753,6 +1765,16 @@
public void setMultiLine(Object info, boolean multiLine) {
AccessibilityNodeInfoCompatKitKat.setMultiLine(info, multiLine);
}
+
+ @Override
+ public CharSequence getRoleDescription(Object info) {
+ return AccessibilityNodeInfoCompatKitKat.getRoleDescription(info);
+ }
+
+ @Override
+ public void setRoleDescription(Object info, CharSequence roleDescription) {
+ AccessibilityNodeInfoCompatKitKat.setRoleDescription(info, roleDescription);
+ }
}
static class AccessibilityNodeInfoApi21Impl extends AccessibilityNodeInfoKitKatImpl {
@@ -3768,6 +3790,42 @@
return IMPL.refresh(mInfo);
}
+ /**
+ * Gets the custom role description.
+ * @return The role description.
+ */
+ public @Nullable CharSequence getRoleDescription() {
+ return IMPL.getRoleDescription(mInfo);
+ }
+
+ /**
+ * Sets the custom role description.
+ *
+ * <p>
+ * The role description allows you to customize the name for the view's semantic
+ * role. For example, if you create a custom subclass of {@link android.view.View}
+ * to display a menu bar, you could assign it the role description of "menu bar".
+ * </p>
+ * <p>
+ * <strong>Warning:</strong> For consistency with other applications, you should
+ * not use the role description to force accessibility services to describe
+ * standard views (such as buttons or checkboxes) using specific wording. For
+ * example, you should not set a role description of "check box" or "tick box" for
+ * a standard {@link android.widget.CheckBox}. Instead let accessibility services
+ * decide what feedback to provide.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param roleDescription The role description.
+ */
+ public void setRoleDescription(@Nullable CharSequence roleDescription) {
+ IMPL.setRoleDescription(mInfo, roleDescription);
+ }
+
@Override
public int hashCode() {
return (mInfo == null) ? 0 : mInfo.hashCode();
diff --git a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
index 64f6634..9666740 100644
--- a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
+++ b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
@@ -19,22 +19,29 @@
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.util.SparseArrayCompat;
import android.support.v4.view.AccessibilityDelegateCompat;
+import android.support.v4.view.KeyEventCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewCompat.FocusDirection;
+import android.support.v4.view.ViewCompat.FocusRealDirection;
import android.support.v4.view.ViewParentCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityManagerCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -47,8 +54,36 @@
* <p>
* Clients should override abstract methods on this class and attach it to the
* host view using {@link ViewCompat#setAccessibilityDelegate}:
- *
+ * <p>
* <pre>
+ * class MyCustomView extends View {
+ * private MyVirtualViewHelper mVirtualViewHelper;
+ *
+ * public MyCustomView(Context context, ...) {
+ * ...
+ * mVirtualViewHelper = new MyVirtualViewHelper(this);
+ * ViewCompat.setAccessibilityDelegate(this, mVirtualViewHelper);
+ * }
+ *
+ * @Override
+ * public boolean dispatchHoverEvent(MotionEvent event) {
+ * return mHelper.dispatchHoverEvent(this, event)
+ * || super.dispatchHoverEvent(event);
+ * }
+ *
+ * @Override
+ * public boolean dispatchKeyEvent(KeyEvent event) {
+ * return mHelper.dispatchKeyEvent(event)
+ * || super.dispatchKeyEvent(event);
+ * }
+ *
+ * @Override
+ * public boolean onFocusChanged(boolean gainFocus, int direction,
+ * Rect previouslyFocusedRect) {
+ * super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ * mHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ * }
+ * }
* mAccessHelper = new MyExploreByTouchHelper(someView);
* ViewCompat.setAccessibilityDelegate(someView, mAccessHelper);
* </pre>
@@ -61,7 +96,7 @@
public static final int HOST_ID = View.NO_ID;
/** Default class name used for virtual views. */
- private static final String DEFAULT_CLASS_NAME = View.class.getName();
+ private static final String DEFAULT_CLASS_NAME = "android.view.View";
// Temporary, reusable data structures.
private final Rect mTempScreenRect = new Rect();
@@ -69,70 +104,81 @@
private final Rect mTempVisibleRect = new Rect();
private final int[] mTempGlobalRect = new int[2];
+ /** Cache of accessibility nodes. This is populated on-demand. */
+ private final SparseArrayCompat<AccessibilityNodeInfoCompat> mCachedNodes =
+ new SparseArrayCompat<>();
+
/** System accessibility manager, used to check state and send events. */
private final AccessibilityManager mManager;
/** View whose internal structure is exposed through this helper. */
- private final View mView;
+ private final View mHost;
- /** Node provider that handles creating nodes and performing actions. */
- private ExploreByTouchNodeProvider mNodeProvider;
+ /** Virtual node provider used to expose logical structure to services. */
+ private MyNodeProvider mNodeProvider;
- /** Virtual view id for the currently focused logical item. */
- private int mFocusedVirtualViewId = INVALID_ID;
+ /** Identifier for the virtual view that holds accessibility focus. */
+ private int mAccessibilityFocusedVirtualViewId = INVALID_ID;
- /** Virtual view id for the currently hovered logical item. */
+ /** Identifier for the virtual view that holds keyboard focus. */
+ private int mKeyboardFocusedVirtualViewId = INVALID_ID;
+
+ /** Identifier for the virtual view that is currently hovered. */
private int mHoveredVirtualViewId = INVALID_ID;
/**
- * Factory method to create a new {@link ExploreByTouchHelper}.
+ * Constructs a new helper that can expose a virtual view hierarchy for the
+ * specified host view.
*
- * @param forView View whose logical children are exposed by this helper.
+ * @param host view whose virtual view hierarchy is exposed by this helper
*/
- public ExploreByTouchHelper(View forView) {
- if (forView == null) {
+ public ExploreByTouchHelper(View host) {
+ if (host == null) {
throw new IllegalArgumentException("View may not be null");
}
- mView = forView;
- final Context context = forView.getContext();
+ mHost = host;
+
+ final Context context = host.getContext();
mManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+ // Host view must be focusable so that we can delegate to virtual
+ // views.
+ host.setFocusable(true);
+ if (ViewCompat.getImportantForAccessibility(host)
+ == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ ViewCompat.setImportantForAccessibility(
+ host, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
- /**
- * Returns the {@link AccessibilityNodeProviderCompat} for this helper.
- *
- * @param host View whose logical children are exposed by this helper.
- * @return The accessibility node provider for this helper.
- */
@Override
public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
if (mNodeProvider == null) {
- mNodeProvider = new ExploreByTouchNodeProvider();
+ mNodeProvider = new MyNodeProvider();
}
return mNodeProvider;
}
/**
+ * Delegates hover events from the host view.
+ * <p>
* Dispatches hover {@link MotionEvent}s to the virtual view hierarchy when
* the Explore by Touch feature is enabled.
* <p>
- * This method should be called by overriding
- * {@link View#dispatchHoverEvent}:
- *
+ * This method should be called by overriding the host view's
+ * {@link View#dispatchHoverEvent(MotionEvent)} method:
* <pre>@Override
* public boolean dispatchHoverEvent(MotionEvent event) {
- * if (mHelper.dispatchHoverEvent(this, event) {
- * return true;
- * }
- * return super.dispatchHoverEvent(event);
+ * return mHelper.dispatchHoverEvent(this, event)
+ * || super.dispatchHoverEvent(event);
* }
* </pre>
*
* @param event The hover event to dispatch to the virtual view hierarchy.
* @return Whether the hover event was handled.
*/
- public boolean dispatchHoverEvent(MotionEvent event) {
+ public final boolean dispatchHoverEvent(@NonNull MotionEvent event) {
if (!mManager.isEnabled()
|| !AccessibilityManagerCompat.isTouchExplorationEnabled(mManager)) {
return false;
@@ -145,7 +191,7 @@
updateHoveredVirtualView(virtualViewId);
return (virtualViewId != INVALID_ID);
case MotionEventCompat.ACTION_HOVER_EXIT:
- if (mFocusedVirtualViewId != INVALID_ID) {
+ if (mAccessibilityFocusedVirtualViewId != INVALID_ID) {
updateHoveredVirtualView(INVALID_ID);
return true;
}
@@ -156,78 +202,412 @@
}
/**
+ * Delegates key events from the host view.
+ * <p>
+ * This method should be called by overriding the host view's
+ * {@link View#dispatchKeyEvent(KeyEvent)} method:
+ * <pre>@Override
+ * public boolean dispatchKeyEvent(KeyEvent event) {
+ * return mHelper.dispatchKeyEvent(event)
+ * || super.dispatchKeyEvent(event);
+ * }
+ * </pre>
+ */
+ public final boolean dispatchKeyEvent(@NonNull KeyEvent event) {
+ boolean handled = false;
+
+ final int action = event.getAction();
+ if (action != KeyEvent.ACTION_UP) {
+ final int keyCode = event.getKeyCode();
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (KeyEventCompat.hasNoModifiers(event)) {
+ final int direction = keyToDirection(keyCode);
+ final int count = 1 + event.getRepeatCount();
+ for (int i = 0; i < count; i++) {
+ if (moveFocus(direction, null)) {
+ handled = true;
+ } else {
+ break;
+ }
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ if (KeyEventCompat.hasNoModifiers(event)) {
+ if (event.getRepeatCount() == 0) {
+ clickKeyboardFocusedVirtualView();
+ handled = true;
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_TAB:
+ if (KeyEventCompat.hasNoModifiers(event)) {
+ handled = moveFocus(View.FOCUS_FORWARD, null);
+ } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
+ handled = moveFocus(View.FOCUS_BACKWARD, null);
+ }
+ break;
+ }
+ }
+
+ return handled;
+ }
+
+ /**
+ * Delegates focus changes from the host view.
+ * <p>
+ * This method should be called by overriding the host view's
+ * {@link View#onFocusChanged(boolean, int, Rect)} method:
+ * <pre>@Override
+ * public boolean onFocusChanged(boolean gainFocus, int direction,
+ * Rect previouslyFocusedRect) {
+ * super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ * mHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ * }
+ * </pre>
+ */
+ public final void onFocusChanged(boolean gainFocus, int direction,
+ @Nullable Rect previouslyFocusedRect) {
+ if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
+ clearKeyboardFocusForVirtualView(mKeyboardFocusedVirtualViewId);
+ }
+
+ if (gainFocus) {
+ moveFocus(direction, previouslyFocusedRect);
+ }
+ }
+
+ /**
+ * @return the identifier of the virtual view that has accessibility focus
+ * or {@link #INVALID_ID} if no virtual view has accessibility
+ * focus
+ */
+ public final int getAccessibilityFocusedVirtualViewId() {
+ return mAccessibilityFocusedVirtualViewId;
+ }
+
+ /**
+ * @return the identifier of the virtual view that has keyboard focus
+ * or {@link #INVALID_ID} if no virtual view has keyboard focus
+ */
+ public final int getKeyboardFocusedVirtualViewId() {
+ return mKeyboardFocusedVirtualViewId;
+ }
+
+ /**
+ * Maps key event codes to focus directions.
+ *
+ * @param keyCode the key event code
+ * @return the corresponding focus direction
+ */
+ @FocusRealDirection
+ private static int keyToDirection(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ return View.FOCUS_LEFT;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ return View.FOCUS_UP;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ return View.FOCUS_RIGHT;
+ default:
+ return View.FOCUS_DOWN;
+ }
+ }
+
+ /**
+ * Obtains the bounds for the specified virtual view.
+ *
+ * @param virtualViewId the identifier of the virtual view
+ * @param outBounds the rect to populate with virtual view bounds
+ */
+ private void getBoundsInParent(int virtualViewId, Rect outBounds) {
+ final AccessibilityNodeInfoCompat node = obtainAccessibilityNodeInfo(virtualViewId);
+ node.getBoundsInParent(outBounds);
+ }
+
+ /**
+ * Adapts AccessibilityNodeInfoCompat for obtaining bounds.
+ */
+ private static final FocusStrategy.BoundsAdapter<AccessibilityNodeInfoCompat> NODE_ADAPTER =
+ new FocusStrategy.BoundsAdapter<AccessibilityNodeInfoCompat>() {
+ @Override
+ public void obtainBounds(AccessibilityNodeInfoCompat node, Rect outBounds) {
+ node.getBoundsInParent(outBounds);
+ }
+ };
+
+ /**
+ * Adapts SparseArrayCompat for iterating through values.
+ */
+ private static final FocusStrategy.CollectionAdapter<SparseArrayCompat<
+ AccessibilityNodeInfoCompat>, AccessibilityNodeInfoCompat> SPARSE_VALUES_ADAPTER =
+ new FocusStrategy.CollectionAdapter<SparseArrayCompat<
+ AccessibilityNodeInfoCompat>, AccessibilityNodeInfoCompat>() {
+ @Override
+ public AccessibilityNodeInfoCompat get(
+ SparseArrayCompat<AccessibilityNodeInfoCompat> collection, int index) {
+ return collection.valueAt(index);
+ }
+
+ @Override
+ public int size(SparseArrayCompat<AccessibilityNodeInfoCompat> collection) {
+ return collection.size();
+ }
+ };
+
+ /**
+ * Attempts to move keyboard focus in the specified direction.
+ *
+ * @param direction the direction in which to move keyboard focus
+ * @param previouslyFocusedRect the bounds of the previously focused item,
+ * or {@code null} if not available
+ * @return {@code true} if keyboard focus moved to a virtual view managed
+ * by this helper, or {@code false} otherwise
+ */
+ private boolean moveFocus(@FocusDirection int direction, @Nullable Rect previouslyFocusedRect) {
+ final int focusedNodeId = mKeyboardFocusedVirtualViewId;
+ final AccessibilityNodeInfoCompat focusedNode =
+ focusedNodeId == INVALID_ID ? null : mCachedNodes.get(focusedNodeId);
+
+ final AccessibilityNodeInfoCompat nextFocusedNode;
+ switch (direction) {
+ case View.FOCUS_FORWARD:
+ case View.FOCUS_BACKWARD:
+ final boolean isLayoutRtl =
+ ViewCompat.getLayoutDirection(mHost) == ViewCompat.LAYOUT_DIRECTION_RTL;
+ nextFocusedNode = FocusStrategy.findNextFocusInRelativeDirection(mCachedNodes,
+ SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, direction, isLayoutRtl,
+ false);
+ break;
+ case View.FOCUS_LEFT:
+ case View.FOCUS_UP:
+ case View.FOCUS_RIGHT:
+ case View.FOCUS_DOWN:
+ final Rect selectedRect = new Rect();
+ if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
+ // Focus is moving from a virtual view within the host.
+ getBoundsInParent(mKeyboardFocusedVirtualViewId, selectedRect);
+ } else if (previouslyFocusedRect != null) {
+ // Focus is moving from a real view outside the host.
+ selectedRect.set(previouslyFocusedRect);
+ } else {
+ // Focus is moving from... somewhere? Make a guess.
+ // Usually this happens when another view was too lazy
+ // to pass the previously focused rect (ex. ScrollView
+ // when moving UP or DOWN).
+ guessPreviouslyFocusedRect(mHost, direction, selectedRect);
+ }
+ nextFocusedNode = FocusStrategy.findNextFocusInAbsoluteDirection(mCachedNodes,
+ SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, selectedRect, direction);
+ break;
+ default:
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_FORWARD, FOCUS_BACKWARD, FOCUS_UP, FOCUS_DOWN, "
+ + "FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ final int nextFocusedNodeId;
+ if (nextFocusedNode == null) {
+ nextFocusedNodeId = INVALID_ID;
+ } else {
+ final int index = mCachedNodes.indexOfValue(nextFocusedNode);
+ nextFocusedNodeId = mCachedNodes.keyAt(index);
+ }
+
+ return requestKeyboardFocusForVirtualView(nextFocusedNodeId);
+ }
+
+ /**
+ * Obtains a best guess for the previously focused rect for keyboard focus
+ * moving in the specified direction.
+ *
+ * @param host the view into which focus is moving
+ * @param direction the absolute direction in which focus is moving
+ * @param outBounds the rect to populate with the best-guess bounds for the
+ * previous focus rect
+ */
+ private static Rect guessPreviouslyFocusedRect(@NonNull View host,
+ @FocusRealDirection int direction, @NonNull Rect outBounds) {
+ final int w = host.getWidth();
+ final int h = host.getHeight();
+
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ outBounds.set(w, 0, w, h);
+ break;
+ case View.FOCUS_UP:
+ outBounds.set(0, h, w, h);
+ break;
+ case View.FOCUS_RIGHT:
+ outBounds.set(-1, 0, -1, h);
+ break;
+ case View.FOCUS_DOWN:
+ outBounds.set(0, -1, w, -1);
+ break;
+ default:
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ return outBounds;
+ }
+
+ /**
+ * Performs a click action on the keyboard focused virtual view, if any.
+ *
+ * @return {@code true} if the click action was performed successfully or
+ * {@code false} otherwise
+ */
+ private boolean clickKeyboardFocusedVirtualView() {
+ return mKeyboardFocusedVirtualViewId != INVALID_ID && onPerformActionForVirtualView(
+ mKeyboardFocusedVirtualViewId, AccessibilityNodeInfoCompat.ACTION_CLICK, null);
+ }
+
+ /**
* Populates an event of the specified type with information about an item
* and attempts to send it up through the view hierarchy.
* <p>
* You should call this method after performing a user action that normally
* fires an accessibility event, such as clicking on an item.
- *
+ * <p>
* <pre>public void performItemClick(T item) {
* ...
* sendEventForVirtualViewId(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED);
* }
* </pre>
*
- * @param virtualViewId The virtual view id for which to send an event.
- * @param eventType The type of event to send.
- * @return true if the event was sent successfully.
+ * @param virtualViewId the identifier of the virtual view for which to
+ * send an event
+ * @param eventType the type of event to send
+ * @return {@code true} if the event was sent successfully, {@code false}
+ * otherwise
*/
- public boolean sendEventForVirtualView(int virtualViewId, int eventType) {
+ public final boolean sendEventForVirtualView(int virtualViewId, int eventType) {
if ((virtualViewId == INVALID_ID) || !mManager.isEnabled()) {
return false;
}
- final ViewParent parent = mView.getParent();
+ final ViewParent parent = mHost.getParent();
if (parent == null) {
return false;
}
final AccessibilityEvent event = createEvent(virtualViewId, eventType);
- return ViewParentCompat.requestSendAccessibilityEvent(parent, mView, event);
+ return ViewParentCompat.requestSendAccessibilityEvent(parent, mHost, event);
}
/**
* Notifies the accessibility framework that the properties of the parent
* view have changed.
* <p>
- * You <b>must</b> call this method after adding or removing items from the
- * parent view.
+ * You <strong>must</strong> call this method after adding or removing
+ * items from the parent view.
*/
- public void invalidateRoot() {
- invalidateVirtualView(HOST_ID);
+ public final void invalidateRoot() {
+ invalidateVirtualView(HOST_ID, AccessibilityEventCompat.CONTENT_CHANGE_TYPE_SUBTREE);
}
/**
* Notifies the accessibility framework that the properties of a particular
* item have changed.
* <p>
- * You <b>must</b> call this method after changing any of the properties set
- * in {@link #onPopulateNodeForVirtualView}.
+ * You <strong>must</strong> call this method after changing any of the
+ * properties set in
+ * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
*
- * @param virtualViewId The virtual view id to invalidate.
+ * @param virtualViewId the virtual view id to invalidate, or
+ * {@link #HOST_ID} to invalidate the root view
+ * @see #invalidateVirtualView(int, int)
*/
- public void invalidateVirtualView(int virtualViewId) {
- sendEventForVirtualView(
- virtualViewId, AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
+ public final void invalidateVirtualView(int virtualViewId) {
+ invalidateVirtualView(virtualViewId,
+ AccessibilityEventCompat.CONTENT_CHANGE_TYPE_UNDEFINED);
}
/**
- * Returns the virtual view id for the currently focused item,
+ * Notifies the accessibility framework that the properties of a particular
+ * item have changed.
+ * <p>
+ * You <strong>must</strong> call this method after changing any of the
+ * properties set in
+ * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
*
- * @return A virtual view id, or {@link #INVALID_ID} if no item is
- * currently focused.
+ * @param virtualViewId the virtual view id to invalidate, or
+ * {@link #HOST_ID} to invalidate the root view
+ * @param changeTypes the bit mask of change types. May be {@code 0} for the
+ * default (undefined) change type or one or more of:
+ * <ul>
+ * <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+ * <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_SUBTREE}
+ * <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_TEXT}
+ * <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_UNDEFINED}
+ * </ul>
*/
+ public final void invalidateVirtualView(int virtualViewId, int changeTypes) {
+ final SparseArrayCompat<AccessibilityNodeInfoCompat> cachedNodes = mCachedNodes;
+ if (virtualViewId == HOST_ID
+ && (changeTypes & AccessibilityEventCompat.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
+ for (int i = 0, count = cachedNodes.size(); i < count; i++) {
+ cachedNodes.valueAt(i).recycle();
+ }
+ cachedNodes.clear();
+ } else {
+ final int index = cachedNodes.indexOfKey(virtualViewId);
+ if (index >= 0) {
+ cachedNodes.valueAt(index).recycle();
+ cachedNodes.removeAt(index);
+ }
+ }
+
+ if (virtualViewId != INVALID_ID && mManager.isEnabled()) {
+ final ViewParent parent = mHost.getParent();
+ if (parent != null) {
+ // Send events up the hierarchy so they can be coalesced.
+ final AccessibilityEvent event = createEvent(virtualViewId,
+ AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
+ AccessibilityEventCompat.setContentChangeTypes(event, changeTypes);
+ ViewParentCompat.requestSendAccessibilityEvent(parent, mHost, event);
+ }
+ }
+ }
+
+ /**
+ * Returns the virtual view ID for the currently accessibility focused
+ * item.
+ *
+ * @return the identifier of the virtual view that has accessibility focus
+ * or {@link #INVALID_ID} if no virtual view has accessibility
+ * focus
+ * @deprecated Use {@link #getAccessibilityFocusedVirtualViewId()}.
+ */
+ @Deprecated
public int getFocusedVirtualView() {
- return mFocusedVirtualViewId;
+ return getAccessibilityFocusedVirtualViewId();
+ }
+
+ /**
+ * Called when the focus state of a virtual view changes.
+ *
+ * @param virtualViewId the virtual view identifier
+ * @param hasFocus {@code true} if the view has focus, {@code false}
+ * otherwise
+ */
+ protected void onVirtualViewKeyboardFocusChanged(int virtualViewId, boolean hasFocus) {
+ // Stub method.
}
/**
* Sets the currently hovered item, sending hover accessibility events as
* necessary to maintain the correct state.
*
- * @param virtualViewId The virtual view id for the item currently being
- * hovered, or {@link #INVALID_ID} if no item is hovered within
- * the parent view.
+ * @param virtualViewId the virtual view id for the item currently being
+ * hovered, or {@link #INVALID_ID} if no item is
+ * hovered within the parent view
*/
private void updateHoveredVirtualView(int virtualViewId) {
if (mHoveredVirtualViewId == virtualViewId) {
@@ -248,11 +628,11 @@
* Constructs and returns an {@link AccessibilityEvent} for the specified
* virtual view id, which includes the host view ({@link #HOST_ID}).
*
- * @param virtualViewId The virtual view id for the item for which to
- * construct an event.
- * @param eventType The type of event to construct.
- * @return An {@link AccessibilityEvent} populated with information about
- * the specified item.
+ * @param virtualViewId the virtual view id for the item for which to
+ * construct an event
+ * @param eventType the type of event to construct
+ * @return an {@link AccessibilityEvent} populated with information about
+ * the specified item
*/
private AccessibilityEvent createEvent(int virtualViewId, int eventType) {
switch (virtualViewId) {
@@ -266,30 +646,46 @@
/**
* Constructs and returns an {@link AccessibilityEvent} for the host node.
*
- * @param eventType The type of event to construct.
- * @return An {@link AccessibilityEvent} populated with information about
- * the specified item.
+ * @param eventType the type of event to construct
+ * @return an {@link AccessibilityEvent} populated with information about
+ * the specified item
*/
private AccessibilityEvent createEventForHost(int eventType) {
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- ViewCompat.onInitializeAccessibilityEvent(mView, event);
+ ViewCompat.onInitializeAccessibilityEvent(mHost, event);
return event;
}
+ @Override
+ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(host, event);
+
+ // Allow the client to populate the event.
+ onPopulateEventForHost(event);
+ }
+
/**
* Constructs and returns an {@link AccessibilityEvent} populated with
* information about the specified item.
*
- * @param virtualViewId The virtual view id for the item for which to
- * construct an event.
- * @param eventType The type of event to construct.
- * @return An {@link AccessibilityEvent} populated with information about
- * the specified item.
+ * @param virtualViewId the virtual view id for the item for which to
+ * construct an event
+ * @param eventType the type of event to construct
+ * @return an {@link AccessibilityEvent} populated with information about
+ * the specified item
*/
private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- event.setEnabled(true);
- event.setClassName(DEFAULT_CLASS_NAME);
+ final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
+ final AccessibilityNodeInfoCompat node = obtainAccessibilityNodeInfo(virtualViewId);
+
+ // Allow the client to override these properties,
+ record.getText().add(node.getText());
+ record.setContentDescription(node.getContentDescription());
+ record.setScrollable(node.isScrollable());
+ record.setPassword(node.isPassword());
+ record.setEnabled(node.isEnabled());
+ record.setChecked(node.isChecked());
// Allow the client to populate the event.
onPopulateEventForVirtualView(virtualViewId, event);
@@ -301,55 +697,75 @@
}
// Don't allow the client to override these properties.
- event.setPackageName(mView.getContext().getPackageName());
-
- final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
- record.setSource(mView, virtualViewId);
+ record.setClassName(node.getClassName());
+ record.setSource(mHost, virtualViewId);
+ event.setPackageName(mHost.getContext().getPackageName());
return event;
}
/**
- * Constructs and returns an {@link AccessibilityNodeInfoCompat} for the
- * specified virtual view id, which includes the host view
- * ({@link #HOST_ID}).
+ * Obtains a populated {@link AccessibilityNodeInfoCompat} for the
+ * virtual view with the specified identifier.
+ * <p>
+ * This method may be called with identifier {@link #HOST_ID} to obtain a
+ * node for the host view.
*
- * @param virtualViewId The virtual view id for the item for which to
- * construct a node.
- * @return An {@link AccessibilityNodeInfoCompat} populated with information
- * about the specified item.
+ * @param virtualViewId the identifier of the virtual view for which to
+ * construct a node
+ * @return an {@link AccessibilityNodeInfoCompat} populated with information
+ * about the specified item
*/
- private AccessibilityNodeInfoCompat createNode(int virtualViewId) {
- switch (virtualViewId) {
- case HOST_ID:
- return createNodeForHost();
- default:
- return createNodeForChild(virtualViewId);
+ @NonNull
+ private AccessibilityNodeInfoCompat obtainAccessibilityNodeInfo(int virtualViewId) {
+ final AccessibilityNodeInfoCompat node;
+ final int cacheIndex = mCachedNodes.indexOfKey(virtualViewId);
+ if (cacheIndex >= 0) {
+ node = mCachedNodes.valueAt(cacheIndex);
+ } else if (virtualViewId == HOST_ID) {
+ node = createNodeForHost();
+ } else {
+ node = createNodeForChild(virtualViewId);
}
+
+ mCachedNodes.put(virtualViewId, node);
+
+ return node;
}
/**
* Constructs and returns an {@link AccessibilityNodeInfoCompat} for the
* host view populated with its virtual descendants.
*
- * @return An {@link AccessibilityNodeInfoCompat} for the parent node.
+ * @return an {@link AccessibilityNodeInfoCompat} for the parent node
*/
+ @NonNull
private AccessibilityNodeInfoCompat createNodeForHost() {
- final AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain(mView);
- ViewCompat.onInitializeAccessibilityNodeInfo(mView, node);
-
- // Allow the client to populate the host node.
- onPopulateNodeForHost(node);
+ final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(mHost);
+ ViewCompat.onInitializeAccessibilityNodeInfo(mHost, info);
// Add the virtual descendants.
- final LinkedList<Integer> virtualViewIds = new LinkedList<Integer>();
+ final ArrayList<Integer> virtualViewIds = new ArrayList<>();
getVisibleVirtualViews(virtualViewIds);
- for (Integer childVirtualViewId : virtualViewIds) {
- node.addChild(mView, childVirtualViewId);
+ final int realNodeCount = info.getChildCount();
+ if (realNodeCount > 0 && virtualViewIds.size() > 0) {
+ throw new RuntimeException("Views cannot have both real and virtual children");
}
- return node;
+ for (int i = 0, count = virtualViewIds.size(); i < count; i++) {
+ info.addChild(mHost, virtualViewIds.get(i));
+ }
+
+ return info;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ // Allow the client to populate the host node.
+ onPopulateNodeForHost(info);
}
/**
@@ -376,15 +792,17 @@
* <li>{@link AccessibilityNodeInfoCompat#setBoundsInParent}
* </ul>
*
- * @param virtualViewId The virtual view id for item for which to construct
- * a node.
- * @return An {@link AccessibilityNodeInfoCompat} for the specified item.
+ * @param virtualViewId the virtual view id for item for which to construct
+ * a node
+ * @return an {@link AccessibilityNodeInfoCompat} for the specified item
*/
+ @NonNull
private AccessibilityNodeInfoCompat createNodeForChild(int virtualViewId) {
final AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain();
// Ensure the client has good defaults.
node.setEnabled(true);
+ node.setFocusable(true);
node.setClassName(DEFAULT_CLASS_NAME);
// Allow the client to populate the node.
@@ -413,12 +831,12 @@
}
// Don't allow the client to override these properties.
- node.setPackageName(mView.getContext().getPackageName());
- node.setSource(mView, virtualViewId);
- node.setParent(mView);
+ node.setPackageName(mHost.getContext().getPackageName());
+ node.setSource(mHost, virtualViewId);
+ node.setParent(mHost);
// Manage internal accessibility focus state.
- if (mFocusedVirtualViewId == virtualViewId) {
+ if (mAccessibilityFocusedVirtualViewId == virtualViewId) {
node.setAccessibilityFocused(true);
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
} else {
@@ -426,6 +844,15 @@
node.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
}
+ // Manage internal keyboard focus state.
+ final boolean isFocused = mKeyboardFocusedVirtualViewId == virtualViewId;
+ if (isFocused) {
+ node.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS);
+ } else if (node.isFocusable()) {
+ node.addAction(AccessibilityNodeInfoCompat.ACTION_FOCUS);
+ }
+ node.setFocused(isFocused);
+
// Set the visibility based on the parent bound.
if (intersectVisibleToUser(mTempParentRect)) {
node.setVisibleToUser(true);
@@ -433,7 +860,7 @@
}
// Calculate screen-relative bound.
- mView.getLocationOnScreen(mTempGlobalRect);
+ mHost.getLocationOnScreen(mTempGlobalRect);
final int offsetX = mTempGlobalRect[0];
final int offsetY = mTempGlobalRect[1];
mTempScreenRect.set(mTempParentRect);
@@ -453,27 +880,21 @@
}
private boolean performActionForHost(int action, Bundle arguments) {
- return ViewCompat.performAccessibilityAction(mView, action, arguments);
+ return ViewCompat.performAccessibilityAction(mHost, action, arguments);
}
private boolean performActionForChild(int virtualViewId, int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
- case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
- return manageFocusForChild(virtualViewId, action, arguments);
- default:
- return onPerformActionForVirtualView(virtualViewId, action, arguments);
- }
- }
-
- private boolean manageFocusForChild(int virtualViewId, int action, Bundle arguments) {
- switch (action) {
- case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
return requestAccessibilityFocus(virtualViewId);
case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
return clearAccessibilityFocus(virtualViewId);
+ case AccessibilityNodeInfoCompat.ACTION_FOCUS:
+ return requestKeyboardFocusForVirtualView(virtualViewId);
+ case AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS:
+ return clearKeyboardFocusForVirtualView(virtualViewId);
default:
- return false;
+ return onPerformActionForVirtualView(virtualViewId, action, arguments);
}
}
@@ -482,8 +903,8 @@
* portion of its parent {@link View}. Modifies {@code localRect} to contain
* only the visible portion.
*
- * @param localRect A rectangle in local (parent) coordinates.
- * @return Whether the specified {@link Rect} is visible on the screen.
+ * @param localRect a rectangle in local (parent) coordinates
+ * @return whether the specified {@link Rect} is visible on the screen
*/
private boolean intersectVisibleToUser(Rect localRect) {
// Missing or empty bounds mean this view is not visible.
@@ -492,12 +913,12 @@
}
// Attached to invisible window means this view is not visible.
- if (mView.getWindowVisibility() != View.VISIBLE) {
+ if (mHost.getWindowVisibility() != View.VISIBLE) {
return false;
}
// An invisible predecessor means that this view is not visible.
- ViewParent viewParent = mView.getParent();
+ ViewParent viewParent = mHost.getParent();
while (viewParent instanceof View) {
final View view = (View) viewParent;
if ((ViewCompat.getAlpha(view) <= 0) || (view.getVisibility() != View.VISIBLE)) {
@@ -512,7 +933,7 @@
}
// If no portion of the parent is visible, this view is not visible.
- if (!mView.getLocalVisibleRect(mTempVisibleRect)) {
+ if (!mHost.getLocalVisibleRect(mTempVisibleRect)) {
return false;
}
@@ -521,15 +942,6 @@
}
/**
- * Returns whether this virtual view is accessibility focused.
- *
- * @return True if the view is accessibility focused.
- */
- private boolean isAccessibilityFocused(int virtualViewId) {
- return (mFocusedVirtualViewId == virtualViewId);
- }
-
- /**
* Attempts to give accessibility focus to a virtual view.
* <p>
* A virtual view will not actually take focus if
@@ -537,9 +949,9 @@
* {@link AccessibilityManager#isTouchExplorationEnabled()} returns false,
* or the view already has accessibility focus.
*
- * @param virtualViewId The id of the virtual view on which to place
- * accessibility focus.
- * @return Whether this virtual view actually took accessibility focus.
+ * @param virtualViewId the identifier of the virtual view on which to
+ * place accessibility focus
+ * @return whether this virtual view actually took accessibility focus
*/
private boolean requestAccessibilityFocus(int virtualViewId) {
if (!mManager.isEnabled()
@@ -547,18 +959,17 @@
return false;
}
// TODO: Check virtual view visibility.
- if (!isAccessibilityFocused(virtualViewId)) {
+ if (mAccessibilityFocusedVirtualViewId != virtualViewId) {
// Clear focus from the previously focused view, if applicable.
- if (mFocusedVirtualViewId != INVALID_ID) {
- sendEventForVirtualView(mFocusedVirtualViewId,
- AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+ if (mAccessibilityFocusedVirtualViewId != INVALID_ID) {
+ clearAccessibilityFocus(mAccessibilityFocusedVirtualViewId);
}
// Set focus on the new view.
- mFocusedVirtualViewId = virtualViewId;
+ mAccessibilityFocusedVirtualViewId = virtualViewId;
// TODO: Only invalidate virtual view bounds.
- mView.invalidate();
+ mHost.invalidate();
sendEventForVirtualView(virtualViewId,
AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
return true;
@@ -569,14 +980,14 @@
/**
* Attempts to clear accessibility focus from a virtual view.
*
- * @param virtualViewId The id of the virtual view from which to clear
- * accessibility focus.
- * @return Whether this virtual view actually cleared accessibility focus.
+ * @param virtualViewId the identifier of the virtual view from which to
+ * clear accessibility focus
+ * @return whether this virtual view actually cleared accessibility focus
*/
private boolean clearAccessibilityFocus(int virtualViewId) {
- if (isAccessibilityFocused(virtualViewId)) {
- mFocusedVirtualViewId = INVALID_ID;
- mView.invalidate();
+ if (mAccessibilityFocusedVirtualViewId == virtualViewId) {
+ mAccessibilityFocusedVirtualViewId = INVALID_ID;
+ mHost.invalidate();
sendEventForVirtualView(virtualViewId,
AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
return true;
@@ -585,6 +996,57 @@
}
/**
+ * Attempts to give keyboard focus to a virtual view.
+ *
+ * @param virtualViewId the identifier of the virtual view on which to
+ * place keyboard focus
+ * @return whether this virtual view actually took keyboard focus
+ */
+ public final boolean requestKeyboardFocusForVirtualView(int virtualViewId) {
+ if (!mHost.isFocused() && !mHost.requestFocus()) {
+ // Host must have real keyboard focus.
+ return false;
+ }
+
+ if (mKeyboardFocusedVirtualViewId == virtualViewId) {
+ // The virtual view already has focus.
+ return false;
+ }
+
+ if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
+ clearKeyboardFocusForVirtualView(mKeyboardFocusedVirtualViewId);
+ }
+
+ mKeyboardFocusedVirtualViewId = virtualViewId;
+
+ onVirtualViewKeyboardFocusChanged(virtualViewId, true);
+ sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_FOCUSED);
+
+ return true;
+ }
+
+ /**
+ * Attempts to clear keyboard focus from a virtual view.
+ *
+ * @param virtualViewId the identifier of the virtual view from which to
+ * clear keyboard focus
+ * @return whether this virtual view actually cleared keyboard focus
+ */
+ public final boolean clearKeyboardFocusForVirtualView(int virtualViewId) {
+ if (mKeyboardFocusedVirtualViewId != virtualViewId) {
+ // The virtual view is not focused.
+ return false;
+ }
+
+ mKeyboardFocusedVirtualViewId = INVALID_ID;
+
+ onVirtualViewKeyboardFocusChanged(virtualViewId, false);
+ sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_FOCUSED);
+
+ return true;
+ }
+
+ /**
* Provides a mapping between view-relative coordinates and logical
* items.
*
@@ -609,22 +1071,25 @@
* Populates an {@link AccessibilityEvent} with information about the
* specified item.
* <p>
- * Implementations <b>must</b> populate the following required fields:
+ * The helper class automatically populates the following fields based on
+ * the values set by
+ * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)},
+ * but implementations may optionally override them:
* <ul>
- * <li>event text, see {@link AccessibilityEvent#getText} or
- * {@link AccessibilityEvent#setContentDescription}
- * </ul>
- * <p>
- * The helper class automatically populates the following fields with
- * default values, but implementations may optionally override them:
- * <ul>
- * <li>item class name, set to android.view.View, see
- * {@link AccessibilityEvent#setClassName}
+ * <li>event text, see {@link AccessibilityEvent#getText()}
+ * <li>content description, see
+ * {@link AccessibilityEvent#setContentDescription(CharSequence)}
+ * <li>scrollability, see {@link AccessibilityEvent#setScrollable(boolean)}
+ * <li>password state, see {@link AccessibilityEvent#setPassword(boolean)}
+ * <li>enabled state, see {@link AccessibilityEvent#setEnabled(boolean)}
+ * <li>checked state, see {@link AccessibilityEvent#setChecked(boolean)}
* </ul>
* <p>
* The following required fields are automatically populated by the
* helper class and may not be overridden:
* <ul>
+ * <li>item class name, set to the value used in
+ * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}
* <li>package name, set to the package of the host view's
* {@link Context}, see {@link AccessibilityEvent#setPackageName}
* <li>event source, set to the host view and virtual view identifier,
@@ -635,55 +1100,76 @@
* populate the event
* @param event The event to populate
*/
- protected abstract void onPopulateEventForVirtualView(
- int virtualViewId, AccessibilityEvent event);
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ // Default implementation is no-op.
+ }
+
+ /**
+ * Populates an {@link AccessibilityEvent} with information about the host
+ * view.
+ * <p>
+ * The default implementation is a no-op.
+ *
+ * @param event the event to populate with information about the host view
+ */
+ protected void onPopulateEventForHost(AccessibilityEvent event) {
+ // Default implementation is no-op.
+ }
/**
* Populates an {@link AccessibilityNodeInfoCompat} with information
* about the specified item.
* <p>
- * Implementations <b>must</b> populate the following required fields:
+ * Implementations <strong>must</strong> populate the following required
+ * fields:
* <ul>
- * <li>event text, see {@link AccessibilityNodeInfoCompat#setText} or
- * {@link AccessibilityNodeInfoCompat#setContentDescription}
+ * <li>event text, see
+ * {@link AccessibilityNodeInfoCompat#setText(CharSequence)} or
+ * {@link AccessibilityNodeInfoCompat#setContentDescription(CharSequence)}
* <li>bounds in parent coordinates, see
- * {@link AccessibilityNodeInfoCompat#setBoundsInParent}
+ * {@link AccessibilityNodeInfoCompat#setBoundsInParent(Rect)}
* </ul>
* <p>
* The helper class automatically populates the following fields with
* default values, but implementations may optionally override them:
* <ul>
- * <li>enabled state, set to true, see
- * {@link AccessibilityNodeInfoCompat#setEnabled}
- * <li>item class name, identical to the class name set by
- * {@link #onPopulateEventForVirtualView}, see
- * {@link AccessibilityNodeInfoCompat#setClassName}
+ * <li>enabled state, set to {@code true}, see
+ * {@link AccessibilityNodeInfoCompat#setEnabled(boolean)}
+ * <li>keyboard focusability, set to {@code true}, see
+ * {@link AccessibilityNodeInfoCompat#setFocusable(boolean)}
+ * <li>item class name, set to {@code android.view.View}, see
+ * {@link AccessibilityNodeInfoCompat#setClassName(CharSequence)}
* </ul>
* <p>
* The following required fields are automatically populated by the
* helper class and may not be overridden:
* <ul>
* <li>package name, identical to the package name set by
- * {@link #onPopulateEventForVirtualView}, see
+ * {@link #onPopulateEventForVirtualView(int, AccessibilityEvent)}, see
* {@link AccessibilityNodeInfoCompat#setPackageName}
* <li>node source, identical to the event source set in
- * {@link #onPopulateEventForVirtualView}, see
+ * {@link #onPopulateEventForVirtualView(int, AccessibilityEvent)}, see
* {@link AccessibilityNodeInfoCompat#setSource(View, int)}
* <li>parent view, set to the host view, see
* {@link AccessibilityNodeInfoCompat#setParent(View)}
* <li>visibility, computed based on parent-relative bounds, see
- * {@link AccessibilityNodeInfoCompat#setVisibleToUser}
+ * {@link AccessibilityNodeInfoCompat#setVisibleToUser(boolean)}
* <li>accessibility focus, computed based on internal helper state, see
- * {@link AccessibilityNodeInfoCompat#setAccessibilityFocused}
+ * {@link AccessibilityNodeInfoCompat#setAccessibilityFocused(boolean)}
+ * <li>keyboard focus, computed based on internal helper state, see
+ * {@link AccessibilityNodeInfoCompat#setFocused(boolean)}
* <li>bounds in screen coordinates, computed based on host view bounds,
- * see {@link AccessibilityNodeInfoCompat#setBoundsInScreen}
+ * see {@link AccessibilityNodeInfoCompat#setBoundsInScreen(Rect)}
* </ul>
* <p>
- * Additionally, the helper class automatically handles accessibility
- * focus management by adding the appropriate
- * {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS} or
+ * Additionally, the helper class automatically handles keyboard focus and
+ * accessibility focus management by adding the appropriate
+ * {@link AccessibilityNodeInfoCompat#ACTION_FOCUS},
+ * {@link AccessibilityNodeInfoCompat#ACTION_CLEAR_FOCUS},
+ * {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS}, or
* {@link AccessibilityNodeInfoCompat#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
- * action. Implementations must <b>never</b> manually add these actions.
+ * actions. Implementations must <strong>never</strong> manually add these
+ * actions.
* <p>
* The helper class also automatically modifies parent- and
* screen-relative bounds to reflect the portion of the item visible
@@ -700,10 +1186,11 @@
* Populates an {@link AccessibilityNodeInfoCompat} with information
* about the host view.
* <p>
- * The following required fields are automatically populated by the
- * helper class and may not be overridden:
+ * The default implementation is a no-op.
+ *
+ * @param node the node to populate with information about the host view
*/
- public void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
+ protected void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
// Default implementation is no-op.
}
@@ -713,8 +1200,9 @@
* {@link AccessibilityNodeInfoCompat#performAction(int, Bundle)} for
* more information.
* <p>
- * Implementations <b>must</b> handle any actions added manually in
- * {@link #onPopulateNodeForVirtualView}.
+ * Implementations <strong>must</strong> handle any actions added manually
+ * in
+ * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
* <p>
* The helper class automatically handles focus management resulting
* from {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS}
@@ -733,13 +1221,16 @@
int virtualViewId, int action, Bundle arguments);
/**
- * Exposes a virtual view hierarchy to the accessibility framework. Only
- * used in API 16+.
+ * Exposes a virtual view hierarchy to the accessibility framework.
*/
- private class ExploreByTouchNodeProvider extends AccessibilityNodeProviderCompat {
+ private class MyNodeProvider extends AccessibilityNodeProviderCompat {
@Override
public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) {
- return ExploreByTouchHelper.this.createNode(virtualViewId);
+ // The caller takes ownership of the node and is expected to
+ // recycle it when done, so always return a copy.
+ final AccessibilityNodeInfoCompat node =
+ ExploreByTouchHelper.this.obtainAccessibilityNodeInfo(virtualViewId);
+ return AccessibilityNodeInfoCompat.obtain(node);
}
@Override
diff --git a/v4/java/android/support/v4/widget/FocusStrategy.java b/v4/java/android/support/v4/widget/FocusStrategy.java
new file mode 100644
index 0000000..8be9f1a
--- /dev/null
+++ b/v4/java/android/support/v4/widget/FocusStrategy.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2015 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.widget;
+
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.View;
+
+import android.support.v4.view.ViewCompat.FocusRealDirection;
+import android.support.v4.view.ViewCompat.FocusRelativeDirection;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Implements absolute and relative focus movement strategies. Adapted from
+ * android.view.FocusFinder to work with generic collections of bounded items.
+ */
+class FocusStrategy {
+ public static <L,T> T findNextFocusInRelativeDirection(@NonNull L focusables,
+ @NonNull CollectionAdapter<L,T> collectionAdapter, @NonNull BoundsAdapter<T> adapter,
+ @Nullable T focused, @FocusRelativeDirection int direction, boolean isLayoutRtl,
+ boolean wrap) {
+ final int count = collectionAdapter.size(focusables);
+ final ArrayList<T> sortedFocusables = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ sortedFocusables.add(collectionAdapter.get(focusables, i));
+ }
+
+ final SequentialComparator<T> comparator = new SequentialComparator<>(isLayoutRtl, adapter);
+ Collections.sort(sortedFocusables, comparator);
+
+ switch (direction) {
+ case View.FOCUS_FORWARD:
+ return getNextFocusable(focused, sortedFocusables, wrap);
+ case View.FOCUS_BACKWARD:
+ return getPreviousFocusable(focused, sortedFocusables, wrap);
+ default:
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_FORWARD, FOCUS_BACKWARD}.");
+ }
+ }
+
+ private static <T> T getNextFocusable(T focused, ArrayList<T> focusables, boolean wrap) {
+ final int count = focusables.size();
+
+ // The position of the next focusable item, which is the first item if
+ // no item is currently focused.
+ final int position = (focused == null ? -1 : focusables.lastIndexOf(focused)) + 1;
+ if (position < count) {
+ return focusables.get(position);
+ } else if (wrap && count > 0) {
+ return focusables.get(0);
+ } else {
+ return null;
+ }
+ }
+
+ private static <T> T getPreviousFocusable(T focused, ArrayList<T> focusables, boolean wrap) {
+ final int count = focusables.size();
+
+ // The position of the previous focusable item, which is the last item
+ // if no item is currently focused.
+ final int position = (focused == null ? count : focusables.indexOf(focused)) - 1;
+ if (position >= 0) {
+ return focusables.get(position);
+ } else if (wrap && count > 0) {
+ return focusables.get(count - 1);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sorts views according to their visual layout and geometry for default tab order.
+ * This is used for sequential focus traversal.
+ */
+ private static class SequentialComparator<T> implements Comparator<T> {
+ private final Rect mTemp1 = new Rect();
+ private final Rect mTemp2 = new Rect();
+
+ private final boolean mIsLayoutRtl;
+ private final BoundsAdapter<T> mAdapter;
+
+ public SequentialComparator(boolean isLayoutRtl, BoundsAdapter<T> adapter) {
+ mIsLayoutRtl = isLayoutRtl;
+ mAdapter = adapter;
+ }
+
+ public int compare(T first, T second) {
+ final Rect firstRect = mTemp1;
+ final Rect secondRect = mTemp2;
+
+ mAdapter.obtainBounds(first, firstRect);
+ mAdapter.obtainBounds(second, secondRect);
+
+ if (firstRect.top < secondRect.top) {
+ return -1;
+ } else if (firstRect.top > secondRect.top) {
+ return 1;
+ } else if (firstRect.left < secondRect.left) {
+ return mIsLayoutRtl ? 1 : -1;
+ } else if (firstRect.left > secondRect.left) {
+ return mIsLayoutRtl ? -1 : 1;
+ } else if (firstRect.bottom < secondRect.bottom) {
+ return -1;
+ } else if (firstRect.bottom > secondRect.bottom) {
+ return 1;
+ } else if (firstRect.right < secondRect.right) {
+ return mIsLayoutRtl ? 1 : -1;
+ } else if (firstRect.right > secondRect.right) {
+ return mIsLayoutRtl ? -1 : 1;
+ } else {
+ // The view are distinct but completely coincident so we
+ // consider them equal for our purposes. Since the sort is
+ // stable, this means that the views will retain their
+ // layout order relative to one another.
+ return 0;
+ }
+ }
+ }
+
+ public static <L,T> T findNextFocusInAbsoluteDirection(@NonNull L focusables,
+ @NonNull CollectionAdapter<L,T> collectionAdapter, @NonNull BoundsAdapter<T> adapter,
+ @Nullable T focused, @NonNull Rect focusedRect, int direction) {
+ // Initialize the best candidate to something impossible so that
+ // the first plausible view will become the best choice.
+ final Rect bestCandidateRect = new Rect(focusedRect);
+
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ bestCandidateRect.offset(focusedRect.width() + 1, 0);
+ break;
+ case View.FOCUS_RIGHT:
+ bestCandidateRect.offset(-(focusedRect.width() + 1), 0);
+ break;
+ case View.FOCUS_UP:
+ bestCandidateRect.offset(0, focusedRect.height() + 1);
+ break;
+ case View.FOCUS_DOWN:
+ bestCandidateRect.offset(0, -(focusedRect.height() + 1));
+ break;
+ default:
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ T closest = null;
+
+ final int count = collectionAdapter.size(focusables);
+ final Rect focusableRect = new Rect();
+ for (int i = 0; i < count; i++) {
+ final T focusable = collectionAdapter.get(focusables, i);
+ if (focusable == focused) {
+ continue;
+ }
+
+ // get focus bounds of other view
+ adapter.obtainBounds(focusable, focusableRect);
+ if (isBetterCandidate(direction, focusedRect, focusableRect, bestCandidateRect)) {
+ bestCandidateRect.set(focusableRect);
+ closest = focusable;
+ }
+ }
+
+ return closest;
+ }
+
+ /**
+ * Is candidate a better candidate than currentBest for a focus search
+ * in a particular direction from a source rect? This is the core
+ * routine that determines the order of focus searching.
+ *
+ * @param direction the direction (up, down, left, right)
+ * @param source the source from which we are searching
+ * @param candidate the candidate rectangle
+ * @param currentBest the current best rectangle
+ * @return {@code true} if the candidate rectangle is a better than the
+ * current best rectangle, {@code false} otherwise
+ */
+ private static boolean isBetterCandidate(
+ @FocusRealDirection int direction, @NonNull Rect source,
+ @NonNull Rect candidate, @NonNull Rect currentBest) {
+ // To be a better candidate, need to at least be a candidate in the
+ // first place. :)
+ if (!isCandidate(source, candidate, direction)) {
+ return false;
+ }
+
+ // We know that candidateRect is a candidate. If currentBest is not
+ // a candidate, candidateRect is better.
+ if (!isCandidate(source, currentBest, direction)) {
+ return true;
+ }
+
+ // If candidateRect is better by beam, it wins.
+ if (beamBeats(direction, source, candidate, currentBest)) {
+ return true;
+ }
+
+ // If currentBest is better, then candidateRect cant' be. :)
+ if (beamBeats(direction, source, currentBest, candidate)) {
+ return false;
+ }
+
+ // Otherwise, do fudge-tastic comparison of the major and minor
+ // axis.
+ final int candidateDist = getWeightedDistanceFor(
+ majorAxisDistance(direction, source, candidate),
+ minorAxisDistance(direction, source, candidate));
+ final int currentBestDist = getWeightedDistanceFor(
+ majorAxisDistance(direction, source, currentBest),
+ minorAxisDistance(direction, source, currentBest));
+ return candidateDist < currentBestDist;
+ }
+
+ /**
+ * One rectangle may be another candidate than another by virtue of
+ * being exclusively in the beam of the source rect.
+ *
+ * @return whether rect1 is a better candidate than rect2 by virtue of
+ * it being in source's beam
+ */
+ private static boolean beamBeats(@FocusRealDirection int direction,
+ @NonNull Rect source, @NonNull Rect rect1, @NonNull Rect rect2) {
+ final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
+ final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
+
+ // If rect1 isn't exclusively in the src beam, it doesn't win.
+ if (rect2InSrcBeam || !rect1InSrcBeam) {
+ return false;
+ }
+
+ // We know rect1 is in the beam, and rect2 is not.
+
+ // If rect1 is to the direction of, and rect2 is not, rect1 wins.
+ // For example, for direction left, if rect1 is to the left of the
+ // source and rect2 is below, then we always prefer the in beam
+ // rect1, since rect2 could be reached by going down.
+ if (!isToDirectionOf(direction, source, rect2)) {
+ return true;
+ }
+
+ // For horizontal directions, being exclusively in beam always
+ // wins.
+ if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) {
+ return true;
+ }
+
+ // For vertical directions, beams only beat up to a point: now, as
+ // long as rect2 isn't completely closer, rect1 wins, e.g. for
+ // direction down, completely closer means for rect2's top edge to
+ // be closer to the source's top edge than rect1's bottom edge.
+ return majorAxisDistance(direction, source, rect1)
+ < majorAxisDistanceToFarEdge(direction, source, rect2);
+ }
+
+ /**
+ * Fudge-factor opportunity: how to calculate distance given major and
+ * minor axis distances.
+ * <p/>
+ * Warning: this fudge factor is finely tuned, be sure to run all focus
+ * tests if you dare tweak it.
+ */
+ private static int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
+ return 13 * majorAxisDistance * majorAxisDistance
+ + minorAxisDistance * minorAxisDistance;
+ }
+
+ /**
+ * Is destRect a candidate for the next focus given the direction? This
+ * checks whether the dest is at least partially to the direction of
+ * (e.g. left of) from source.
+ * <p/>
+ * Includes an edge case for an empty rect,which is used in some cases
+ * when searching from a point on the screen.
+ */
+ private static boolean isCandidate(@NonNull Rect srcRect, @NonNull Rect destRect,
+ @FocusRealDirection int direction) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return (srcRect.right > destRect.right || srcRect.left >= destRect.right)
+ && srcRect.left > destRect.left;
+ case View.FOCUS_RIGHT:
+ return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
+ && srcRect.right < destRect.right;
+ case View.FOCUS_UP:
+ return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
+ && srcRect.top > destRect.top;
+ case View.FOCUS_DOWN:
+ return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
+ && srcRect.bottom < destRect.bottom;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+
+ /**
+ * Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap?
+ *
+ * @param direction the direction (up, down, left, right)
+ * @param rect1 the first rectangle
+ * @param rect2 the second rectangle
+ * @return whether the beams overlap
+ */
+ private static boolean beamsOverlap(@FocusRealDirection int direction,
+ @NonNull Rect rect1, @NonNull Rect rect2) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ case View.FOCUS_RIGHT:
+ return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
+ case View.FOCUS_UP:
+ case View.FOCUS_DOWN:
+ return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * e.g for left, is 'to left of'
+ */
+ private static boolean isToDirectionOf(@FocusRealDirection int direction,
+ @NonNull Rect src, @NonNull Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return src.left >= dest.right;
+ case View.FOCUS_RIGHT:
+ return src.right <= dest.left;
+ case View.FOCUS_UP:
+ return src.top >= dest.bottom;
+ case View.FOCUS_DOWN:
+ return src.bottom <= dest.top;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * @return the distance from the edge furthest in the given direction
+ * of source to the edge nearest in the given direction of
+ * dest. If the dest is not in the direction from source,
+ * returns 0.
+ */
+ private static int majorAxisDistance(@FocusRealDirection int direction,
+ @NonNull Rect source, @NonNull Rect dest) {
+ return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
+ }
+
+ private static int majorAxisDistanceRaw(@FocusRealDirection int direction,
+ @NonNull Rect source, @NonNull Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return source.left - dest.right;
+ case View.FOCUS_RIGHT:
+ return dest.left - source.right;
+ case View.FOCUS_UP:
+ return source.top - dest.bottom;
+ case View.FOCUS_DOWN:
+ return dest.top - source.bottom;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * @return the distance along the major axis w.r.t the direction from
+ * the edge of source to the far edge of dest. If the dest is
+ * not in the direction from source, returns 1 to break ties
+ * with {@link #majorAxisDistance}.
+ */
+ private static int majorAxisDistanceToFarEdge(@FocusRealDirection int direction,
+ @NonNull Rect source, @NonNull Rect dest) {
+ return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
+ }
+
+ private static int majorAxisDistanceToFarEdgeRaw(
+ @FocusRealDirection int direction, @NonNull Rect source,
+ @NonNull Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return source.left - dest.left;
+ case View.FOCUS_RIGHT:
+ return dest.right - source.right;
+ case View.FOCUS_UP:
+ return source.top - dest.top;
+ case View.FOCUS_DOWN:
+ return dest.bottom - source.bottom;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * Finds the distance on the minor axis w.r.t the direction to the
+ * nearest edge of the destination rectangle.
+ *
+ * @param direction the direction (up, down, left, right)
+ * @param source the source rect
+ * @param dest the destination rect
+ * @return the distance
+ */
+ private static int minorAxisDistance(@FocusRealDirection int direction, @NonNull Rect source,
+ @NonNull Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ case View.FOCUS_RIGHT:
+ // the distance between the center verticals
+ return Math.abs(
+ ((source.top + source.height() / 2) -
+ ((dest.top + dest.height() / 2))));
+ case View.FOCUS_UP:
+ case View.FOCUS_DOWN:
+ // the distance between the center horizontals
+ return Math.abs(
+ ((source.left + source.width() / 2) -
+ ((dest.left + dest.width() / 2))));
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * Adapter used to obtain bounds from a generic data type.
+ */
+ public interface BoundsAdapter<T> {
+ void obtainBounds(T data, Rect outBounds);
+ }
+
+ /**
+ * Adapter used to obtain items from a generic collection type.
+ */
+ public interface CollectionAdapter<T, V> {
+ V get(T collection, int index);
+ int size(T collection);
+ }
+}
diff --git a/v4/java/android/support/v4/widget/PopupWindowCompat.java b/v4/java/android/support/v4/widget/PopupWindowCompat.java
index 17fc95b..68dbb02 100644
--- a/v4/java/android/support/v4/widget/PopupWindowCompat.java
+++ b/v4/java/android/support/v4/widget/PopupWindowCompat.java
@@ -16,6 +16,9 @@
package android.support.v4.widget;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.PopupWindow;
@@ -43,6 +46,13 @@
@Override
public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
int gravity) {
+ final int hgrav = GravityCompat.getAbsoluteGravity(gravity,
+ ViewCompat.getLayoutDirection(anchor)) & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if (hgrav == Gravity.RIGHT) {
+ // Flip the location to align the right sides of the popup and
+ // anchor instead of left.
+ xoff -= (popup.getWidth() - anchor.getWidth());
+ }
popup.showAsDropDown(anchor, xoff, yoff);
}
diff --git a/v4/java/android/support/v4/widget/SimpleCursorAdapter.java b/v4/java/android/support/v4/widget/SimpleCursorAdapter.java
index ca71e9e..12a85bf 100644
--- a/v4/java/android/support/v4/widget/SimpleCursorAdapter.java
+++ b/v4/java/android/support/v4/widget/SimpleCursorAdapter.java
@@ -63,7 +63,7 @@
super(context, layout, c);
mTo = to;
mOriginalFrom = from;
- findColumns(from);
+ findColumns(c, from);
}
/**
@@ -89,7 +89,7 @@
super(context, layout, c, flags);
mTo = to;
mOriginalFrom = from;
- findColumns(from);
+ findColumns(c, from);
}
/**
@@ -109,10 +109,9 @@
*
* @throws IllegalStateException if binding cannot occur
*
- * @see android.widget.CursorAdapter#bindView(android.view.View,
- * android.content.Context, android.database.Cursor)
+ * @see android.widget.CursorAdapter#bindView(View, Context, Cursor)
* @see #getViewBinder()
- * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+ * @see #setViewBinder(ViewBinder)
* @see #setViewImage(ImageView, String)
* @see #setViewText(TextView, String)
*/
@@ -156,7 +155,7 @@
* @return a ViewBinder or null if the binder does not exist
*
* @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
- * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+ * @see #setViewBinder(ViewBinder)
*/
public ViewBinder getViewBinder() {
return mViewBinder;
@@ -201,7 +200,7 @@
/**
* Called by bindView() to set the text for a TextView but only if
* there is no existing ViewBinder or if the existing ViewBinder cannot
- * handle binding to an TextView.
+ * handle binding to a TextView.
*
* Intended to be overridden by Adapters that need to filter strings
* retrieved from the database.
@@ -221,7 +220,7 @@
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
* @see #setStringConversionColumn(int)
- * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+ * @see #setCursorToStringConverter(CursorToStringConverter)
* @see #getCursorToStringConverter()
*/
public int getStringConversionColumn() {
@@ -239,7 +238,7 @@
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
* @see #getStringConversionColumn()
- * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+ * @see #setCursorToStringConverter(CursorToStringConverter)
* @see #getCursorToStringConverter()
*/
public void setStringConversionColumn(int stringConversionColumn) {
@@ -253,7 +252,7 @@
* @return null if the converter does not exist or an instance of
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
*
- * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+ * @see #setCursorToStringConverter(CursorToStringConverter)
* @see #getStringConversionColumn()
* @see #setStringConversionColumn(int)
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
@@ -269,7 +268,7 @@
* @param cursorToStringConverter the Cursor to String converter, or
* null to remove the converter
*
- * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+ * @see #setCursorToStringConverter(CursorToStringConverter)
* @see #getStringConversionColumn()
* @see #setStringConversionColumn(int)
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
@@ -301,20 +300,21 @@
}
/**
- * Create a map from an array of strings to an array of column-id integers in mCursor.
- * If mCursor is null, the array will be discarded.
- *
+ * Create a map from an array of strings to an array of column-id integers in cursor c.
+ * If c is null, the array will be discarded.
+ *
+ * @param c the cursor to find the columns from
* @param from the Strings naming the columns of interest
*/
- private void findColumns(String[] from) {
- if (mCursor != null) {
+ private void findColumns(Cursor c, String[] from) {
+ if (c != null) {
int i;
int count = from.length;
if (mFrom == null || mFrom.length != count) {
mFrom = new int[count];
}
for (i = 0; i < count; i++) {
- mFrom[i] = mCursor.getColumnIndexOrThrow(from[i]);
+ mFrom[i] = c.getColumnIndexOrThrow(from[i]);
}
} else {
mFrom = null;
@@ -323,15 +323,16 @@
@Override
public Cursor swapCursor(Cursor c) {
- Cursor res = super.swapCursor(c);
- // rescan columns in case cursor layout is different
- findColumns(mOriginalFrom);
- return res;
+ // super.swapCursor() will notify observers before we have
+ // a valid mapping, make sure we have a mapping before this
+ // happens
+ findColumns(c, mOriginalFrom);
+ return super.swapCursor(c);
}
-
+
/**
* Change the cursor and change the column-to-view mappings at the same time.
- *
+ *
* @param c The database cursor. Can be null if the cursor is not available yet.
* @param from A list of column names representing the data to bind to the UI. Can be null
* if the cursor is not available yet.
@@ -343,8 +344,11 @@
public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
mOriginalFrom = from;
mTo = to;
- super.changeCursor(c);
- findColumns(mOriginalFrom);
+ // super.changeCursor() will notify observers before we have
+ // a valid mapping, make sure we have a mapping before this
+ // happens
+ findColumns(c, mOriginalFrom);
+ super.changeCursor(c);
}
/**
@@ -356,11 +360,11 @@
* change the way binding occurs for views supported by
* SimpleCursorAdapter.
*
- * @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
+ * @see SimpleCursorAdapter#bindView(View, Context, Cursor)
* @see SimpleCursorAdapter#setViewImage(ImageView, String)
* @see SimpleCursorAdapter#setViewText(TextView, String)
*/
- public static interface ViewBinder {
+ public interface ViewBinder {
/**
* Binds the Cursor column defined by the specified index to the specified view.
*
@@ -383,7 +387,7 @@
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
- public static interface CursorToStringConverter {
+ public interface CursorToStringConverter {
/**
* Returns a CharSequence representing the specified Cursor.
*
diff --git a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
index 091d5a4..c0edf53 100644
--- a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
+++ b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
@@ -37,6 +37,7 @@
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.PrintManager;
+import android.print.PrintAttributes.MediaSize;
import android.print.pdf.PrintedPdfDocument;
import android.util.Log;
@@ -87,13 +88,20 @@
public void onFinish();
}
+ /**
+ * Whether the PrintActivity respects the suggested orientation
+ */
+ protected boolean mPrintActivityRespectsOrientation;
+
int mScaleMode = SCALE_MODE_FILL;
int mColorMode = COLOR_MODE_COLOR;
- int mOrientation = ORIENTATION_LANDSCAPE;
+ int mOrientation;
PrintHelperKitkat(Context context) {
+ mPrintActivityRespectsOrientation = true;
+
mContext = context;
}
@@ -150,6 +158,10 @@
* {@link #ORIENTATION_LANDSCAPE} or {@link #ORIENTATION_PORTRAIT}
*/
public int getOrientation() {
+ /// Unset defaults to landscape but might turn image
+ if (mOrientation == 0) {
+ return ORIENTATION_LANDSCAPE;
+ }
return mOrientation;
}
@@ -164,6 +176,20 @@
}
/**
+ * Check if the supplied bitmap should best be printed on a portrait orientation paper.
+ *
+ * @param bitmap The bitmap to be printed.
+ * @return true iff the picture should best be printed on a portrait orientation paper.
+ */
+ private static boolean isPortrait(Bitmap bitmap) {
+ if (bitmap.getWidth() <= bitmap.getHeight()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
* Prints a bitmap.
*
* @param jobName The print job name.
@@ -177,8 +203,10 @@
}
final int fittingMode = mScaleMode; // grab the fitting mode at time of call
PrintManager printManager = (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE);
- PrintAttributes.MediaSize mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT;
- if (bitmap.getWidth() > bitmap.getHeight()) {
+ PrintAttributes.MediaSize mediaSize;
+ if (isPortrait(bitmap)) {
+ mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT;
+ } else {
mediaSize = PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE;
}
PrintAttributes attr = new PrintAttributes.Builder()
@@ -325,7 +353,9 @@
final LayoutResultCallback layoutResultCallback,
Bundle bundle) {
- mAttributes = newPrintAttributes;
+ synchronized (this) {
+ mAttributes = newPrintAttributes;
+ }
if (cancellationSignal.isCanceled()) {
layoutResultCallback.onLayoutCancelled();
@@ -343,7 +373,6 @@
}
mLoadBitmap = new AsyncTask<Uri, Boolean, Bitmap>() {
-
@Override
protected void onPreExecute() {
// First register for cancellation requests.
@@ -370,12 +399,35 @@
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
+
+ // If orientation was not set by the caller, try to fit the bitmap on
+ // the current paper by potentially rotating the bitmap by 90 degrees.
+ if (bitmap != null
+ && (!mPrintActivityRespectsOrientation || mOrientation == 0)) {
+ MediaSize mediaSize;
+
+ synchronized (this) {
+ mediaSize = mAttributes.getMediaSize();
+ }
+
+ if (mediaSize != null) {
+ if (mediaSize.isPortrait() != isPortrait(bitmap)) {
+ Matrix rotation = new Matrix();
+
+ rotation.postRotate(90);
+ bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
+ bitmap.getHeight(), rotation, true);
+ }
+ }
+ }
+
mBitmap = bitmap;
if (bitmap != null) {
PrintDocumentInfo info = new PrintDocumentInfo.Builder(jobName)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_PHOTO)
.setPageCount(1)
.build();
+
boolean changed = !newPrintAttributes.equals(oldPrintAttributes);
layoutResultCallback.onLayoutFinished(info, changed);
@@ -478,7 +530,7 @@
PrintAttributes.Builder builder = new PrintAttributes.Builder();
builder.setColorMode(mColorMode);
- if (mOrientation == ORIENTATION_LANDSCAPE) {
+ if (mOrientation == ORIENTATION_LANDSCAPE || mOrientation == 0) {
builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE);
} else if (mOrientation == ORIENTATION_PORTRAIT) {
builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT);
diff --git a/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java b/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
index 3805d02..0629d24 100644
--- a/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
+++ b/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
@@ -23,6 +23,9 @@
* KitKat-specific AccessibilityNodeInfo API implementation.
*/
class AccessibilityNodeInfoCompatKitKat {
+ private static final String ROLE_DESCRIPTION_KEY =
+ "AccessibilityNodeInfo.roleDescription";
+
static int getLiveRegion(Object info) {
return ((AccessibilityNodeInfo) info).getLiveRegion();
}
@@ -112,6 +115,16 @@
((AccessibilityNodeInfo) info).setMultiLine(multiLine);
}
+ public static CharSequence getRoleDescription(Object info) {
+ Bundle extras = getExtras(info);
+ return extras.getCharSequence(ROLE_DESCRIPTION_KEY);
+ }
+
+ public static void setRoleDescription(Object info, CharSequence roleDescription) {
+ Bundle extras = getExtras(info);
+ extras.putCharSequence(ROLE_DESCRIPTION_KEY, roleDescription);
+ }
+
static class CollectionInfo {
static int getColumnCount(Object info) {
return ((AccessibilityNodeInfo.CollectionInfo) info).getColumnCount();
diff --git a/v4/tests/AndroidManifest.xml b/v4/tests/AndroidManifest.xml
index f0fea8f..def9267 100644
--- a/v4/tests/AndroidManifest.xml
+++ b/v4/tests/AndroidManifest.xml
@@ -36,8 +36,6 @@
<activity android:name="android.support.v4.view.ViewPagerWithTabStripActivity"/>
- <activity android:name="android.support.v4.widget.TestActivity"/>
-
<activity android:name="android.support.v4.view.VpaActivity"/>
<activity
diff --git a/v4/tests/java/android/support/v4/app/NestedFragmentRestoreTest.java b/v4/tests/java/android/support/v4/app/NestedFragmentRestoreTest.java
new file mode 100644
index 0000000..2ec1a6c
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/NestedFragmentRestoreTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.app.test.FragmentTestActivity.ChildFragment;
+import android.support.v4.app.test.FragmentTestActivity.ChildFragment.OnAttachListener;
+import android.support.v4.app.test.FragmentTestActivity.ParentFragment;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNotSame;
+import static junit.framework.TestCase.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+public class NestedFragmentRestoreTest {
+
+ @Rule
+ public ActivityTestRule<FragmentTestActivity> mActivityRule = new ActivityTestRule<>(
+ FragmentTestActivity.class);
+
+ public NestedFragmentRestoreTest() {
+ }
+
+ @Test
+ @SmallTest
+ public void recreateActivity() throws Throwable {
+ final FragmentTestActivity activity = mActivityRule.getActivity();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ FragmentTestActivity.ParentFragment parent = new ParentFragment();
+ parent.setRetainChildInstance(true);
+
+ activity.getSupportFragmentManager().beginTransaction()
+ .add(parent, "parent")
+ .commitNow();
+ }
+ });
+
+ FragmentManager fm = activity.getSupportFragmentManager();
+ ParentFragment parent = (ParentFragment) fm.findFragmentByTag("parent");
+ ChildFragment child = parent.getChildFragment();
+
+ final Context[] attachedTo = new Context[1];
+ final CountDownLatch latch = new CountDownLatch(1);
+ child.setOnAttachListener(new OnAttachListener() {
+ @Override
+ public void onAttach(Context activity, ChildFragment fragment) {
+ attachedTo[0] = activity;
+ latch.countDown();
+ }
+ });
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ activity.recreate();
+ }
+ });
+
+ assertTrue("timeout waiting for recreate", latch.await(10, TimeUnit.SECONDS));
+
+ assertNotNull("attached as part of recreate", attachedTo[0]);
+ assertNotSame("attached to new context", activity, attachedTo[0]);
+ assertNotSame("attached to new parent fragment", parent, child);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/NestedFragmentTest.java b/v4/tests/java/android/support/v4/app/NestedFragmentTest.java
index fdc60b7..cc884f9 100644
--- a/v4/tests/java/android/support/v4/app/NestedFragmentTest.java
+++ b/v4/tests/java/android/support/v4/app/NestedFragmentTest.java
@@ -22,6 +22,7 @@
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.app.test.FragmentTestActivity.ChildFragment;
import android.support.v4.app.test.FragmentTestActivity.ParentFragment;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
@@ -57,7 +58,7 @@
@UiThreadTest
public void testThrowsWhenUsingReservedRequestCode() {
try {
- mParentFragment.childFragment.startActivityForResult(
+ mParentFragment.getChildFragment().startActivityForResult(
new Intent(Intent.ACTION_CALL), 16777216 /* requestCode */);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {}
@@ -72,12 +73,12 @@
new IntentFilter(Intent.ACTION_CALL), activityResult, true /* block */);
// Sanity check that onActivityResult hasn't been called yet.
- assertFalse(mParentFragment.childFragment.onActivityResultCalled);
+ assertFalse(mParentFragment.getChildFragment().onActivityResultCalled);
final CountDownLatch latch = new CountDownLatch(1);
getActivity().runOnUiThread(new Runnable() {
public void run() {
- mParentFragment.childFragment.startActivityForResult(
+ mParentFragment.getChildFragment().startActivityForResult(
new Intent(Intent.ACTION_CALL),
5 /* requestCode */);
latch.countDown();
@@ -87,9 +88,9 @@
assertTrue(getInstrumentation().checkMonitorHit(activityMonitor, 1));
- assertTrue(mParentFragment.childFragment.onActivityResultCalled);
- assertEquals(5, mParentFragment.childFragment.onActivityResultRequestCode);
- assertEquals(Activity.RESULT_OK,
- mParentFragment.childFragment.onActivityResultResultCode);
+ final ChildFragment childFragment = mParentFragment.getChildFragment();
+ assertTrue(childFragment.onActivityResultCalled);
+ assertEquals(5, childFragment.onActivityResultRequestCode);
+ assertEquals(Activity.RESULT_OK, childFragment.onActivityResultResultCode);
}
}
diff --git a/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java b/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java
index ca84498..1560ef9 100644
--- a/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java
+++ b/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java
@@ -23,7 +23,6 @@
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
import android.support.v4.test.R;
import android.transition.Transition;
import android.transition.Transition.TransitionListener;
@@ -199,21 +198,38 @@
}
public static class ParentFragment extends Fragment {
+ static final String CHILD_FRAGMENT_TAG = "childFragment";
public boolean wasAttachedInTime;
- public ChildFragment childFragment;
+
+ private boolean mRetainChild;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- childFragment = new ChildFragment();
- FragmentManager fm = getChildFragmentManager();
- fm.beginTransaction().add(childFragment, "foo").commit();
- fm.executePendingTransactions();
- wasAttachedInTime = childFragment.attached;
+
+ ChildFragment f = getChildFragment();
+ if (f == null) {
+ f = new ChildFragment();
+ if (mRetainChild) {
+ f.setRetainInstance(true);
+ }
+ getChildFragmentManager().beginTransaction().add(f, CHILD_FRAGMENT_TAG).commitNow();
+ }
+ wasAttachedInTime = f.attached;
+ }
+
+ public ChildFragment getChildFragment() {
+ return (ChildFragment) getChildFragmentManager().findFragmentByTag(CHILD_FRAGMENT_TAG);
+ }
+
+ public void setRetainChildInstance(boolean retainChild) {
+ mRetainChild = retainChild;
}
}
public static class ChildFragment extends Fragment {
+ private OnAttachListener mOnAttachListener;
+
public boolean attached;
public boolean onActivityResultCalled;
public int onActivityResultRequestCode;
@@ -223,6 +239,17 @@
public void onAttach(Context activity) {
super.onAttach(activity);
attached = true;
+ if (mOnAttachListener != null) {
+ mOnAttachListener.onAttach(activity, this);
+ }
+ }
+
+ public void setOnAttachListener(OnAttachListener listener) {
+ mOnAttachListener = listener;
+ }
+
+ public interface OnAttachListener {
+ void onAttach(Context activity, ChildFragment fragment);
}
@Override
diff --git a/v4/tests/java/android/support/v4/content/ContextCompatTest.java b/v4/tests/java/android/support/v4/content/ContextCompatTest.java
index e2fc0b7..cccf892 100644
--- a/v4/tests/java/android/support/v4/content/ContextCompatTest.java
+++ b/v4/tests/java/android/support/v4/content/ContextCompatTest.java
@@ -15,51 +15,52 @@
*/
package android.support.v4.content;
-import android.content.res.ColorStateList;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.support.v4.BaseInstrumentationTestCase;
import android.support.v4.ThemedYellowActivity;
-import android.support.v4.content.ContextCompat;
import android.support.v4.test.R;
import android.support.v4.testutils.TestUtils;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.TypedValue;
+import org.junit.Before;
+import org.junit.Test;
-public class ContextCompatTest extends ActivityInstrumentationTestCase2<ThemedYellowActivity> {
- private static final String TAG = "ResourcesCompatTest";
+import static org.junit.Assert.assertEquals;
+
+@SmallTest
+public class ContextCompatTest extends BaseInstrumentationTestCase<ThemedYellowActivity> {
+ private Context mContext;
public ContextCompatTest() {
- super("android.support.v4.content", ThemedYellowActivity.class);
+ super(ThemedYellowActivity.class);
}
- @UiThreadTest
- @SmallTest
- public void testGetColor() throws Throwable {
- Context context = getActivity();
+ @Before
+ public void setup() {
+ mContext = mActivityTestRule.getActivity();
+ }
+ @Test
+ public void testGetColor() throws Throwable {
assertEquals("Unthemed color load", 0xFFFF8090,
- ContextCompat.getColor(context, R.color.text_color));
+ ContextCompat.getColor(mContext, R.color.text_color));
if (Build.VERSION.SDK_INT >= 23) {
// The following test is only expected to pass on v23+ devices. The result of
// calling theme-aware getColor() in pre-v23 is undefined.
assertEquals("Themed yellow color load",
- ContextCompat.getColor(context, R.color.simple_themed_selector),
+ ContextCompat.getColor(mContext, R.color.simple_themed_selector),
0xFFF0B000);
}
}
- @UiThreadTest
- @SmallTest
+ @Test
public void testGetColorStateList() throws Throwable {
- Context context = getActivity();
-
ColorStateList unthemedColorStateList =
- ContextCompat.getColorStateList(context, R.color.complex_unthemed_selector);
+ ContextCompat.getColorStateList(mContext, R.color.complex_unthemed_selector);
assertEquals("Unthemed color state list load: default", 0xFF70A0C0,
unthemedColorStateList.getDefaultColor());
assertEquals("Unthemed color state list load: focused", 0xFF70B0F0,
@@ -73,7 +74,7 @@
// The following tests are only expected to pass on v23+ devices. The result of
// calling theme-aware getColorStateList() in pre-v23 is undefined.
ColorStateList themedYellowColorStateList =
- ContextCompat.getColorStateList(context, R.color.complex_themed_selector);
+ ContextCompat.getColorStateList(mContext, R.color.complex_themed_selector);
assertEquals("Themed yellow color state list load: default", 0xFFF0B000,
themedYellowColorStateList.getDefaultColor());
assertEquals("Themed yellow color state list load: focused", 0xFFF0A020,
@@ -85,45 +86,35 @@
}
}
- @UiThreadTest
- @SmallTest
+ @Test
public void testGetDrawable() throws Throwable {
- Context context = getActivity();
-
Drawable unthemedDrawable =
- ContextCompat.getDrawable(context, R.drawable.test_drawable_red);
+ ContextCompat.getDrawable(mContext, R.drawable.test_drawable_red);
TestUtils.assertAllPixelsOfColor("Unthemed drawable load",
- unthemedDrawable, context.getResources().getColor(R.color.test_red));
+ unthemedDrawable, mContext.getResources().getColor(R.color.test_red));
if (Build.VERSION.SDK_INT >= 23) {
// The following test is only expected to pass on v23+ devices. The result of
// calling theme-aware getDrawable() in pre-v23 is undefined.
Drawable themedYellowDrawable =
- ContextCompat.getDrawable(context, R.drawable.themed_drawable);
+ ContextCompat.getDrawable(mContext, R.drawable.themed_drawable);
TestUtils.assertAllPixelsOfColor("Themed yellow drawable load",
themedYellowDrawable, 0xFFF0B000);
}
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testCheckSelfPermissionNull() {
+ ContextCompat.checkSelfPermission(mContext, null);
+ }
- @UiThreadTest
- @SmallTest
- public void testCheckSelfPermission() throws Throwable {
- Context context = getActivity();
-
- try {
- ContextCompat.checkSelfPermission(context, null);
- fail("Should have thrown an exception on null parameter");
- } catch (IllegalArgumentException iae) {
- // This is the expected condition - just ignore and continue with the rest of the
- // tests in this method.
- }
-
+ @Test
+ public void testCheckSelfPermission() {
assertEquals("Vibrate permission granted", PackageManager.PERMISSION_GRANTED,
- ContextCompat.checkSelfPermission(context,
+ ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.VIBRATE));
assertEquals("Wake lock permission granted", PackageManager.PERMISSION_GRANTED,
- ContextCompat.checkSelfPermission(context,
+ ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.WAKE_LOCK));
if (Build.VERSION.SDK_INT >= 23) {
@@ -132,34 +123,34 @@
// to apps targeting SDK 23 even if those are defined in the manifest.
// This is why the following permissions are expected to be denied.
assertEquals("Read contacts permission granted", PackageManager.PERMISSION_DENIED,
- ContextCompat.checkSelfPermission(context,
+ ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.READ_CONTACTS));
assertEquals("Write contacts permission granted", PackageManager.PERMISSION_DENIED,
- ContextCompat.checkSelfPermission(context,
+ ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.WRITE_CONTACTS));
} else {
// And on older devices declared dangerous permissions are expected to be granted.
assertEquals("Read contacts permission denied", PackageManager.PERMISSION_GRANTED,
- ContextCompat.checkSelfPermission(context,
+ ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.READ_CONTACTS));
assertEquals("Write contacts permission denied", PackageManager.PERMISSION_GRANTED,
- ContextCompat.checkSelfPermission(context,
+ ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.WRITE_CONTACTS));
}
// The following permissions (normal and dangerous) are expected to be denied as they are
// not declared in our manifest.
assertEquals("Access network state permission denied", PackageManager.PERMISSION_DENIED,
- ContextCompat.checkSelfPermission(context,
+ ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.ACCESS_NETWORK_STATE));
assertEquals("Bluetooth permission denied", PackageManager.PERMISSION_DENIED,
- ContextCompat.checkSelfPermission(context,
+ ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.BLUETOOTH));
assertEquals("Call phone permission denied", PackageManager.PERMISSION_DENIED,
- ContextCompat.checkSelfPermission(context,
+ ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.CALL_PHONE));
assertEquals("Delete packages permission denied", PackageManager.PERMISSION_DENIED,
- ContextCompat.checkSelfPermission(context,
+ ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.DELETE_PACKAGES));
}
}
\ No newline at end of file
diff --git a/v4/tests/java/android/support/v4/content/res/ResourcesCompatTest.java b/v4/tests/java/android/support/v4/content/res/ResourcesCompatTest.java
index 4efca5d..19d5609 100644
--- a/v4/tests/java/android/support/v4/content/res/ResourcesCompatTest.java
+++ b/v4/tests/java/android/support/v4/content/res/ResourcesCompatTest.java
@@ -19,53 +19,53 @@
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
-import android.support.v4.content.res.ResourcesCompat;
+import android.support.test.InstrumentationRegistry;
import android.support.v4.test.R;
import android.support.v4.testutils.TestUtils;
-import android.support.v4.widget.TestActivity;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.DisplayMetrics;
-import android.util.TypedValue;
+import org.junit.Before;
+import org.junit.Test;
-public class ResourcesCompatTest extends ActivityInstrumentationTestCase2<TestActivity> {
- private static final String TAG = "ResourcesCompatTest";
+import static org.junit.Assert.assertEquals;
- public ResourcesCompatTest() {
- super("android.support.v4.content.res", TestActivity.class);
+@SmallTest
+public class ResourcesCompatTest {
+ private Resources mResources;
+
+ @Before
+ public void setup() {
+ mResources = InstrumentationRegistry.getContext().getResources();
}
- @UiThreadTest
- @SmallTest
+ @Test
public void testGetColor() throws Throwable {
- final Resources res = getActivity().getResources();
assertEquals("Unthemed color load",
- ResourcesCompat.getColor(res, R.color.text_color, null),
+ ResourcesCompat.getColor(mResources, R.color.text_color, null),
0xFFFF8090);
if (Build.VERSION.SDK_INT >= 23) {
// The following tests are only expected to pass on v23+ devices. The result of
// calling theme-aware getColor() in pre-v23 is undefined.
- final Resources.Theme yellowTheme = res.newTheme();
+ final Resources.Theme yellowTheme = mResources.newTheme();
yellowTheme.applyStyle(R.style.YellowTheme, true);
assertEquals("Themed yellow color load", 0xFFF0B000,
- ResourcesCompat.getColor(res, R.color.simple_themed_selector, yellowTheme));
+ ResourcesCompat.getColor(mResources, R.color.simple_themed_selector,
+ yellowTheme));
- final Resources.Theme lilacTheme = res.newTheme();
+ final Resources.Theme lilacTheme = mResources.newTheme();
lilacTheme.applyStyle(R.style.LilacTheme, true);
assertEquals("Themed lilac color load", 0xFFF080F0,
- ResourcesCompat.getColor(res, R.color.simple_themed_selector, lilacTheme));
+ ResourcesCompat.getColor(mResources, R.color.simple_themed_selector,
+ lilacTheme));
}
}
- @UiThreadTest
- @SmallTest
+ @Test
public void testGetColorStateList() throws Throwable {
- final Resources res = getActivity().getResources();
-
final ColorStateList unthemedColorStateList =
- ResourcesCompat.getColorStateList(res, R.color.complex_unthemed_selector, null);
+ ResourcesCompat.getColorStateList(mResources, R.color.complex_unthemed_selector,
+ null);
assertEquals("Unthemed color state list load: default", 0xFF70A0C0,
unthemedColorStateList.getDefaultColor());
assertEquals("Unthemed color state list load: focused", 0xFF70B0F0,
@@ -78,10 +78,10 @@
if (Build.VERSION.SDK_INT >= 23) {
// The following tests are only expected to pass on v23+ devices. The result of
// calling theme-aware getColorStateList() in pre-v23 is undefined.
- final Resources.Theme yellowTheme = res.newTheme();
+ final Resources.Theme yellowTheme = mResources.newTheme();
yellowTheme.applyStyle(R.style.YellowTheme, true);
final ColorStateList themedYellowColorStateList =
- ResourcesCompat.getColorStateList(res, R.color.complex_themed_selector,
+ ResourcesCompat.getColorStateList(mResources, R.color.complex_themed_selector,
yellowTheme);
assertEquals("Themed yellow color state list load: default", 0xFFF0B000,
themedYellowColorStateList.getDefaultColor());
@@ -92,10 +92,10 @@
themedYellowColorStateList.getColorForState(
new int[]{android.R.attr.state_pressed}, 0));
- final Resources.Theme lilacTheme = res.newTheme();
+ final Resources.Theme lilacTheme = mResources.newTheme();
lilacTheme.applyStyle(R.style.LilacTheme, true);
final ColorStateList themedLilacColorStateList =
- ResourcesCompat.getColorStateList(res, R.color.complex_themed_selector,
+ ResourcesCompat.getColorStateList(mResources, R.color.complex_themed_selector,
lilacTheme);
assertEquals("Themed lilac color state list load: default", 0xFFF080F0,
themedLilacColorStateList.getDefaultColor());
@@ -108,43 +108,37 @@
}
}
- @UiThreadTest
- @SmallTest
+ @Test
public void testGetDrawable() throws Throwable {
- final Resources res = getActivity().getResources();
-
final Drawable unthemedDrawable =
- ResourcesCompat.getDrawable(res, R.drawable.test_drawable_red, null);
+ ResourcesCompat.getDrawable(mResources, R.drawable.test_drawable_red, null);
TestUtils.assertAllPixelsOfColor("Unthemed drawable load",
- unthemedDrawable, res.getColor(R.color.test_red));
+ unthemedDrawable, mResources.getColor(R.color.test_red));
if (Build.VERSION.SDK_INT >= 23) {
// The following tests are only expected to pass on v23+ devices. The result of
// calling theme-aware getDrawable() in pre-v23 is undefined.
- final Resources.Theme yellowTheme = res.newTheme();
+ final Resources.Theme yellowTheme = mResources.newTheme();
yellowTheme.applyStyle(R.style.YellowTheme, true);
final Drawable themedYellowDrawable =
- ResourcesCompat.getDrawable(res, R.drawable.themed_drawable, yellowTheme);
+ ResourcesCompat.getDrawable(mResources, R.drawable.themed_drawable,
+ yellowTheme);
TestUtils.assertAllPixelsOfColor("Themed yellow drawable load",
themedYellowDrawable, 0xFFF0B000);
- final Resources.Theme lilacTheme = res.newTheme();
+ final Resources.Theme lilacTheme = mResources.newTheme();
lilacTheme.applyStyle(R.style.LilacTheme, true);
final Drawable themedLilacDrawable =
- ResourcesCompat.getDrawable(res, R.drawable.themed_drawable, lilacTheme);
+ ResourcesCompat.getDrawable(mResources, R.drawable.themed_drawable, lilacTheme);
TestUtils.assertAllPixelsOfColor("Themed lilac drawable load",
themedLilacDrawable, 0xFFF080F0);
}
}
- @UiThreadTest
- @SmallTest
+ @Test
public void testGetDrawableForDensityUnthemed() throws Throwable {
// Density-aware drawable loading for now only works on raw bitmap drawables.
- final Resources res = getActivity().getResources();
- final DisplayMetrics metrics = res.getDisplayMetrics();
-
// Different variants of density_aware_drawable are set up in the following way:
// mdpi - 12x12 px which is 12x12 dip
// hdpi - 21x21 px which is 14x14 dip
@@ -155,13 +149,13 @@
// dimensions.
final Drawable unthemedDrawableForMediumDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.density_aware_drawable,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
DisplayMetrics.DENSITY_MEDIUM, null);
// For pre-v15 devices we should get a drawable that corresponds to the density of the
// current device. For v15+ devices we should get a drawable that corresponds to the
// density requested in the API call.
final int expectedSizeForMediumDensity = (Build.VERSION.SDK_INT < 15) ?
- res.getDimensionPixelSize(R.dimen.density_aware_size) : 12;
+ mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 12;
assertEquals("Unthemed density-aware drawable load: medium width",
expectedSizeForMediumDensity, unthemedDrawableForMediumDensity.getIntrinsicWidth());
assertEquals("Unthemed density-aware drawable load: medium height",
@@ -169,39 +163,39 @@
unthemedDrawableForMediumDensity.getIntrinsicHeight());
final Drawable unthemedDrawableForHighDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.density_aware_drawable,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
DisplayMetrics.DENSITY_HIGH, null);
// For pre-v15 devices we should get a drawable that corresponds to the density of the
// current device. For v15+ devices we should get a drawable that corresponds to the
// density requested in the API call.
final int expectedSizeForHighDensity = (Build.VERSION.SDK_INT < 15) ?
- res.getDimensionPixelSize(R.dimen.density_aware_size) : 21;
+ mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 21;
assertEquals("Unthemed density-aware drawable load: high width",
expectedSizeForHighDensity, unthemedDrawableForHighDensity.getIntrinsicWidth());
assertEquals("Unthemed density-aware drawable load: high height",
expectedSizeForHighDensity, unthemedDrawableForHighDensity.getIntrinsicHeight());
final Drawable unthemedDrawableForXHighDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.density_aware_drawable,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
DisplayMetrics.DENSITY_XHIGH, null);
// For pre-v15 devices we should get a drawable that corresponds to the density of the
// current device. For v15+ devices we should get a drawable that corresponds to the
// density requested in the API call.
final int expectedSizeForXHighDensity = (Build.VERSION.SDK_INT < 15) ?
- res.getDimensionPixelSize(R.dimen.density_aware_size) : 32;
+ mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 32;
assertEquals("Unthemed density-aware drawable load: xhigh width",
expectedSizeForXHighDensity, unthemedDrawableForXHighDensity.getIntrinsicWidth());
assertEquals("Unthemed density-aware drawable load: xhigh height",
expectedSizeForXHighDensity, unthemedDrawableForXHighDensity.getIntrinsicHeight());
final Drawable unthemedDrawableForXXHighDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.density_aware_drawable,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
DisplayMetrics.DENSITY_XXHIGH, null);
// For pre-v15 devices we should get a drawable that corresponds to the density of the
// current device. For v15+ devices we should get a drawable that corresponds to the
// density requested in the API call.
final int expectedSizeForXXHighDensity = (Build.VERSION.SDK_INT < 15) ?
- res.getDimensionPixelSize(R.dimen.density_aware_size) : 54;
+ mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 54;
assertEquals("Unthemed density-aware drawable load: xxhigh width",
expectedSizeForXXHighDensity, unthemedDrawableForXXHighDensity.getIntrinsicWidth());
assertEquals("Unthemed density-aware drawable load: xxhigh height",
@@ -209,9 +203,7 @@
unthemedDrawableForXXHighDensity.getIntrinsicHeight());
}
-
- @UiThreadTest
- @SmallTest
+ @Test
public void testGetDrawableForDensityThemed() throws Throwable {
if (Build.VERSION.SDK_INT < 21) {
// The following tests are only expected to pass on v21+ devices. The result of
@@ -222,67 +214,64 @@
// Density- and theme-aware drawable loading for now only works partially. This test
// checks only for theming of a tinted bitmap XML drawable, but not correct scaling.
- final Resources res = getActivity().getResources();
- final DisplayMetrics metrics = res.getDisplayMetrics();
-
// Set up the two test themes, yellow and lilac.
- final Resources.Theme yellowTheme = res.newTheme();
+ final Resources.Theme yellowTheme = mResources.newTheme();
yellowTheme.applyStyle(R.style.YellowTheme, true);
- final Resources.Theme lilacTheme = res.newTheme();
+ final Resources.Theme lilacTheme = mResources.newTheme();
lilacTheme.applyStyle(R.style.LilacTheme, true);
Drawable themedYellowDrawableForMediumDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
DisplayMetrics.DENSITY_MEDIUM, yellowTheme);
// We should get a drawable that corresponds to the theme requested in the API call.
TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : medium color",
themedYellowDrawableForMediumDensity, 0xFFF0B000);
Drawable themedLilacDrawableForMediumDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
DisplayMetrics.DENSITY_MEDIUM, lilacTheme);
// We should get a drawable that corresponds to the theme requested in the API call.
TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : medium color",
themedLilacDrawableForMediumDensity, 0xFFF080F0);
Drawable themedYellowDrawableForHighDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
DisplayMetrics.DENSITY_HIGH, yellowTheme);
// We should get a drawable that corresponds to the theme requested in the API call.
TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : high color",
themedYellowDrawableForHighDensity, 0xFFF0B000);
Drawable themedLilacDrawableForHighDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
DisplayMetrics.DENSITY_HIGH, lilacTheme);
// We should get a drawable that corresponds to the theme requested in the API call.
TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : high color",
themedLilacDrawableForHighDensity, 0xFFF080F0);
Drawable themedYellowDrawableForXHighDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
DisplayMetrics.DENSITY_XHIGH, yellowTheme);
// We should get a drawable that corresponds to the theme requested in the API call.
TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : xhigh color",
themedYellowDrawableForXHighDensity, 0xFFF0B000);
Drawable themedLilacDrawableForXHighDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
DisplayMetrics.DENSITY_XHIGH, lilacTheme);
// We should get a drawable that corresponds to the theme requested in the API call.
TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : xhigh color",
themedLilacDrawableForXHighDensity, 0xFFF080F0);
Drawable themedYellowDrawableForXXHighDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
DisplayMetrics.DENSITY_XXHIGH, yellowTheme);
// We should get a drawable that corresponds to the theme requested in the API call.
TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : xxhigh color",
themedYellowDrawableForXXHighDensity, 0xFFF0B000);
Drawable themedLilacDrawableForXXHighDensity =
- ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
DisplayMetrics.DENSITY_XXHIGH, lilacTheme);
// We should get a drawable that corresponds to the theme requested in the API call.
TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : xxhigh color",
diff --git a/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java b/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
index b25ed4f..998980b 100644
--- a/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
+++ b/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
@@ -17,17 +17,20 @@
package android.support.v4.graphics;
import android.graphics.Color;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.lang.Integer;
import java.util.ArrayList;
-/**
- * @hide
- */
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
@SmallTest
-public class ColorUtilsTest extends AndroidTestCase {
+public class ColorUtilsTest {
// 0.5% of the max value
private static final float ALLOWED_OFFSET_HUE = 360 * 0.005f;
@@ -81,16 +84,18 @@
public void testColorToHSL() {
for (TestEntry entry : sEntryList) {
- testColorToHSL(entry.rgb, entry.hsl);
+ verifyColorToHSL(entry.rgb, entry.hsl);
}
}
+ @Test
public void testHSLToColor() {
for (TestEntry entry : sEntryList) {
- testHSLToColor(entry.hsl, entry.rgb);
+ verifyHSLToColor(entry.hsl, entry.rgb);
}
}
+ @Test
public void testColorToHslLimits() {
final float[] hsl = new float[3];
@@ -103,68 +108,77 @@
}
}
+ @Test
public void testColorToXYZ() {
for (TestEntry entry : sEntryList) {
- testColorToXYZ(entry.rgb, entry.xyz);
+ verifyColorToXYZ(entry.rgb, entry.xyz);
}
}
+ @Test
public void testColorToLAB() {
for (TestEntry entry : sEntryList) {
- testColorToLAB(entry.rgb, entry.lab);
+ verifyColorToLAB(entry.rgb, entry.lab);
}
}
+ @Test
public void testLABToXYZ() {
for (TestEntry entry : sEntryList) {
- testLABToXYZ(entry.lab, entry.xyz);
+ verifyLABToXYZ(entry.lab, entry.xyz);
}
}
+ @Test
public void testXYZToColor() {
for (TestEntry entry : sEntryList) {
- testXYZToColor(entry.xyz, entry.rgb);
+ verifyXYZToColor(entry.xyz, entry.rgb);
}
}
+ @Test
public void testLABToColor() {
for (TestEntry entry : sEntryList) {
- testLABToColor(entry.lab, entry.rgb);
+ verifyLABToColor(entry.lab, entry.rgb);
}
}
+ @Test
public void testMinAlphas() {
for (TestEntry entry : sEntryList) {
- testMinAlpha("Black title", entry.rgb, entry.blackMinAlpha30,
+ verifyMinAlpha("Black title", entry.rgb, entry.blackMinAlpha30,
ColorUtils.calculateMinimumAlpha(Color.BLACK, entry.rgb, 3.0f));
- testMinAlpha("Black body", entry.rgb, entry.blackMinAlpha45,
+ verifyMinAlpha("Black body", entry.rgb, entry.blackMinAlpha45,
ColorUtils.calculateMinimumAlpha(Color.BLACK, entry.rgb, 4.5f));
- testMinAlpha("White title", entry.rgb, entry.whiteMinAlpha30,
+ verifyMinAlpha("White title", entry.rgb, entry.whiteMinAlpha30,
ColorUtils.calculateMinimumAlpha(Color.WHITE, entry.rgb, 3.0f));
- testMinAlpha("White body", entry.rgb, entry.whiteMinAlpha45,
+ verifyMinAlpha("White body", entry.rgb, entry.whiteMinAlpha45,
ColorUtils.calculateMinimumAlpha(Color.WHITE, entry.rgb, 4.5f));
}
}
+ @Test
public void testCircularInterpolationForwards() {
- assertEquals(0f, ColorUtils.circularInterpolate(0, 180, 0f));
- assertEquals(90f, ColorUtils.circularInterpolate(0, 180, 0.5f));
- assertEquals(180f, ColorUtils.circularInterpolate(0, 180, 1f));
+ assertEquals(0f, ColorUtils.circularInterpolate(0, 180, 0f), 0f);
+ assertEquals(90f, ColorUtils.circularInterpolate(0, 180, 0.5f), 0f);
+ assertEquals(180f, ColorUtils.circularInterpolate(0, 180, 1f), 0f);
}
+ @Test
public void testCircularInterpolationBackwards() {
- assertEquals(180f, ColorUtils.circularInterpolate(180, 0, 0f));
- assertEquals(90f, ColorUtils.circularInterpolate(180, 0, 0.5f));
- assertEquals(0f, ColorUtils.circularInterpolate(180, 0, 1f));
+ assertEquals(180f, ColorUtils.circularInterpolate(180, 0, 0f), 0f);
+ assertEquals(90f, ColorUtils.circularInterpolate(180, 0, 0.5f), 0f);
+ assertEquals(0f, ColorUtils.circularInterpolate(180, 0, 1f), 0f);
}
+ @Test
public void testCircularInterpolationCrossZero() {
- assertEquals(270f, ColorUtils.circularInterpolate(270, 90, 0f));
- assertEquals(180f, ColorUtils.circularInterpolate(270, 90, 0.5f));
- assertEquals(90f, ColorUtils.circularInterpolate(270, 90, 1f));
+ assertEquals(270f, ColorUtils.circularInterpolate(270, 90, 0f), 0f);
+ assertEquals(180f, ColorUtils.circularInterpolate(270, 90, 0.5f), 0f);
+ assertEquals(90f, ColorUtils.circularInterpolate(270, 90, 1f), 0f);
}
- private static void testMinAlpha(String title, int color, float expected, int actual) {
+ private static void verifyMinAlpha(String title, int color, float expected, int actual) {
final String message = title + " text within error for #" + Integer.toHexString(color);
if (expected < 0) {
assertEquals(message, actual, -1);
@@ -173,7 +187,7 @@
}
}
- private static void testColorToHSL(int color, float[] expected) {
+ private static void verifyColorToHSL(int color, float[] expected) {
float[] actualHSL = new float[3];
ColorUtils.colorToHSL(color, actualHSL);
@@ -185,7 +199,7 @@
ALLOWED_OFFSET_LIGHTNESS);
}
- private static void testHSLToColor(float[] hsl, int expected) {
+ private static void verifyHSLToColor(float[] hsl, int expected) {
final int actualRgb = ColorUtils.HSLToColor(hsl);
assertEquals("Red not within offset", Color.red(expected), Color.red(actualRgb),
@@ -196,7 +210,7 @@
ALLOWED_OFFSET_RGB_COMPONENT);
}
- private static void testColorToLAB(int color, double[] expected) {
+ private static void verifyColorToLAB(int color, double[] expected) {
double[] result = new double[3];
ColorUtils.colorToLAB(color, result);
@@ -205,7 +219,7 @@
assertEquals("B not within offset", expected[2], result[2], ALLOWED_OFFSET_LAB);
}
- private static void testColorToXYZ(int color, double[] expected) {
+ private static void verifyColorToXYZ(int color, double[] expected) {
double[] result = new double[3];
ColorUtils.colorToXYZ(color, result);
@@ -214,7 +228,7 @@
assertEquals("Z not within offset", expected[2], result[2], ALLOWED_OFFSET_XYZ);
}
- private static void testLABToXYZ(double[] lab, double[] expected) {
+ private static void verifyLABToXYZ(double[] lab, double[] expected) {
double[] result = new double[3];
ColorUtils.LABToXYZ(lab[0], lab[1], lab[2], result);
@@ -223,17 +237,17 @@
assertEquals("Z not within offset", expected[2], result[2], ALLOWED_OFFSET_XYZ);
}
- private static void testXYZToColor(double[] xyz, int expected) {
+ private static void verifyXYZToColor(double[] xyz, int expected) {
final int result = ColorUtils.XYZToColor(xyz[0], xyz[1], xyz[2]);
- assertRGBComponentsClose(expected, result);
+ verifyRGBComponentsClose(expected, result);
}
- private static void testLABToColor(double[] lab, int expected) {
+ private static void verifyLABToColor(double[] lab, int expected) {
final int result = ColorUtils.LABToColor(lab[0], lab[1], lab[2]);
- assertRGBComponentsClose(expected, result);
+ verifyRGBComponentsClose(expected, result);
}
- private static void assertRGBComponentsClose(int expected, int actual) {
+ private static void verifyRGBComponentsClose(int expected, int actual) {
final String message = "Expected: #" + Integer.toHexString(expected)
+ ", Actual: #" + Integer.toHexString(actual);
assertEquals("R not equal: " + message, Color.red(expected), Color.red(actual), 1);
diff --git a/v4/tests/java/android/support/v4/graphics/DrawableCompatTest.java b/v4/tests/java/android/support/v4/graphics/DrawableCompatTest.java
index 4c4ce5b..32e30e1 100644
--- a/v4/tests/java/android/support/v4/graphics/DrawableCompatTest.java
+++ b/v4/tests/java/android/support/v4/graphics/DrawableCompatTest.java
@@ -19,20 +19,27 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
+import android.support.test.runner.AndroidJUnit4;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
-public class DrawableCompatTest extends AndroidTestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
- @SmallTest
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DrawableCompatTest {
+ @Test
public void testDrawableUnwrap() {
final Drawable original = new GradientDrawable();
final Drawable wrappedDrawable = DrawableCompat.wrap(original);
assertSame(original, DrawableCompat.unwrap(wrappedDrawable));
}
- @SmallTest
+ @Test
public void testDrawableChangeBoundsCopy() {
final Rect bounds = new Rect(0, 0, 10, 10);
@@ -43,10 +50,9 @@
assertEquals(bounds, wrapped.getBounds());
}
- @SmallTest
+ @Test
public void testDrawableWrapOnlyWrapsOnce() {
final Drawable wrappedDrawable = DrawableCompat.wrap(new GradientDrawable());
assertSame(wrappedDrawable, DrawableCompat.wrap(wrappedDrawable));
}
-
}
\ No newline at end of file
diff --git a/v4/tests/java/android/support/v4/text/BidiFormatterTest.java b/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
index 1d8ef7a..6dc2042 100644
--- a/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
+++ b/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
@@ -16,14 +16,19 @@
package android.support.v4.text;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.Locale;
-/** @hide */
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+@RunWith(AndroidJUnit4.class)
@SmallTest
-public class BidiFormatterTest extends AndroidTestCase {
+public class BidiFormatterTest {
private static final BidiFormatter LTR_FMT = BidiFormatter.getInstance(false /* LTR context */);
private static final BidiFormatter RTL_FMT = BidiFormatter.getInstance(true /* RTL context */);
@@ -42,6 +47,7 @@
private static final String RLE = "\u202B";
private static final String PDF = "\u202C";
+ @Test
public void testIsRtlContext() {
assertEquals(false, LTR_FMT.isRtlContext());
assertEquals(true, RTL_FMT.isRtlContext());
@@ -50,11 +56,13 @@
assertEquals(true, BidiFormatter.getInstance(true).isRtlContext());
}
+ @Test
public void testBuilderIsRtlContext() {
assertEquals(false, new BidiFormatter.Builder(false).build().isRtlContext());
assertEquals(true, new BidiFormatter.Builder(true).build().isRtlContext());
}
+ @Test
public void testIsRtl() {
assertEquals(true, BidiFormatter.getInstance(true).isRtl(HE));
assertEquals(true, BidiFormatter.getInstance(false).isRtl(HE));
@@ -63,6 +71,7 @@
assertEquals(false, BidiFormatter.getInstance(false).isRtl(EN));
}
+ @Test
public void testUnicodeWrap() {
// Make sure an input of null doesn't crash anything.
assertNull(LTR_FMT.unicodeWrap(null));
@@ -148,14 +157,16 @@
LTR_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR));
assertEquals("entry and exit dir opposite to LTR context, no isolation",
HE + EN + HE,
- LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR, false));
+ LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR,
+ false));
assertEquals("entry and exit dir opposite to RTL context",
EN + HE + EN + RLM,
RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL));
assertEquals("entry and exit dir opposite to RTL context, no isolation",
EN + HE + EN,
- RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL, false));
+ RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL,
+ false));
// Entry and exit directionality matching context, but with opposite overall directionality.
assertEquals("overall dir (but not entry or exit dir) opposite to LTR context",
@@ -166,7 +177,8 @@
LTR_FMT.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL));
assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, no isolation",
RLE + EN + HE + EN + PDF,
- LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL, false));
+ LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL,
+ false));
assertEquals("overall dir (but not entry or exit dir) opposite to RTL context",
LRE + HE + EN + HE + PDF + RLM,
@@ -176,6 +188,7 @@
RTL_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR));
assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, no isolation",
LRE + HE + EN + HE + PDF,
- RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR, false));
+ RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR,
+ false));
}
}
diff --git a/v4/tests/java/android/support/v4/view/GravityCompatTest.java b/v4/tests/java/android/support/v4/view/GravityCompatTest.java
index 2e2f180..36e557a 100644
--- a/v4/tests/java/android/support/v4/view/GravityCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/GravityCompatTest.java
@@ -17,13 +17,20 @@
import android.graphics.Rect;
import android.os.Build;
+import android.support.test.runner.AndroidJUnit4;
import android.support.v4.testutils.TestUtils;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.Gravity;
+import org.junit.Test;
+import org.junit.runner.RunWith;
-public class GravityCompatTest extends AndroidTestCase {
- @SmallTest
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GravityCompatTest {
+ @Test
public void testConstants() {
// Compat constants must match core constants since they can be OR'd with
// other core constants.
@@ -31,7 +38,7 @@
assertEquals("End constants", Gravity.END, GravityCompat.END);
}
- @SmallTest
+ @Test
public void testGetAbsoluteGravity() {
assertEquals("Left under LTR",
GravityCompat.getAbsoluteGravity(Gravity.LEFT, ViewCompat.LAYOUT_DIRECTION_LTR),
@@ -78,7 +85,7 @@
}
}
- @SmallTest
+ @Test
public void testApplyNoOffsetsLtr() {
Rect outRect = new Rect();
@@ -176,7 +183,7 @@
outRect, 100, 50, 200, 100);
}
- @SmallTest
+ @Test
public void testApplyNoOffsetsRtl() {
Rect outRect = new Rect();
diff --git a/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java b/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java
index 057db99..48d6dab 100644
--- a/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java
@@ -16,12 +16,19 @@
package android.support.v4.view;
import android.os.Build;
+import android.support.test.runner.AndroidJUnit4;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.ViewGroup;
+import org.junit.Test;
+import org.junit.runner.RunWith;
-public class MarginLayoutParamsCompatTest extends AndroidTestCase {
- @SmallTest
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MarginLayoutParamsCompatTest {
+ @Test
public void testLayoutDirection() {
ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
@@ -43,8 +50,7 @@
MarginLayoutParamsCompat.getLayoutDirection(mlp));
}
-
- @SmallTest
+ @Test
public void testMappingOldMarginsToNewMarginsLtr() {
ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
@@ -57,7 +63,7 @@
MarginLayoutParamsCompat.getMarginEnd(mlp));
}
- @SmallTest
+ @Test
public void testMappingOldMarginsToNewMarginsRtl() {
ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
@@ -79,7 +85,7 @@
}
}
- @SmallTest
+ @Test
public void testMappingNewMarginsToNewMarginsLtr() {
ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
@@ -98,7 +104,7 @@
MarginLayoutParamsCompat.getMarginStart(mlp));
}
- @SmallTest
+ @Test
public void testMappingNewMarginsToNewMarginsRtl() {
ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
@@ -123,7 +129,7 @@
MarginLayoutParamsCompat.getMarginStart(mlp));
}
- @SmallTest
+ @Test
public void testResolveMarginsLtr() {
ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
@@ -142,7 +148,7 @@
assertEquals("Keeping left margin field under LTR", 50, mlp.leftMargin);
}
- @SmallTest
+ @Test
public void testResolveMarginsRtl() {
ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
diff --git a/v4/tests/java/android/support/v4/view/ViewCompatTest.java b/v4/tests/java/android/support/v4/view/ViewCompatTest.java
index 29a0f5a..c4217a5 100644
--- a/v4/tests/java/android/support/v4/view/ViewCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/ViewCompatTest.java
@@ -15,12 +15,19 @@
*/
package android.support.v4.view;
+import android.support.test.runner.AndroidJUnit4;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
+import org.junit.Test;
+import org.junit.runner.RunWith;
-public class ViewCompatTest extends AndroidTestCase {
- @SmallTest
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ViewCompatTest {
+ @Test
public void testConstants() {
// Compat constants must match core constants since they can be used interchangeably
// in various support lib calls.
diff --git a/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
index 13a78dc..a2ac8bd 100644
--- a/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
@@ -31,10 +31,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+@MediumTest
public class ViewPropertyAnimatorCompatTest extends BaseInstrumentationTestCase<VpaActivity> {
private View mView;
- private int mVariable;
private int mNumListenerCalls = 0;
public ViewPropertyAnimatorCompatTest() {
@@ -42,13 +42,12 @@
}
@Before
- public void setUp() throws Exception {
+ public void setUp() {
final Activity activity = mActivityTestRule.getActivity();
mView = activity.findViewById(R.id.view);
}
@Test
- @MediumTest
public void testWithEndAction() throws Throwable {
final CountDownLatch latch1 = new CountDownLatch(1);
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@@ -87,7 +86,6 @@
}
@Test
- @MediumTest
public void testWithStartAction() throws Throwable {
final CountDownLatch latch1 = new CountDownLatch(1);
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
diff --git a/v4/tests/java/android/support/v4/view/VpaActivity.java b/v4/tests/java/android/support/v4/view/VpaActivity.java
index d84d62d..64d698a 100644
--- a/v4/tests/java/android/support/v4/view/VpaActivity.java
+++ b/v4/tests/java/android/support/v4/view/VpaActivity.java
@@ -16,14 +16,12 @@
package android.support.v4.view;
-import android.app.Activity;
+import android.support.v4.BaseTestActivity;
import android.support.v4.test.R;
-import android.os.Bundle;
-public class VpaActivity extends Activity {
+public class VpaActivity extends BaseTestActivity {
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.vpa_activity);
+ protected int getContentViewLayoutResId() {
+ return R.layout.vpa_activity;
}
}
diff --git a/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
index 42d82e9..db4a497 100644
--- a/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
+++ b/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
@@ -15,14 +15,8 @@
*/
package android.support.v4.widget;
-import android.os.Build;
-
-/**
- * @hide
- */
public class DonutScrollerCompatTest extends ScrollerCompatTestBase {
-
public DonutScrollerCompatTest() {
- super(Build.VERSION_CODES.DONUT);
+ super(4);
}
}
diff --git a/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
index c777808..e947749 100644
--- a/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
+++ b/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
@@ -15,11 +15,7 @@
*/
package android.support.v4.widget;
-/**
- * @hide
- */
public class GingerbreadScrollerCompatTest extends ScrollerCompatTestBase {
-
public GingerbreadScrollerCompatTest() {
super(9);
}
diff --git a/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
index acbc89d..5d8a16d 100644
--- a/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
+++ b/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
@@ -15,13 +15,7 @@
*/
package android.support.v4.widget;
-import android.os.Build;
-
-/**
- * @hide
- */
public class IcsScrollerCompatTest extends ScrollerCompatTestBase {
-
public IcsScrollerCompatTest() {
super(14);
}
diff --git a/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
index 2b83fc2..7b7c9f5 100644
--- a/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
+++ b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
@@ -16,22 +16,28 @@
package android.support.v4.widget;
import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
-/**
- * @hide
- */
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
@MediumTest
-abstract public class ScrollerCompatTestBase extends AndroidTestCase {
+public abstract class ScrollerCompatTestBase {
private static final boolean DEBUG = false;
@@ -52,9 +58,11 @@
Constructor<ScrollerCompat> constructor = ScrollerCompat.class
.getDeclaredConstructor(int.class, Context.class, Interpolator.class);
constructor.setAccessible(true);
- mScroller = constructor.newInstance(mApiLevel, getContext(), interpolator);
+ mScroller = constructor.newInstance(mApiLevel, InstrumentationRegistry.getContext(),
+ interpolator);
}
+ @Test
public void testTargetReached() throws Throwable {
if (DEBUG) {
Log.d(TAG, "testing if target is reached");
@@ -70,6 +78,7 @@
mScroller.getCurrY());
}
+ @Test
public void testAbort() throws Throwable {
if (DEBUG) {
Log.d(TAG, "testing abort");
diff --git a/v4/tests/java/android/support/v4/widget/TestActivity.java b/v4/tests/java/android/support/v4/widget/TestActivity.java
deleted file mode 100644
index 9ab5188..0000000
--- a/v4/tests/java/android/support/v4/widget/TestActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2015 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.widget;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-
-public class TestActivity extends Activity {
- FrameLayout mContainer;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mContainer = new FrameLayout(this);
-
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- setContentView(mContainer);
- }
-}
diff --git a/v7/appcompat/api/current.txt b/v7/appcompat/api/current.txt
index 1d1671f..5134178 100644
--- a/v7/appcompat/api/current.txt
+++ b/v7/appcompat/api/current.txt
@@ -378,6 +378,7 @@
field public static int alertDialogStyle;
field public static int alertDialogTheme;
field public static int allowStacking;
+ field public static int alpha;
field public static int arrowHeadLength;
field public static int arrowShaftLength;
field public static int autoCompleteTextViewStyle;
@@ -393,6 +394,7 @@
field public static int buttonBarNeutralButtonStyle;
field public static int buttonBarPositiveButtonStyle;
field public static int buttonBarStyle;
+ field public static int buttonGravity;
field public static int buttonPanelSideLayout;
field public static int buttonStyle;
field public static int buttonStyleSmall;
@@ -455,6 +457,7 @@
field public static int listDividerAlertDialog;
field public static int listItemLayout;
field public static int listLayout;
+ field public static int listMenuViewStyle;
field public static int listPopupWindowStyle;
field public static int listPreferredItemHeight;
field public static int listPreferredItemHeightLarge;
@@ -503,6 +506,7 @@
field public static int splitTrack;
field public static int srcCompat;
field public static int state_above_anchor;
+ field public static int subMenuArrow;
field public static int submitBackground;
field public static int subtitle;
field public static int subtitleTextAppearance;
@@ -517,6 +521,7 @@
field public static int textAppearanceLargePopupMenu;
field public static int textAppearanceListItem;
field public static int textAppearanceListItemSmall;
+ field public static int textAppearancePopupMenuHeader;
field public static int textAppearanceSearchResultSubtitle;
field public static int textAppearanceSearchResultTitle;
field public static int textAppearanceSmallPopupMenu;
@@ -526,11 +531,12 @@
field public static int thickness;
field public static int thumbTextPadding;
field public static int title;
+ field public static int titleMargin;
field public static int titleMarginBottom;
field public static int titleMarginEnd;
field public static int titleMarginStart;
field public static int titleMarginTop;
- field public static int titleMargins;
+ field public static deprecated int titleMargins;
field public static int titleTextAppearance;
field public static int titleTextColor;
field public static int titleTextStyle;
@@ -553,12 +559,10 @@
public static final class R.bool {
ctor public R.bool();
field public static int abc_action_bar_embed_tabs;
- field public static int abc_action_bar_embed_tabs_pre_jb;
- field public static int abc_action_bar_expanded_action_views_exclusive;
field public static int abc_allow_stacked_button_bar;
field public static int abc_config_actionMenuItemAllCaps;
- field public static int abc_config_allowActionMenuItemTextWithIcon;
field public static int abc_config_closeDialogWhenTouchOutside;
+ field public static int abc_config_enableCascadingSubmenus;
field public static int abc_config_showMenuShortcutsWhenKeyboardPresent;
}
@@ -566,6 +570,7 @@
ctor public R.color();
field public static int abc_background_cache_hint_selector_material_dark;
field public static int abc_background_cache_hint_selector_material_light;
+ field public static int abc_btn_colored_borderless_text_material;
field public static int abc_color_highlight_material;
field public static int abc_input_method_navigation_guard;
field public static int abc_primary_text_disable_only_material_dark;
@@ -578,6 +583,13 @@
field public static int abc_search_url_text_selected;
field public static int abc_secondary_text_material_dark;
field public static int abc_secondary_text_material_light;
+ field public static int abc_tint_btn_checkable;
+ field public static int abc_tint_default;
+ field public static int abc_tint_edittext;
+ field public static int abc_tint_seek_thumb;
+ field public static int abc_tint_spinner;
+ field public static int abc_tint_switch_thumb;
+ field public static int abc_tint_switch_track;
field public static int accent_material_dark;
field public static int accent_material_light;
field public static int background_floating_material_dark;
@@ -683,7 +695,6 @@
field public static int abc_list_item_padding_horizontal_material;
field public static int abc_panel_menu_list_width;
field public static int abc_search_view_preferred_width;
- field public static int abc_search_view_text_min_width;
field public static int abc_seekbar_track_background_height_material;
field public static int abc_seekbar_track_progress_height_material;
field public static int abc_select_dialog_padding_start_material;
@@ -699,6 +710,7 @@
field public static int abc_text_size_headline_material;
field public static int abc_text_size_large_material;
field public static int abc_text_size_medium_material;
+ field public static int abc_text_size_menu_header_material;
field public static int abc_text_size_menu_material;
field public static int abc_text_size_small_material;
field public static int abc_text_size_subhead_material;
@@ -740,6 +752,7 @@
field public static int abc_dialog_material_background_light;
field public static int abc_edit_text_material;
field public static int abc_ic_ab_back_material;
+ field public static int abc_ic_arrow_drop_right_black_24dp;
field public static int abc_ic_clear_material;
field public static int abc_ic_commit_search_api_mtrl_alpha;
field public static int abc_ic_go_search_api_material;
@@ -816,6 +829,7 @@
field public static int alertTitle;
field public static int always;
field public static int beginning;
+ field public static int bottom;
field public static int buttonPanel;
field public static int cancel_action;
field public static int checkbox;
@@ -877,6 +891,7 @@
field public static int src_in;
field public static int src_over;
field public static int status_bar_latest_event_content;
+ field public static int submenuarrow;
field public static int submit_area;
field public static int tabMode;
field public static int text;
@@ -885,6 +900,7 @@
field public static int time;
field public static int title;
field public static int title_template;
+ field public static int top;
field public static int topPanel;
field public static int up;
field public static int useLogo;
@@ -896,7 +912,6 @@
ctor public R.integer();
field public static int abc_config_activityDefaultDur;
field public static int abc_config_activityShortDur;
- field public static int abc_max_action_buttons;
field public static int cancel_button_image_alpha;
field public static int status_bar_notification_info_maxnum;
}
@@ -920,6 +935,7 @@
field public static int abc_list_menu_item_icon;
field public static int abc_list_menu_item_layout;
field public static int abc_list_menu_item_radio;
+ field public static int abc_popup_menu_header_item_layout;
field public static int abc_popup_menu_item_layout;
field public static int abc_screen_content_include;
field public static int abc_screen_simple;
@@ -954,6 +970,18 @@
field public static int abc_activitychooserview_choose_application;
field public static int abc_capital_off;
field public static int abc_capital_on;
+ field public static int abc_font_family_body_1_material;
+ field public static int abc_font_family_body_2_material;
+ field public static int abc_font_family_button_material;
+ field public static int abc_font_family_caption_material;
+ field public static int abc_font_family_display_1_material;
+ field public static int abc_font_family_display_2_material;
+ field public static int abc_font_family_display_3_material;
+ field public static int abc_font_family_display_4_material;
+ field public static int abc_font_family_headline_material;
+ field public static int abc_font_family_menu_material;
+ field public static int abc_font_family_subhead_material;
+ field public static int abc_font_family_title_material;
field public static int abc_search_hint;
field public static int abc_searchview_description_clear;
field public static int abc_searchview_description_query;
@@ -1015,6 +1043,7 @@
field public static int Base_TextAppearance_AppCompat_Widget_Button;
field public static int Base_TextAppearance_AppCompat_Widget_Button_Inverse;
field public static int Base_TextAppearance_AppCompat_Widget_DropDownItem;
+ field public static int Base_TextAppearance_AppCompat_Widget_PopupMenu_Header;
field public static int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large;
field public static int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small;
field public static int Base_TextAppearance_AppCompat_Widget_Switch;
@@ -1094,6 +1123,7 @@
field public static int Base_Widget_AppCompat_Light_ActionBar_TabView;
field public static int Base_Widget_AppCompat_Light_PopupMenu;
field public static int Base_Widget_AppCompat_Light_PopupMenu_Overflow;
+ field public static int Base_Widget_AppCompat_ListMenuView;
field public static int Base_Widget_AppCompat_ListPopupWindow;
field public static int Base_Widget_AppCompat_ListView;
field public static int Base_Widget_AppCompat_ListView_DropDown;
@@ -1178,6 +1208,7 @@
field public static int TextAppearance_AppCompat_Widget_Button;
field public static int TextAppearance_AppCompat_Widget_Button_Inverse;
field public static int TextAppearance_AppCompat_Widget_DropDownItem;
+ field public static int TextAppearance_AppCompat_Widget_PopupMenu_Header;
field public static int TextAppearance_AppCompat_Widget_PopupMenu_Large;
field public static int TextAppearance_AppCompat_Widget_PopupMenu_Small;
field public static int TextAppearance_AppCompat_Widget_Switch;
@@ -1264,6 +1295,7 @@
field public static int Widget_AppCompat_Light_PopupMenu_Overflow;
field public static int Widget_AppCompat_Light_SearchView;
field public static int Widget_AppCompat_Light_Spinner_DropDown_ActionBar;
+ field public static int Widget_AppCompat_ListMenuView;
field public static int Widget_AppCompat_ListPopupWindow;
field public static int Widget_AppCompat_ListView;
field public static int Widget_AppCompat_ListView_DropDown;
@@ -1417,6 +1449,7 @@
field public static int AppCompatTheme_imageButtonStyle;
field public static int AppCompatTheme_listChoiceBackgroundIndicator;
field public static int AppCompatTheme_listDividerAlertDialog;
+ field public static int AppCompatTheme_listMenuViewStyle;
field public static int AppCompatTheme_listPopupWindowStyle;
field public static int AppCompatTheme_listPreferredItemHeight;
field public static int AppCompatTheme_listPreferredItemHeightLarge;
@@ -1442,6 +1475,7 @@
field public static int AppCompatTheme_textAppearanceLargePopupMenu;
field public static int AppCompatTheme_textAppearanceListItem;
field public static int AppCompatTheme_textAppearanceListItemSmall;
+ field public static int AppCompatTheme_textAppearancePopupMenuHeader;
field public static int AppCompatTheme_textAppearanceSearchResultSubtitle;
field public static int AppCompatTheme_textAppearanceSearchResultTitle;
field public static int AppCompatTheme_textAppearanceSmallPopupMenu;
@@ -1460,6 +1494,10 @@
field public static int AppCompatTheme_windowMinWidthMinor;
field public static int AppCompatTheme_windowNoTitle;
field public static int ButtonBarLayout_allowStacking;
+ field public static final int[] ColorStateListItem;
+ field public static int ColorStateListItem_alpha;
+ field public static int ColorStateListItem_android_alpha;
+ field public static int ColorStateListItem_android_color;
field public static final int[] CompoundButton;
field public static int CompoundButton_android_button;
field public static int CompoundButton_buttonTint;
@@ -1525,6 +1563,7 @@
field public static int MenuView_android_verticalDivider;
field public static int MenuView_android_windowAnimationStyle;
field public static int MenuView_preserveIconSpacing;
+ field public static int MenuView_subMenuArrow;
field public static final int[] PopupWindow;
field public static final int[] PopupWindowBackgroundState;
field public static int PopupWindowBackgroundState_state_above_anchor;
@@ -1575,9 +1614,10 @@
field public static int TextAppearance_android_textStyle;
field public static int TextAppearance_android_typeface;
field public static int TextAppearance_textAllCaps;
- field public static final int[] Toolbar;
+ field public static final deprecated int[] Toolbar;
field public static int Toolbar_android_gravity;
field public static int Toolbar_android_minHeight;
+ field public static int Toolbar_buttonGravity;
field public static int Toolbar_collapseContentDescription;
field public static int Toolbar_collapseIcon;
field public static int Toolbar_contentInsetEnd;
@@ -1594,11 +1634,12 @@
field public static int Toolbar_subtitleTextAppearance;
field public static int Toolbar_subtitleTextColor;
field public static int Toolbar_title;
+ field public static int Toolbar_titleMargin;
field public static int Toolbar_titleMarginBottom;
field public static int Toolbar_titleMarginEnd;
field public static int Toolbar_titleMarginStart;
field public static int Toolbar_titleMarginTop;
- field public static int Toolbar_titleMargins;
+ field public static deprecated int Toolbar_titleMargins;
field public static int Toolbar_titleTextAppearance;
field public static int Toolbar_titleTextColor;
field public static final int[] View;
@@ -2060,6 +2101,10 @@
method public int getPopupTheme();
method public java.lang.CharSequence getSubtitle();
method public java.lang.CharSequence getTitle();
+ method public int getTitleMarginBottom();
+ method public int getTitleMarginEnd();
+ method public int getTitleMarginStart();
+ method public int getTitleMarginTop();
method public boolean hasExpandedActionView();
method public boolean hideOverflowMenu();
method public void inflateMenu(int);
@@ -2085,6 +2130,11 @@
method public void setSubtitleTextColor(int);
method public void setTitle(int);
method public void setTitle(java.lang.CharSequence);
+ method public void setTitleMargin(int, int, int, int);
+ method public void setTitleMarginBottom(int);
+ method public void setTitleMarginEnd(int);
+ method public void setTitleMarginStart(int);
+ method public void setTitleMarginTop(int);
method public void setTitleTextAppearance(android.content.Context, int);
method public void setTitleTextColor(int);
method public boolean showOverflowMenu();
diff --git a/v7/appcompat/res-public/values/public_attrs.xml b/v7/appcompat/res-public/values/public_attrs.xml
index fdb7ff5..3a7fb48 100644
--- a/v7/appcompat/res-public/values/public_attrs.xml
+++ b/v7/appcompat/res-public/values/public_attrs.xml
@@ -50,6 +50,7 @@
<public type="attr" name="actionViewClass"/>
<public type="attr" name="alertDialogStyle"/>
<public type="attr" name="alertDialogTheme"/>
+ <public type="attr" name="alpha"/>
<public type="attr" name="arrowHeadLength"/>
<public type="attr" name="arrowShaftLength"/>
<public type="attr" name="autoCompleteTextViewStyle"/>
@@ -65,6 +66,7 @@
<public type="attr" name="buttonBarNeutralButtonStyle"/>
<public type="attr" name="buttonBarPositiveButtonStyle"/>
<public type="attr" name="buttonBarStyle"/>
+ <public type="attr" name="buttonGravity" />
<public type="attr" name="buttonStyle"/>
<public type="attr" name="buttonStyleSmall"/>
<public type="attr" name="buttonTint"/>
@@ -126,6 +128,7 @@
<public type="attr" name="listPreferredItemPaddingRight"/>
<public type="attr" name="logo"/>
<public type="attr" name="logoDescription"/>
+ <public type="attr" name="maxButtonHeight" />
<public type="attr" name="measureWithLargestChild"/>
<public type="attr" name="middleBarArrowSize"/>
<public type="attr" name="navigationContentDescription"/>
@@ -174,6 +177,7 @@
<public type="attr" name="textAppearanceLargePopupMenu"/>
<public type="attr" name="textAppearanceListItem"/>
<public type="attr" name="textAppearanceListItemSmall"/>
+ <public type="attr" name="textAppearancePopupMenuHeader"/>
<public type="attr" name="textAppearanceSearchResultSubtitle"/>
<public type="attr" name="textAppearanceSearchResultTitle"/>
<public type="attr" name="textAppearanceSmallPopupMenu"/>
@@ -184,6 +188,7 @@
<public type="attr" name="title"/>
<public type="attr" name="titleMarginBottom"/>
<public type="attr" name="titleMarginEnd"/>
+ <public type="attr" name="titleMargin"/>
<public type="attr" name="titleMargins"/>
<public type="attr" name="titleMarginStart"/>
<public type="attr" name="titleMarginTop"/>
diff --git a/v7/appcompat/res-public/values/public_styles.xml b/v7/appcompat/res-public/values/public_styles.xml
index aba9c8b..d4f23db 100644
--- a/v7/appcompat/res-public/values/public_styles.xml
+++ b/v7/appcompat/res-public/values/public_styles.xml
@@ -56,6 +56,7 @@
<public type="style" name="TextAppearance.AppCompat.Widget.Button"/>
<public type="style" name="TextAppearance.AppCompat.Widget.Button.Inverse"/>
<public type="style" name="TextAppearance.AppCompat.Widget.DropDownItem"/>
+ <public type="style" name="TextAppearance.AppCompat.Widget.PopupMenu.Header"/>
<public type="style" name="TextAppearance.AppCompat.Widget.PopupMenu.Large"/>
<public type="style" name="TextAppearance.AppCompat.Widget.PopupMenu.Small"/>
<public type="style" name="TextAppearance.AppCompat.Widget.Switch"/>
diff --git a/v7/appcompat/res/values-w480dp/config.xml b/v7/appcompat/res/color/abc_btn_colored_borderless_text_material.xml
similarity index 60%
copy from v7/appcompat/res/values-w480dp/config.xml
copy to v7/appcompat/res/color/abc_btn_colored_borderless_text_material.xml
index e95b6ff..f4a93b7 100644
--- a/v7/appcompat/res/values-w480dp/config.xml
+++ b/v7/appcompat/res/color/abc_btn_colored_borderless_text_material.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
+<!-- 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.
@@ -13,6 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
- <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-</resources>
+
+<!-- Used for the text of a borderless colored button. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="?android:attr/textColorSecondary" />
+ <item android:color="?attr/colorAccent"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_btn_checkable.xml b/v7/appcompat/res/color/abc_tint_btn_checkable.xml
new file mode 100644
index 0000000..0c663f6
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_btn_checkable.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal" app:alpha="?android:disabledAlpha"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+ <item android:color="?attr/colorControlNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_default.xml b/v7/appcompat/res/color/abc_tint_default.xml
new file mode 100644
index 0000000..8d7c391
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_default.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal" app:alpha="?android:disabledAlpha"/>
+ <item android:state_focused="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_pressed="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_activated="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_selected="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+ <item android:color="?attr/colorControlNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_edittext.xml b/v7/appcompat/res/color/abc_tint_edittext.xml
new file mode 100644
index 0000000..536d77f
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_edittext.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal"
+ app:alpha="?android:disabledAlpha"/>
+ <item android:state_pressed="false" android:state_focused="false"
+ android:color="?attr/colorControlNormal"/>
+ <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-w480dp/config.xml b/v7/appcompat/res/color/abc_tint_seek_thumb.xml
similarity index 61%
rename from v7/appcompat/res/values-w480dp/config.xml
rename to v7/appcompat/res/color/abc_tint_seek_thumb.xml
index e95b6ff..cb53788 100644
--- a/v7/appcompat/res/values-w480dp/config.xml
+++ b/v7/appcompat/res/color/abc_tint_seek_thumb.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
+<!--
+ Copyright (C) 2015 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.
@@ -13,6 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
- <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-</resources>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorControlActivated" app:alpha="?android:attr/disabledAlpha"/>
+ <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_spinner.xml b/v7/appcompat/res/color/abc_tint_spinner.xml
new file mode 100644
index 0000000..44333dd
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_spinner.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal" app:alpha="?android:disabledAlpha"/>
+ <item android:state_pressed="false" android:state_focused="false" android:color="?attr/colorControlNormal"/>
+ <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_switch_thumb.xml b/v7/appcompat/res/color/abc_tint_switch_thumb.xml
new file mode 100644
index 0000000..fc8bd24
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_switch_thumb.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorSwitchThumbNormal" app:alpha="?android:attr/disabledAlpha"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+ <item android:color="?attr/colorSwitchThumbNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_switch_track.xml b/v7/appcompat/res/color/abc_tint_switch_track.xml
new file mode 100644
index 0000000..22322f8
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_switch_track.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?android:attr/colorForeground" app:alpha="0.1"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated" app:alpha="0.3"/>
+ <item android:color="?android:attr/colorForeground" app:alpha="0.3"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_ic_arrow_drop_right_black_24dp.xml b/v7/appcompat/res/drawable/abc_ic_arrow_drop_right_black_24dp.xml
new file mode 100644
index 0000000..68547eb
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_arrow_drop_right_black_24dp.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:width="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:autoMirrored="true">
+
+ <group
+ android:name="arrow"
+ android:rotation="90.0"
+ android:pivotX="12.0"
+ android:pivotY="12.0">
+ <path android:fillColor="@android:color/black" android:pathData="M7,14 L12,9 L17,14 L7,14 Z" />
+ <path android:pathData="M0,0 L24,0 L24,24 L0,24 L0,0 Z" />
+ </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
index d32ad10..08adfd1 100644
--- a/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
+++ b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
@@ -28,7 +28,6 @@
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:gravity="bottom"
- app:allowStacking="@bool/abc_allow_stacked_button_bar"
style="?attr/buttonBarStyle">
<Button
diff --git a/v7/appcompat/res/layout/abc_popup_menu_header_item_layout.xml b/v7/appcompat/res/layout/abc_popup_menu_header_item_layout.xml
new file mode 100644
index 0000000..a40b6dd
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_popup_menu_header_item_layout.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/dropdownListPreferredItemHeight"
+ android:minWidth="196dip"
+ android:paddingLeft="16dip"
+ android:paddingRight="16dip">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/textAppearancePopupMenuHeader"
+ android:layout_gravity="center_vertical"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:textAlignment="viewStart" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml b/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
index 47125fe..bf630ff 100644
--- a/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
+++ b/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
@@ -56,6 +56,16 @@
</RelativeLayout>
+ <ImageView
+ android:id="@+id/submenuarrow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="8dp"
+ android:layout_marginLeft="8dp"
+ android:scaleType="center"
+ android:visibility="gone" />
+
<!-- Checkbox, and/or radio button will be inserted here. -->
</android.support.v7.view.menu.ListMenuItemView>
diff --git a/v7/appcompat/res/layout/abc_search_view.xml b/v7/appcompat/res/layout/abc_search_view.xml
index a7446e3..b496f5d 100644
--- a/v7/appcompat/res/layout/abc_search_view.xml
+++ b/v7/appcompat/res/layout/abc_search_view.xml
@@ -80,7 +80,6 @@
android:layout_height="36dip"
android:layout_width="0dp"
android:layout_weight="1"
- android:minWidth="@dimen/abc_search_view_text_min_width"
android:layout_gravity="bottom"
android:paddingLeft="@dimen/abc_dropdownitem_text_padding_left"
android:paddingRight="@dimen/abc_dropdownitem_text_padding_right"
diff --git a/v7/appcompat/res/values-b+sr+Latn/strings.xml b/v7/appcompat/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..fc92231
--- /dev/null
+++ b/v7/appcompat/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="abc_action_mode_done" msgid="4076576682505996667">"Gotovo"</string>
+ <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Odlazak na Početnu"</string>
+ <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Kretanje nagore"</string>
+ <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Još opcija"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Skupi"</string>
+ <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
+ <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
+ <string name="abc_searchview_description_search" msgid="8264924765203268293">"Pretraga"</string>
+ <string name="abc_search_hint" msgid="7723749260725869598">"Pretražite..."</string>
+ <string name="abc_searchview_description_query" msgid="2550479030709304392">"Upit za pretragu"</string>
+ <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Brisanje upita"</string>
+ <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Slanje upita"</string>
+ <string name="abc_searchview_description_voice" msgid="893419373245838918">"Glasovna pretraga"</string>
+ <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Izbor aplikacije"</string>
+ <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Prikaži sve"</string>
+ <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Deli sa aplikacijom %s"</string>
+ <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Deli sa"</string>
+ <string name="status_bar_notification_info_overflow" msgid="2869576371154716097">">999"</string>
+ <string name="abc_capital_on" msgid="3405795526292276155">"UKLJUČI"</string>
+ <string name="abc_capital_off" msgid="121134116657445385">"ISKLJUČI"</string>
+</resources>
diff --git a/v7/appcompat/res/values-bn-rBD/strings.xml b/v7/appcompat/res/values-bn-rBD/strings.xml
index 6be164f..07c4b54 100644
--- a/v7/appcompat/res/values-bn-rBD/strings.xml
+++ b/v7/appcompat/res/values-bn-rBD/strings.xml
@@ -31,8 +31,8 @@
<string name="abc_searchview_description_voice" msgid="893419373245838918">"ভয়েস অনুসন্ধান"</string>
<string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"একটি অ্যাপ্লিকেশান চয়ন করুন"</string>
<string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"সবগুলো দেখুন"</string>
- <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s এর সাথে ভাগ করুন"</string>
- <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"এর সাথে ভাগ করুন"</string>
+ <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s এর সাথে শেয়ার করুন"</string>
+ <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"এর সাথে শেয়ার করুন"</string>
<string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"৯৯৯+"</string>
<string name="abc_capital_on" msgid="3405795526292276155">"চালু"</string>
<string name="abc_capital_off" msgid="121134116657445385">"বন্ধ"</string>
diff --git a/v7/appcompat/res/values-land/bools.xml b/v7/appcompat/res/values-land/bools.xml
deleted file mode 100644
index 7d1a1af..0000000
--- a/v7/appcompat/res/values-land/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-land/config.xml b/v7/appcompat/res/values-land/config.xml
deleted file mode 100644
index d0d990d..0000000
--- a/v7/appcompat/res/values-land/config.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<resources>
- <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-large/bools.xml b/v7/appcompat/res/values-large/bools.xml
deleted file mode 100644
index 7d1a1af..0000000
--- a/v7/appcompat/res/values-large/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-large/config.xml b/v7/appcompat/res/values-large/config.xml
index c4f04a3..58e34a0 100644
--- a/v7/appcompat/res/values-large/config.xml
+++ b/v7/appcompat/res/values-large/config.xml
@@ -20,11 +20,6 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
- <!-- Whether action menu items should obey the "withText" showAsAction.
- This may be set to false for situations where space is
- extremely limited. -->
- <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-
<!-- see comment in values/config.xml -->
<dimen name="abc_config_prefDialogWidth">440dp</dimen>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/v7/appcompat/res/values-large/dimens.xml b/v7/appcompat/res/values-large/dimens.xml
index 16bb4f6..5afdda4 100644
--- a/v7/appcompat/res/values-large/dimens.xml
+++ b/v7/appcompat/res/values-large/dimens.xml
@@ -15,13 +15,6 @@
-->
<resources>
- <!-- Minimum width of the search view text entry area. -->
- <dimen name="abc_search_view_text_min_width">192dip</dimen>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">4</integer>
-
<item type="dimen" name="abc_dialog_fixed_width_major">60%</item>
<item type="dimen" name="abc_dialog_fixed_width_minor">90%</item>
<item type="dimen" name="abc_dialog_fixed_height_major">60%</item>
diff --git a/v7/appcompat/res/values-sw600dp/dimens.xml b/v7/appcompat/res/values-sw600dp/dimens.xml
index a4bc455..a83abb1 100644
--- a/v7/appcompat/res/values-sw600dp/dimens.xml
+++ b/v7/appcompat/res/values-sw600dp/dimens.xml
@@ -15,12 +15,6 @@
-->
<resources>
-
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">5</integer>
-
<!-- Use the default title sizes on tablets. -->
<dimen name="abc_text_size_title_material_toolbar">20dp</dimen>
<!-- Use the default subtitle sizes on tablets. -->
diff --git a/v7/appcompat/res/values-sw720dp/config.xml b/v7/appcompat/res/values-sw720dp/config.xml
new file mode 100644
index 0000000..f36adba
--- /dev/null
+++ b/v7/appcompat/res/values-sw720dp/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources>
+ <!-- Enable cascading submenus. -->
+ <bool name="abc_config_enableCascadingSubmenus">true</bool>
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-tr/strings.xml b/v7/appcompat/res/values-tr/strings.xml
index 56aecf6..185cd4d 100644
--- a/v7/appcompat/res/values-tr/strings.xml
+++ b/v7/appcompat/res/values-tr/strings.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="abc_action_mode_done" msgid="4076576682505996667">"Tamamlandı"</string>
+ <string name="abc_action_mode_done" msgid="4076576682505996667">"Bitti"</string>
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ana ekrana git"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Yukarı git"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Diğer seçenekler"</string>
diff --git a/v7/appcompat/res/values-v21/styles_base.xml b/v7/appcompat/res/values-v21/styles_base.xml
index 8e13da4..d31f6f9 100644
--- a/v7/appcompat/res/values-v21/styles_base.xml
+++ b/v7/appcompat/res/values-v21/styles_base.xml
@@ -152,6 +152,12 @@
parent="android:TextAppearance.Material.Widget.PopupMenu.Small">
</style>
+ <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Header" parent="TextAppearance.AppCompat">
+ <item name="android:fontFamily">@string/abc_font_family_title_material</item>
+ <item name="android:textSize">@dimen/abc_text_size_menu_header_material</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ </style>
+
<!-- Search View result styles -->
<style name="Base.TextAppearance.AppCompat.SearchResult.Title"
@@ -180,7 +186,9 @@
<style name="Base.Widget.AppCompat.Button.Borderless" parent="android:Widget.Material.Button.Borderless" />
- <style name="Base.Widget.AppCompat.Button.Borderless.Colored" parent="android:Widget.Material.Button.Borderless.Colored" />
+ <style name="Base.Widget.AppCompat.Button.Borderless.Colored" parent="android:Widget.Material.Button.Borderless.Colored">
+ <item name="android:textColor">@color/abc_btn_colored_borderless_text_material</item>
+ </style>
<style name="Base.Widget.AppCompat.ButtonBar" parent="android:Widget.Material.ButtonBar" />
diff --git a/v7/appcompat/res/values-v23/styles_base.xml b/v7/appcompat/res/values-v23/styles_base.xml
index 56563c7..e6be6b9 100644
--- a/v7/appcompat/res/values-v23/styles_base.xml
+++ b/v7/appcompat/res/values-v23/styles_base.xml
@@ -17,6 +17,8 @@
<resources>
+ <style name="Base.Widget.AppCompat.Button.Borderless.Colored" parent="android:Widget.Material.Button.Borderless.Colored" />
+
<style name="Base.Widget.AppCompat.Button.Colored" parent="android:Widget.Material.Button.Colored" />
<style name="Base.Widget.AppCompat.RatingBar.Indicator" parent="android:Widget.Material.RatingBar.Indicator" />
diff --git a/v7/appcompat/res/values-w360dp/dimens.xml b/v7/appcompat/res/values-w360dp/dimens.xml
deleted file mode 100644
index e5b2456..0000000
--- a/v7/appcompat/res/values-w360dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">3</integer>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-w480dp/bools.xml b/v7/appcompat/res/values-w480dp/bools.xml
deleted file mode 100644
index 470f89b..0000000
--- a/v7/appcompat/res/values-w480dp/bools.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<resources>
- <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-w500dp/dimens.xml b/v7/appcompat/res/values-w500dp/dimens.xml
deleted file mode 100644
index dd6458b..0000000
--- a/v7/appcompat/res/values-w500dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">4</integer>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-w600dp/dimens.xml b/v7/appcompat/res/values-w600dp/dimens.xml
deleted file mode 100644
index 252ba6a..0000000
--- a/v7/appcompat/res/values-w600dp/dimens.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">5</integer>
-
- <!-- Minimum width of the search view text entry area. -->
- <dimen name="abc_search_view_text_min_width">192dip</dimen>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-w720dp/bools.xml b/v7/appcompat/res/values-w720dp/bools.xml
deleted file mode 100644
index 05c5aab..0000000
--- a/v7/appcompat/res/values-w720dp/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <bool name="abc_action_bar_expanded_action_views_exclusive">false</bool>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-xlarge-land/dimens.xml b/v7/appcompat/res/values-xlarge-land/dimens.xml
deleted file mode 100644
index dea6c74..0000000
--- a/v7/appcompat/res/values-xlarge-land/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
-
- <!-- Minimum width of the search view text entry area. -->
- <dimen name="abc_search_view_text_min_width">256dip</dimen>
-
-</resources>
diff --git a/v7/appcompat/res/values-xlarge/bools.xml b/v7/appcompat/res/values-xlarge/bools.xml
deleted file mode 100644
index 05c5aab..0000000
--- a/v7/appcompat/res/values-xlarge/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <bool name="abc_action_bar_expanded_action_views_exclusive">false</bool>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-xlarge/dimens.xml b/v7/appcompat/res/values-xlarge/dimens.xml
index 0dd244a..f0d560d 100644
--- a/v7/appcompat/res/values-xlarge/dimens.xml
+++ b/v7/appcompat/res/values-xlarge/dimens.xml
@@ -15,15 +15,6 @@
-->
<resources>
-
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">5</integer>
-
- <!-- Minimum width of the search view text entry area. -->
- <dimen name="abc_search_view_text_min_width">192dip</dimen>
-
<item type="dimen" name="abc_dialog_fixed_width_major">50%</item>
<item type="dimen" name="abc_dialog_fixed_width_minor">70%</item>
<item type="dimen" name="abc_dialog_fixed_height_major">60%</item>
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index 6553b6e..73fa8df 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -174,6 +174,8 @@
<attr name="textAppearanceLargePopupMenu" format="reference"/>
<!-- Text color, typeface, size, and style for small text inside of a popup menu. -->
<attr name="textAppearanceSmallPopupMenu" format="reference"/>
+ <!-- Text color, typeface, size, and style for header text inside of a popup menu. -->
+ <attr name="textAppearancePopupMenuHeader" format="reference" />
<!-- =================== -->
@@ -385,6 +387,8 @@
<!-- Default style for the Switch widget. -->
<attr name="switchStyle" format="reference" />
+ <!-- Default menu-style ListView style. -->
+ <attr name="listMenuViewStyle" format="reference" />
</declare-styleable>
@@ -560,6 +564,8 @@
<attr name="android:itemIconDisabledAlpha"/>
<!-- Whether space should be reserved in layout when an icon is missing. -->
<attr name="preserveIconSpacing" format="boolean" />
+ <!-- Drawable for the arrow icon indicating a particular item is a submenu. -->
+ <attr name="subMenuArrow" format="reference" />
</declare-styleable>
<declare-styleable name="ActionMenuView">
<!-- Size of padding on either end of a divider. -->
@@ -801,17 +807,39 @@
<attr name="title" />
<attr name="subtitle" />
<attr name="android:gravity" />
- <attr name="titleMargins" format="dimension" />
+ <!-- Specifies extra space on the left, start, right and end sides
+ of the toolbar's title. Margin values should be positive. -->
+ <attr name="titleMargin" format="dimension" />
+ <!-- Specifies extra space on the start side of the toolbar's title.
+ If both this attribute and titleMargin are specified, then this
+ attribute takes precedence. Margin values should be positive. -->
<attr name="titleMarginStart" format="dimension" />
+ <!-- Specifies extra space on the end side of the toolbar's title.
+ If both this attribute and titleMargin are specified, then this
+ attribute takes precedence. Margin values should be positive. -->
<attr name="titleMarginEnd" format="dimension" />
+ <!-- Specifies extra space on the top side of the toolbar's title.
+ If both this attribute and titleMargin are specified, then this
+ attribute takes precedence. Margin values should be positive. -->
<attr name="titleMarginTop" format="dimension" />
+ <!-- Specifies extra space on the bottom side of the toolbar's title.
+ If both this attribute and titleMargin are specified, then this
+ attribute takes precedence. Margin values should be positive. -->
<attr name="titleMarginBottom" format="dimension" />
+ <!-- {@deprecated Use titleMargin} -->
+ <attr name="titleMargins" format="dimension" />
<attr name="contentInsetStart" />
<attr name="contentInsetEnd" />
<attr name="contentInsetLeft" />
<attr name="contentInsetRight" />
<attr name="maxButtonHeight" format="dimension" />
-
+ <attr name="buttonGravity">
+ <!-- Push object to the top of its container, not changing its size. -->
+ <flag name="top" value="0x30" />
+ <!-- Push object to the bottom of its container, not changing its size. -->
+ <flag name="bottom" value="0x50" />
+ </attr>
+ <!-- Icon drawable to use for the collapse button. -->
<attr name="collapseIcon" format="reference" />
<!-- Text to set as the content description for the collapse button. -->
<attr name="collapseContentDescription" format="string" />
@@ -824,10 +852,6 @@
<!-- Text to set as the content description for the navigation button
located at the start of the toolbar. -->
<attr name="navigationContentDescription" format="string" />
-
- <!-- Allows us to read in the minHeight attr pre-v16 -->
- <attr name="android:minHeight" />
-
<!-- Drawable to set as the logo that appears at the starting side of
the Toolbar, just after the navigation button. -->
<attr name="logo" />
@@ -838,6 +862,7 @@
<attr name="titleTextColor" format="color" />
<!-- A color to apply to the subtitle string. -->
<attr name="subtitleTextColor" format="color" />
+ <attr name="android:minHeight" />
</declare-styleable>
<declare-styleable name="PopupWindowBackgroundState">
@@ -965,6 +990,15 @@
<attr name="allowStacking" format="boolean" />
</declare-styleable>
+ <!-- Attributes that can be assigned to a ColorStateList item. -->
+ <declare-styleable name="ColorStateListItem">
+ <!-- Base color for this state. -->
+ <attr name="android:color" />
+ <!-- Alpha multiplier applied to the base color. -->
+ <attr name="alpha" format="float" />
+ <attr name="android:alpha"/>
+ </declare-styleable>
+
<declare-styleable name="AppCompatImageView">
<attr name="android:src"/>
<!-- TODO -->
diff --git a/v7/appcompat/res/values/bools.xml b/v7/appcompat/res/values/bools.xml
index 33d4948..825ece0 100644
--- a/v7/appcompat/res/values/bools.xml
+++ b/v7/appcompat/res/values/bools.xml
@@ -17,8 +17,6 @@
<resources>
<bool name="abc_action_bar_embed_tabs">true</bool>
- <bool name="abc_action_bar_embed_tabs_pre_jb">false</bool>
- <bool name="abc_action_bar_expanded_action_views_exclusive">true</bool>
<bool name="abc_config_showMenuShortcutsWhenKeyboardPresent">false</bool>
diff --git a/v7/appcompat/res/values/config.xml b/v7/appcompat/res/values/config.xml
index e0c521b..e682eb1 100644
--- a/v7/appcompat/res/values/config.xml
+++ b/v7/appcompat/res/values/config.xml
@@ -17,11 +17,6 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
- <!-- Whether action menu items should obey the "withText" showAsAction
- flag. This may be set to false for situations where space is
- extremely limited. -->
- <bool name="abc_config_allowActionMenuItemTextWithIcon">false</bool>
-
<!-- The maximum width we would prefer dialogs to be. 0 if there is no
maximum (let them grow as large as the screen). Actual values are
specified for -large and -xlarge configurations. -->
@@ -38,6 +33,10 @@
<bool name="abc_config_closeDialogWhenTouchOutside">true</bool>
+ <!-- Whether to open UI submenus side by side with the top menu (as opposed to
+ replacing the top menu). -->
+ <bool name="abc_config_enableCascadingSubmenus">false</bool>
+
<!-- Maximum numerical value that will be shown in a status bar
notification icon or in the notification itself. Will be replaced
with @string/status_bar_notification_info_overflow when shown in the
@@ -45,4 +44,4 @@
<integer name="status_bar_notification_info_maxnum">999</integer>
<integer name="cancel_button_image_alpha">127</integer>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/v7/appcompat/res/values/dimens.xml b/v7/appcompat/res/values/dimens.xml
index 37130e3..3eeeb5d 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -15,12 +15,6 @@
-->
<resources>
-
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">2</integer>
-
<!-- Maximum width for a stacked action bar tab. This prevents
action bar tabs from becoming too wide on a wide screen when only
a few are present. -->
@@ -33,8 +27,6 @@
<dimen name="abc_panel_menu_list_width">296dp</dimen>
- <!-- Minimum width of the search view text entry area. -->
- <dimen name="abc_search_view_text_min_width">160dip</dimen>
<!-- Preferred width of the search view. -->
<dimen name="abc_search_view_preferred_width">320dip</dimen>
diff --git a/v7/appcompat/res/values/dimens_material.xml b/v7/appcompat/res/values/dimens_material.xml
index 357dc3e..e8723e2 100644
--- a/v7/appcompat/res/values/dimens_material.xml
+++ b/v7/appcompat/res/values/dimens_material.xml
@@ -54,6 +54,7 @@
<dimen name="abc_text_size_title_material_toolbar">20dp</dimen>
<dimen name="abc_text_size_subtitle_material_toolbar">16dp</dimen>
<dimen name="abc_text_size_menu_material">16sp</dimen>
+ <dimen name="abc_text_size_menu_header_material">14sp</dimen>
<dimen name="abc_text_size_body_2_material">14sp</dimen>
<dimen name="abc_text_size_body_1_material">14sp</dimen>
<dimen name="abc_text_size_caption_material">12sp</dimen>
diff --git a/v7/appcompat/res/values/donottranslate_material.xml b/v7/appcompat/res/values/donottranslate_material.xml
new file mode 100644
index 0000000..3f01f7a
--- /dev/null
+++ b/v7/appcompat/res/values/donottranslate_material.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+
+ <string name="abc_font_family_display_4_material">sans-serif-light</string>
+ <string name="abc_font_family_display_3_material">sans-serif</string>
+ <string name="abc_font_family_display_2_material">sans-serif</string>
+ <string name="abc_font_family_display_1_material">sans-serif</string>
+ <string name="abc_font_family_headline_material">sans-serif</string>
+ <string name="abc_font_family_title_material">sans-serif-medium</string>
+ <string name="abc_font_family_subhead_material">sans-serif</string>
+ <string name="abc_font_family_menu_material">sans-serif</string>
+ <string name="abc_font_family_body_2_material">sans-serif-medium</string>
+ <string name="abc_font_family_body_1_material">sans-serif</string>
+ <string name="abc_font_family_caption_material">sans-serif</string>
+ <string name="abc_font_family_button_material">sans-serif-medium</string>
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/styles.xml b/v7/appcompat/res/values/styles.xml
index 525f4ed..ef49cff 100644
--- a/v7/appcompat/res/values/styles.xml
+++ b/v7/appcompat/res/values/styles.xml
@@ -143,37 +143,25 @@
parent="Base.Widget.AppCompat.Light.PopupMenu.Overflow">
</style>
- <style name="Widget.AppCompat.PopupMenu" parent="Base.Widget.AppCompat.PopupMenu">
- </style>
+ <style name="Widget.AppCompat.PopupMenu" parent="Base.Widget.AppCompat.PopupMenu" />
- <style name="Widget.AppCompat.Light.PopupMenu"
- parent="Base.Widget.AppCompat.Light.PopupMenu">
- </style>
+ <style name="Widget.AppCompat.Light.PopupMenu" parent="Base.Widget.AppCompat.Light.PopupMenu" />
- <style name="Widget.AppCompat.ListView.Menu" parent="Base.Widget.AppCompat.ListView.Menu">
- </style>
+ <style name="Widget.AppCompat.ListView.Menu" parent="Base.Widget.AppCompat.ListView.Menu" />
+
+ <style name="Widget.AppCompat.ListMenuView" parent="Base.Widget.AppCompat.ListMenuView" />
<style name="Widget.AppCompat.PopupWindow" parent="Base.Widget.AppCompat.PopupWindow">
</style>
- <style name="TextAppearance.AppCompat.Widget.PopupMenu.Large"
- parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large">
- </style>
+ <style name="TextAppearance.AppCompat.Widget.PopupMenu.Large" parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large" />
- <style name="TextAppearance.AppCompat.Widget.PopupMenu.Small"
- parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small">
- </style>
+ <style name="TextAppearance.AppCompat.Widget.PopupMenu.Small" parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small" />
- <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Large"
- parent="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Large">
- </style>
-
- <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Small"
- parent="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Small">
- </style>
+ <style name="TextAppearance.AppCompat.Widget.PopupMenu.Header" parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Header" />
<style name="TextAppearance.AppCompat.SearchResult.Title"
- parent="Base.TextAppearance.AppCompat.SearchResult.Title">
+ parent="TextAppearance.AppCompat.SearchResult.Title">
</style>
<style name="TextAppearance.AppCompat.SearchResult.Subtitle"
@@ -329,6 +317,8 @@
<style name="Widget.AppCompat.Light.ListPopupWindow" parent="Widget.AppCompat.ListPopupWindow" />
<style name="Widget.AppCompat.Light.AutoCompleteTextView" parent="Widget.AppCompat.AutoCompleteTextView" />
<style name="Widget.AppCompat.Light.ActivityChooserView" parent="Widget.AppCompat.ActivityChooserView" />
+ <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Widget.PopupMenu.Large" />
+ <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Widget.PopupMenu.Small" />
<!-- These styles didn't exist on v7. Since we only use the media template in later versions
(ICS+), just define it here and use the correct references in values/v14 -->
diff --git a/v7/appcompat/res/values/styles_base.xml b/v7/appcompat/res/values/styles_base.xml
index 90a7486..8b808da 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -209,6 +209,10 @@
<item name="android:divider">@null</item>
</style>
+ <style name="Base.Widget.AppCompat.ListMenuView" parent="android:Widget">
+ <item name="subMenuArrow">@drawable/abc_ic_arrow_drop_right_black_24dp</item>
+ </style>
+
<style name="Base.TextAppearance.AppCompat.Widget.DropDownItem"
parent="android:TextAppearance.Small">
<item name="android:textColor">?android:attr/textColorPrimaryDisableOnly</item>
@@ -248,16 +252,13 @@
<style name="Base.Widget.AppCompat.Light.PopupMenu" parent="@style/Widget.AppCompat.ListPopupWindow">
</style>
- <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Menu">
- </style>
+ <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Menu"/>
- <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Menu">
- </style>
+ <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Menu"/>
- <style name="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Menu">
- </style>
-
- <style name="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Menu">
+ <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Header" parent="TextAppearance.AppCompat">
+ <item name="android:textSize">@dimen/abc_text_size_menu_header_material</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="Base.TextAppearance.AppCompat.SearchResult" parent="">
@@ -300,8 +301,9 @@
<item name="titleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Title</item>
<item name="subtitleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Subtitle</item>
<item name="android:minHeight">?attr/actionBarSize</item>
- <item name="titleMargins">4dp</item>
+ <item name="titleMargin">4dp</item>
<item name="maxButtonHeight">@dimen/abc_action_bar_default_height_material</item>
+ <item name="buttonGravity">top</item>
<item name="collapseIcon">?attr/homeAsUpIndicator</item>
<item name="collapseContentDescription">@string/abc_toolbar_collapse_description</item>
<item name="contentInsetStart">16dp</item>
@@ -453,7 +455,7 @@
<!-- Colored borderless ink button -->
<style name="Base.Widget.AppCompat.Button.Borderless.Colored">
- <item name="android:textColor">?attr/colorAccent</item>
+ <item name="android:textColor">@color/abc_btn_colored_borderless_text_material</item>
</style>
<style name="Base.Widget.AppCompat.Button.ButtonBar.AlertDialog" parent="Widget.AppCompat.Button.Borderless.Colored">
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index 4aaaed1..cfa2f1b 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -190,8 +190,10 @@
<item name="popupMenuStyle">@style/Widget.AppCompat.PopupMenu</item>
<item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Large</item>
<item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Small</item>
+ <item name="textAppearancePopupMenuHeader">@style/TextAppearance.AppCompat.Widget.PopupMenu.Header</item>
<item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
<item name="dropDownListViewStyle">?android:attr/dropDownListViewStyle</item>
+ <item name="listMenuViewStyle">@style/Widget.AppCompat.ListMenuView</item>
<!-- SearchView attributes -->
<item name="searchViewStyle">@style/Widget.AppCompat.SearchView</item>
@@ -347,8 +349,10 @@
<item name="popupMenuStyle">@style/Widget.AppCompat.Light.PopupMenu</item>
<item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Large</item>
<item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Small</item>
+ <item name="textAppearancePopupMenuHeader">@style/TextAppearance.AppCompat.Widget.PopupMenu.Header</item>
<item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
<item name="dropDownListViewStyle">?android:attr/dropDownListViewStyle</item>
+ <item name="listMenuViewStyle">@style/Widget.AppCompat.ListMenuView</item>
<!-- SearchView attributes -->
<item name="searchViewStyle">@style/Widget.AppCompat.Light.SearchView</item>
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
index 734f46f..d01dc47 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
@@ -28,6 +28,7 @@
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
+import android.support.v4.view.KeyEventCompat;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.Toolbar;
import android.view.KeyEvent;
@@ -491,16 +492,15 @@
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- final int keyCode = event.getKeyCode();
- if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+ if (KeyEventCompat.isCtrlPressed(event) &&
+ event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK) == '<') {
+ // Capture the Control-< and send focus to the ActionBar
final int action = event.getAction();
if (action == KeyEvent.ACTION_DOWN) {
- if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
- final ActionBar actionBar = getSupportActionBar();
- if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) {
- mEatKeyUpEvent = true;
- return true;
- }
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) {
+ mEatKeyUpEvent = true;
+ return true;
}
} else if (action == KeyEvent.ACTION_UP && mEatKeyUpEvent) {
mEatKeyUpEvent = false;
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
index 27f4265..93d4cec 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
@@ -27,6 +27,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
+import android.support.v4.os.BuildCompat;
import android.support.v4.view.WindowCompat;
import android.support.v7.appcompat.R;
import android.support.v7.view.ActionMode;
@@ -179,7 +180,9 @@
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
- if (sdk >= 23) {
+ if (BuildCompat.isAtLeastN()) {
+ return new AppCompatDelegateImplN(context, window, callback);
+ } else if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplN.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplN.java
new file mode 100644
index 0000000..d840ca6
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplN.java
@@ -0,0 +1,55 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.view.KeyboardShortcutGroup;
+import android.view.Menu;
+import android.view.Window;
+
+import java.util.List;
+
+class AppCompatDelegateImplN extends AppCompatDelegateImplV23 {
+
+ AppCompatDelegateImplN(Context context, Window window, AppCompatCallback callback) {
+ super(context, window, callback);
+ }
+
+ @Override
+ Window.Callback wrapWindowCallback(Window.Callback callback) {
+ return new AppCompatWindowCallbackN(callback);
+ }
+
+ class AppCompatWindowCallbackN extends AppCompatWindowCallbackV23 {
+ AppCompatWindowCallbackN(Window.Callback callback) {
+ super(callback);
+ }
+
+ @Override
+ public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, Menu menu) {
+ final PanelFeatureState panel = getPanelState(Window.FEATURE_OPTIONS_PANEL, true);
+ if (panel != null && panel.menu != null) {
+ // The menu provided is one created by PhoneWindow which we don't actually use.
+ // Instead we'll pass through our own...
+ super.onProvideKeyboardShortcuts(data, panel.menu);
+ } else {
+ // If we don't have a menu, jump pass through the original instead
+ super.onProvideKeyboardShortcuts(data, menu);
+ }
+ }
+ }
+}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
index 49f4d5f..b934f92 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
@@ -1507,7 +1507,7 @@
return null;
}
- private PanelFeatureState getPanelState(int featureId, boolean required) {
+ protected PanelFeatureState getPanelState(int featureId, boolean required) {
PanelFeatureState[] ar;
if ((ar = mPanels) == null || ar.length <= featureId) {
PanelFeatureState[] nar = new PanelFeatureState[featureId + 1];
@@ -1810,7 +1810,7 @@
}
}
- private static final class PanelFeatureState {
+ protected static final class PanelFeatureState {
/** Feature ID for this panel. */
int featureId;
diff --git a/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java b/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java
index 0e50cc1..3144c78 100644
--- a/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java
+++ b/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java
@@ -17,11 +17,13 @@
package android.support.v7.view;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v7.appcompat.R;
+import android.util.DisplayMetrics;
import android.view.ViewConfiguration;
/**
@@ -42,8 +44,48 @@
mContext = context;
}
+ /**
+ * Returns the maximum number of action buttons that should be permitted within an action
+ * bar/action mode. This will be used to determine how many showAsAction="ifRoom" items can fit.
+ * "always" items can override this.
+ */
public int getMaxActionButtons() {
- return mContext.getResources().getInteger(R.integer.abc_max_action_buttons);
+ final Configuration config = mContext.getResources().getConfiguration();
+ final int sdk = Build.VERSION.SDK_INT;
+
+ final int widthDp;
+ final int heightDp;
+ if (sdk >= Build.VERSION_CODES.HONEYCOMB_MR1) {
+ widthDp = config.screenWidthDp;
+ heightDp = config.screenHeightDp;
+ } else {
+ final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+ widthDp = (int) (metrics.widthPixels / metrics.density);
+ heightDp = (int) (metrics.heightPixels / metrics.density);
+ }
+
+ final int smallest;
+ if (sdk >= Build.VERSION_CODES.HONEYCOMB_MR2) {
+ smallest = config.smallestScreenWidthDp;
+ } else {
+ // Not quite perfect but close enough
+ smallest = Math.min(widthDp, heightDp);
+ }
+
+ if (smallest > 600 || widthDp > 600 || (widthDp > 960 && heightDp > 720)
+ || (widthDp > 720 && heightDp > 960)) {
+ // For values-w600dp, values-sw600dp and values-xlarge.
+ return 5;
+ } else if (widthDp >= 500 || (widthDp > 640 && heightDp > 480)
+ || (widthDp > 480 && heightDp > 640)) {
+ // For values-w500dp and values-large.
+ return 4;
+ } else if (widthDp >= 360) {
+ // For values-w360dp.
+ return 3;
+ } else {
+ return 2;
+ }
}
public boolean showsOverflowMenuButton() {
@@ -59,14 +101,7 @@
}
public boolean hasEmbeddedTabs() {
- final int targetSdk = mContext.getApplicationInfo().targetSdkVersion;
- if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) {
- return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs);
- }
-
- // The embedded tabs policy changed in Jellybean; give older apps the old policy
- // so they get what they expect.
- return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs_pre_jb);
+ return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs);
}
public int getTabContainerHeight() {
diff --git a/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java b/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java
index 45278bc..ade266f 100644
--- a/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java
+++ b/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java
@@ -18,6 +18,7 @@
import android.view.ActionMode;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
@@ -27,6 +28,8 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import java.util.List;
+
/**
* A simple decorator stub for Window.Callback that passes through any calls
* to the wrapped instance as a base implementation. Call super.foo() to call into
@@ -159,4 +162,9 @@
public void onActionModeFinished(ActionMode mode) {
mWrapped.onActionModeFinished(mode);
}
+
+ @Override
+ public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, Menu menu) {
+ mWrapped.onProvideKeyboardShortcuts(data, menu);
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java b/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
index 0b80cf2..06e79af 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
@@ -28,9 +28,11 @@
import android.support.v7.appcompat.R;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.AppCompatTextView;
+import android.support.v7.widget.ForwardingListener;
import android.support.v7.widget.ListPopupWindow;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
@@ -49,7 +51,7 @@
private CharSequence mTitle;
private Drawable mIcon;
private MenuBuilder.ItemInvoker mItemInvoker;
- private ListPopupWindow.ForwardingListener mForwardingListener;
+ private ForwardingListener mForwardingListener;
private PopupCallback mPopupCallback;
private boolean mAllowTextWithIcon;
@@ -71,8 +73,7 @@
public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final Resources res = context.getResources();
- mAllowTextWithIcon = res.getBoolean(
- R.bool.abc_config_allowActionMenuItemTextWithIcon);
+ mAllowTextWithIcon = shouldAllowTextWithIcon();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ActionMenuItemView, defStyle, 0);
mMinWidth = a.getDimensionPixelSize(
@@ -93,11 +94,32 @@
super.onConfigurationChanged(newConfig);
}
- mAllowTextWithIcon = getContext().getResources().getBoolean(
- R.bool.abc_config_allowActionMenuItemTextWithIcon);
+ mAllowTextWithIcon = shouldAllowTextWithIcon();
updateTextButtonVisibility();
}
+ /**
+ * Whether action menu items should obey the "withText" showAsAction flag. This may be set to
+ * false for situations where space is extremely limited. -->
+ */
+ private boolean shouldAllowTextWithIcon() {
+ final Configuration config = getContext().getResources().getConfiguration();
+
+ final int widthDp;
+ final int heightDp;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
+ widthDp = config.screenWidthDp;
+ heightDp = config.screenHeightDp;
+ } else {
+ final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ widthDp = (int) (metrics.widthPixels / metrics.density);
+ heightDp = (int) (metrics.heightPixels / metrics.density);
+ }
+
+ return widthDp >= 480 || (widthDp >= 640 && heightDp >= 480)
+ || config.orientation == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
@Override
public void setPadding(int l, int t, int r, int b) {
mSavedPaddingLeft = l;
@@ -291,13 +313,13 @@
}
}
- private class ActionMenuItemForwardingListener extends ListPopupWindow.ForwardingListener {
+ private class ActionMenuItemForwardingListener extends ForwardingListener {
public ActionMenuItemForwardingListener() {
super(ActionMenuItemView.this);
}
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
if (mPopupCallback != null) {
return mPopupCallback.getPopup();
}
@@ -308,7 +330,7 @@
protected boolean onForwardingStarted() {
// Call the invoker, then check if the expected popup is showing.
if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
- final ListPopupWindow popup = getPopup();
+ final ShowableListMenu popup = getPopup();
return popup != null && popup.isShowing();
}
return false;
@@ -323,6 +345,6 @@
}
public static abstract class PopupCallback {
- public abstract ListPopupWindow getPopup();
+ public abstract ShowableListMenu getPopup();
}
}
diff --git a/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java b/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java
new file mode 100644
index 0000000..9843f5e
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java
@@ -0,0 +1,727 @@
+/*
+ * 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.view.menu;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.support.annotation.AttrRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.appcompat.R;
+import android.support.v7.widget.MenuItemHoverListener;
+import android.support.v7.widget.MenuPopupWindow;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.View.OnKeyListener;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.AbsListView;
+import android.widget.FrameLayout;
+import android.widget.HeaderViewListAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
+import android.widget.TextView;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A popup for a menu which will allow multiple submenus to appear in a cascading fashion, side by
+ * side.
+ */
+final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKeyListener,
+ PopupWindow.OnDismissListener {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({HORIZ_POSITION_LEFT, HORIZ_POSITION_RIGHT})
+ public @interface HorizPosition {}
+
+ private static final int HORIZ_POSITION_LEFT = 0;
+ private static final int HORIZ_POSITION_RIGHT = 1;
+
+ /**
+ * Delay between hovering over a menu item with a mouse and receiving
+ * side-effects (ex. opening a sub-menu or closing unrelated menus).
+ */
+ private static final int SUBMENU_TIMEOUT_MS = 200;
+
+ private final Context mContext;
+ private final int mMenuMaxWidth;
+ private final int mPopupStyleAttr;
+ private final int mPopupStyleRes;
+ private final boolean mOverflowOnly;
+ private final Handler mSubMenuHoverHandler;
+
+ /** List of menus that were added before this popup was shown. */
+ private final List<MenuBuilder> mPendingMenus = new LinkedList<>();
+
+ /**
+ * List of open menus. The first item is the root menu and each
+ * subsequent item is a direct submenu of the previous item.
+ */
+ private final List<CascadingMenuInfo> mShowingMenus = new ArrayList<>();
+
+ private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ // Only move the popup if it's showing and non-modal. We don't want
+ // to be moving around the only interactive window, since there's a
+ // good chance the user is interacting with it.
+ if (isShowing() && mShowingMenus.size() > 0
+ && !mShowingMenus.get(0).window.isModal()) {
+ final View anchor = mShownAnchorView;
+ if (anchor == null || !anchor.isShown()) {
+ dismiss();
+ } else {
+ // Recompute window sizes and positions.
+ for (CascadingMenuInfo info : mShowingMenus) {
+ info.window.show();
+ }
+ }
+ }
+ }
+ };
+
+ private final MenuItemHoverListener mMenuItemHoverListener = new MenuItemHoverListener() {
+ @Override
+ public void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
+ // If the mouse moves between two windows, hover enter/exit pairs
+ // may be received out of order. So, instead of canceling all
+ // pending runnables, only cancel runnables for the host menu.
+ mSubMenuHoverHandler.removeCallbacksAndMessages(menu);
+ }
+
+ @Override
+ public void onItemHoverEnter(
+ @NonNull final MenuBuilder menu, @NonNull final MenuItem item) {
+ // Something new was hovered, cancel all scheduled runnables.
+ mSubMenuHoverHandler.removeCallbacksAndMessages(null);
+
+ // Find the position of the hovered menu within the added menus.
+ int menuIndex = -1;
+ for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
+ if (menu == mShowingMenus.get(i).menu) {
+ menuIndex = i;
+ break;
+ }
+ }
+
+ if (menuIndex == -1) {
+ return;
+ }
+
+ final CascadingMenuInfo nextInfo;
+ final int nextIndex = menuIndex + 1;
+ if (nextIndex < mShowingMenus.size()) {
+ nextInfo = mShowingMenus.get(nextIndex);
+ } else {
+ nextInfo = null;
+ }
+
+ final Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ // Close any other submenus that might be open at the
+ // current or a deeper level.
+ if (nextInfo != null) {
+ // Disable exit animations to prevent overlapping
+ // fading out submenus.
+ mShouldCloseImmediately = true;
+ nextInfo.menu.close(false /* closeAllMenus */);
+ mShouldCloseImmediately = false;
+ }
+
+ // Then open the selected submenu, if there is one.
+ if (item.isEnabled() && item.hasSubMenu()) {
+ menu.performItemAction(item, 0);
+ }
+ }
+ };
+ final long uptimeMillis = SystemClock.uptimeMillis() + SUBMENU_TIMEOUT_MS;
+ mSubMenuHoverHandler.postAtTime(runnable, menu, uptimeMillis);
+ }
+ };
+
+ private int mRawDropDownGravity = Gravity.NO_GRAVITY;
+ private int mDropDownGravity = Gravity.NO_GRAVITY;
+ private View mAnchorView;
+ private View mShownAnchorView;
+ private int mLastPosition;
+ private int mInitXOffset;
+ private int mInitYOffset;
+ private boolean mForceShowIcon;
+ private boolean mShowTitle;
+ private Callback mPresenterCallback;
+ private ViewTreeObserver mTreeObserver;
+ private PopupWindow.OnDismissListener mOnDismissListener;
+
+ /** Whether popup menus should disable exit animations when closing. */
+ private boolean mShouldCloseImmediately;
+
+ /**
+ * Initializes a new cascading-capable menu popup.
+ *
+ * @param anchor A parent view to get the {@link android.view.View#getWindowToken()} token from.
+ */
+ public CascadingMenuPopup(@NonNull Context context, @NonNull View anchor,
+ @AttrRes int popupStyleAttr, @StyleRes int popupStyleRes, boolean overflowOnly) {
+ mContext = context;
+ mAnchorView = anchor;
+ mPopupStyleAttr = popupStyleAttr;
+ mPopupStyleRes = popupStyleRes;
+ mOverflowOnly = overflowOnly;
+
+ mForceShowIcon = false;
+ mLastPosition = getInitialMenuPosition();
+
+ final Resources res = context.getResources();
+ mMenuMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
+ res.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth));
+
+ mSubMenuHoverHandler = new Handler();
+ }
+
+ @Override
+ public void setForceShowIcon(boolean forceShow) {
+ mForceShowIcon = forceShow;
+ }
+
+ private MenuPopupWindow createPopupWindow() {
+ MenuPopupWindow popupWindow = new MenuPopupWindow(
+ mContext, null, mPopupStyleAttr, mPopupStyleRes);
+ popupWindow.setHoverListener(mMenuItemHoverListener);
+ popupWindow.setOnItemClickListener(this);
+ popupWindow.setOnDismissListener(this);
+ popupWindow.setAnchorView(mAnchorView);
+ popupWindow.setDropDownGravity(mDropDownGravity);
+ popupWindow.setModal(true);
+ return popupWindow;
+ }
+
+ @Override
+ public void show() {
+ if (isShowing()) {
+ return;
+ }
+
+ // Display all pending menus.
+ for (MenuBuilder menu : mPendingMenus) {
+ showMenu(menu);
+ }
+ mPendingMenus.clear();
+
+ mShownAnchorView = mAnchorView;
+
+ if (mShownAnchorView != null) {
+ final boolean addGlobalListener = mTreeObserver == null;
+ mTreeObserver = mShownAnchorView.getViewTreeObserver(); // Refresh to latest
+ if (addGlobalListener) {
+ mTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
+ }
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ // Need to make another list to avoid a concurrent modification
+ // exception, as #onDismiss may clear mPopupWindows while we are
+ // iterating. Remove from the last added menu so that the callbacks
+ // are received in order from foreground to background.
+ final int length = mShowingMenus.size();
+ if (length > 0) {
+ final CascadingMenuInfo[] addedMenus =
+ mShowingMenus.toArray(new CascadingMenuInfo[length]);
+ for (int i = length - 1; i >= 0; i--) {
+ final CascadingMenuInfo info = addedMenus[i];
+ if (info.window.isShowing()) {
+ info.window.dismiss();
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
+ dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determines the proper initial menu position for the current LTR/RTL configuration.
+ * @return The initial position.
+ */
+ @HorizPosition
+ private int getInitialMenuPosition() {
+ final int layoutDirection = ViewCompat.getLayoutDirection(mAnchorView);
+ return layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL ? HORIZ_POSITION_LEFT :
+ HORIZ_POSITION_RIGHT;
+ }
+
+ /**
+ * Determines whether the next submenu (of the given width) should display on the right or on
+ * the left of the most recent menu.
+ *
+ * @param nextMenuWidth Width of the next submenu to display.
+ * @return The position to display it.
+ */
+ @HorizPosition
+ private int getNextMenuPosition(int nextMenuWidth) {
+ ListView lastListView = mShowingMenus.get(mShowingMenus.size() - 1).getListView();
+
+ final int[] screenLocation = new int[2];
+ lastListView.getLocationOnScreen(screenLocation);
+
+ final Rect displayFrame = new Rect();
+ mShownAnchorView.getWindowVisibleDisplayFrame(displayFrame);
+
+ if (mLastPosition == HORIZ_POSITION_RIGHT) {
+ final int right = screenLocation[0] + lastListView.getWidth() + nextMenuWidth;
+ if (right > displayFrame.right) {
+ return HORIZ_POSITION_LEFT;
+ }
+ return HORIZ_POSITION_RIGHT;
+ } else { // LEFT
+ final int left = screenLocation[0] - nextMenuWidth;
+ if (left < 0) {
+ return HORIZ_POSITION_RIGHT;
+ }
+ return HORIZ_POSITION_LEFT;
+ }
+ }
+
+ @Override
+ public void addMenu(MenuBuilder menu) {
+ menu.addMenuPresenter(this, mContext);
+
+ if (isShowing()) {
+ showMenu(menu);
+ } else {
+ mPendingMenus.add(menu);
+ }
+ }
+
+ /**
+ * Prepares and shows the specified menu immediately.
+ *
+ * @param menu the menu to show
+ */
+ private void showMenu(@NonNull MenuBuilder menu) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly);
+ adapter.setForceShowIcon(mForceShowIcon);
+
+ final int menuWidth = measureIndividualMenuWidth(adapter, null, mContext, mMenuMaxWidth);
+ final MenuPopupWindow popupWindow = createPopupWindow();
+ popupWindow.setAdapter(adapter);
+ popupWindow.setWidth(menuWidth);
+ popupWindow.setDropDownGravity(mDropDownGravity);
+
+ final CascadingMenuInfo parentInfo;
+ final View parentView;
+ if (mShowingMenus.size() > 0) {
+ parentInfo = mShowingMenus.get(mShowingMenus.size() - 1);
+ parentView = findParentViewForSubmenu(parentInfo, menu);
+ } else {
+ parentInfo = null;
+ parentView = null;
+ }
+
+ final int x;
+ final int y;
+ final Rect epicenterBounds;
+ if (parentView != null) {
+ // This menu is a cascading submenu anchored to a parent view.
+ popupWindow.setTouchModal(false);
+ popupWindow.setEnterTransition(null);
+
+ final @HorizPosition int nextMenuPosition = getNextMenuPosition(menuWidth);
+ final boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT;
+ mLastPosition = nextMenuPosition;
+
+ final int[] tempLocation = new int[2];
+
+ // This popup menu will be positioned relative to the top-left edge
+ // of the view representing its parent menu.
+ parentView.getLocationInWindow(tempLocation);
+ final int parentOffsetLeft = parentInfo.window.getHorizontalOffset() + tempLocation[0];
+ final int parentOffsetTop = parentInfo.window.getVerticalOffset() + tempLocation[1];
+
+ // By now, mDropDownGravity is the resolved absolute gravity, so
+ // this should work in both LTR and RTL.
+ if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) {
+ if (showOnRight) {
+ x = parentOffsetLeft + menuWidth;
+ } else {
+ x = parentOffsetLeft - parentView.getWidth();
+ }
+ } else {
+ if (showOnRight) {
+ x = parentOffsetLeft + parentView.getWidth();
+ } else {
+ x = parentOffsetLeft - menuWidth;
+ }
+ }
+
+ y = parentOffsetTop;
+ epicenterBounds = null;
+ } else {
+ x = mInitXOffset;
+ y = mInitYOffset;
+ epicenterBounds = getEpicenterBounds();
+ }
+
+ popupWindow.setHorizontalOffset(x);
+ popupWindow.setVerticalOffset(y);
+ popupWindow.setEpicenterBounds(epicenterBounds);
+
+ final CascadingMenuInfo menuInfo = new CascadingMenuInfo(popupWindow, menu, mLastPosition);
+ mShowingMenus.add(menuInfo);
+
+ popupWindow.show();
+
+ // If this is the root menu, show the title if requested.
+ if (parentInfo == null && mShowTitle && menu.getHeaderTitle() != null) {
+ final ListView listView = popupWindow.getListView();
+ final FrameLayout titleItemView = (FrameLayout) inflater.inflate(
+ R.layout.abc_popup_menu_header_item_layout, listView, false);
+ final TextView titleView = (TextView) titleItemView.findViewById(android.R.id.title);
+ titleItemView.setEnabled(false);
+ titleView.setText(menu.getHeaderTitle());
+ listView.addHeaderView(titleItemView, null, false);
+
+ // Show again to update the title.
+ popupWindow.show();
+ }
+ }
+
+ /**
+ * Returns the menu item within the specified parent menu that owns
+ * specified submenu.
+ *
+ * @param parent the parent menu
+ * @param submenu the submenu for which the index should be returned
+ * @return the menu item that owns the submenu, or {@code null} if not
+ * present
+ */
+ private MenuItem findMenuItemForSubmenu(
+ @NonNull MenuBuilder parent, @NonNull MenuBuilder submenu) {
+ for (int i = 0, count = parent.size(); i < count; i++) {
+ final MenuItem item = parent.getItem(i);
+ if (item.hasSubMenu() && submenu == item.getSubMenu()) {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Attempts to find the view for the menu item that owns the specified
+ * submenu.
+ *
+ * @param parentInfo info for the parent menu
+ * @param submenu the submenu whose parent view should be obtained
+ * @return the parent view, or {@code null} if one could not be found
+ */
+ @Nullable
+ private View findParentViewForSubmenu(
+ @NonNull CascadingMenuInfo parentInfo, @NonNull MenuBuilder submenu) {
+ final MenuItem owner = findMenuItemForSubmenu(parentInfo.menu, submenu);
+ if (owner == null) {
+ // Couldn't find the submenu owner.
+ return null;
+ }
+
+ // The adapter may be wrapped. Adjust the index if necessary.
+ final int headersCount;
+ final MenuAdapter menuAdapter;
+ final ListView listView = parentInfo.getListView();
+ final ListAdapter listAdapter = listView.getAdapter();
+ if (listAdapter instanceof HeaderViewListAdapter) {
+ final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) listAdapter;
+ headersCount = headerAdapter.getHeadersCount();
+ menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter();
+ } else {
+ headersCount = 0;
+ menuAdapter = (MenuAdapter) listAdapter;
+ }
+
+ // Find the index within the menu adapter's data set of the menu item.
+ int ownerPosition = AbsListView.INVALID_POSITION;
+ for (int i = 0, count = menuAdapter.getCount(); i < count; i++) {
+ if (owner == menuAdapter.getItem(i)) {
+ ownerPosition = i;
+ break;
+ }
+ }
+ if (ownerPosition == AbsListView.INVALID_POSITION) {
+ // Couldn't find the owner within the menu adapter.
+ return null;
+ }
+
+ // Adjust the index for the adapter used to display views.
+ ownerPosition += headersCount;
+
+ // Adjust the index for the visible views.
+ final int ownerViewPosition = ownerPosition - listView.getFirstVisiblePosition();
+ if (ownerViewPosition < 0 || ownerViewPosition >= listView.getChildCount()) {
+ // Not visible on screen.
+ return null;
+ }
+
+ return listView.getChildAt(ownerViewPosition);
+ }
+
+ /**
+ * @return {@code true} if the popup is currently showing, {@code false} otherwise.
+ */
+ @Override
+ public boolean isShowing() {
+ return mShowingMenus.size() > 0 && mShowingMenus.get(0).window.isShowing();
+ }
+
+ /**
+ * Called when one or more of the popup windows was dismissed.
+ */
+ @Override
+ public void onDismiss() {
+ // The dismiss listener doesn't pass the calling window, so walk
+ // through the stack to figure out which one was just dismissed.
+ CascadingMenuInfo dismissedInfo = null;
+ for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
+ final CascadingMenuInfo info = mShowingMenus.get(i);
+ if (!info.window.isShowing()) {
+ dismissedInfo = info;
+ break;
+ }
+ }
+
+ // Close all menus starting from the dismissed menu, passing false
+ // since we are manually closing only a subset of windows.
+ if (dismissedInfo != null) {
+ dismissedInfo.menu.close(false);
+ }
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ for (CascadingMenuInfo info : mShowingMenus) {
+ toMenuAdapter(info.getListView().getAdapter()).notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ mPresenterCallback = cb;
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ // Don't allow double-opening of the same submenu.
+ for (CascadingMenuInfo info : mShowingMenus) {
+ if (subMenu == info.menu) {
+ // Just re-focus that one.
+ info.getListView().requestFocus();
+ return true;
+ }
+ }
+
+ if (subMenu.hasVisibleItems()) {
+ addMenu(subMenu);
+
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onOpenSubMenu(subMenu);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Finds the index of the specified menu within the list of added menus.
+ *
+ * @param menu the menu to find
+ * @return the index of the menu, or {@code -1} if not present
+ */
+ private int findIndexOfAddedMenu(@NonNull MenuBuilder menu) {
+ for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
+ final CascadingMenuInfo info = mShowingMenus.get(i);
+ if (menu == info.menu) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ final int menuIndex = findIndexOfAddedMenu(menu);
+ if (menuIndex < 0) {
+ return;
+ }
+
+ // Recursively close descendant menus.
+ final int nextMenuIndex = menuIndex + 1;
+ if (nextMenuIndex < mShowingMenus.size()) {
+ final CascadingMenuInfo childInfo = mShowingMenus.get(nextMenuIndex);
+ childInfo.menu.close(false /* closeAllMenus */);
+ }
+
+ // Close the target menu.
+ final CascadingMenuInfo info = mShowingMenus.remove(menuIndex);
+ info.menu.removeMenuPresenter(this);
+ if (mShouldCloseImmediately) {
+ // Disable all exit animations.
+ info.window.setExitTransition(null);
+ info.window.setAnimationStyle(0);
+ }
+ info.window.dismiss();
+
+ final int count = mShowingMenus.size();
+ if (count > 0) {
+ mLastPosition = mShowingMenus.get(count - 1).position;
+ } else {
+ mLastPosition = getInitialMenuPosition();
+ }
+
+ if (count == 0) {
+ // This was the last window. Clean up.
+ dismiss();
+
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onCloseMenu(menu, true);
+ }
+
+ if (mTreeObserver != null) {
+ if (mTreeObserver.isAlive()) {
+ mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
+ }
+ mTreeObserver = null;
+ }
+
+
+ // If every [sub]menu was dismissed, that means the whole thing was
+ // dismissed, so notify the owner.
+ mOnDismissListener.onDismiss();
+ } else if (allMenusAreClosing) {
+ // Close all menus starting from the root. This will recursively
+ // close any remaining menus, so we don't need to propagate the
+ // "closeAllMenus" flag. The last window will clean up.
+ final CascadingMenuInfo rootInfo = mShowingMenus.get(0);
+ rootInfo.menu.close(false /* closeAllMenus */);
+ }
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ return null;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ }
+
+ @Override
+ public void setGravity(int dropDownGravity) {
+ if (mRawDropDownGravity != dropDownGravity) {
+ mRawDropDownGravity = dropDownGravity;
+ mDropDownGravity = GravityCompat.getAbsoluteGravity(
+ dropDownGravity, ViewCompat.getLayoutDirection(mAnchorView));
+ }
+ }
+
+ @Override
+ public void setAnchorView(@NonNull View anchor) {
+ if (mAnchorView != anchor) {
+ mAnchorView = anchor;
+
+ // Gravity resolution may have changed, update from raw gravity.
+ mDropDownGravity = GravityCompat.getAbsoluteGravity(
+ mRawDropDownGravity, ViewCompat.getLayoutDirection(mAnchorView));
+ }
+ }
+
+ @Override
+ public void setOnDismissListener(OnDismissListener listener) {
+ mOnDismissListener = listener;
+ }
+
+ @Override
+ public ListView getListView() {
+ return mShowingMenus.isEmpty()
+ ? null
+ : mShowingMenus.get(mShowingMenus.size() - 1).getListView();
+ }
+
+ @Override
+ public void setHorizontalOffset(int x) {
+ mInitXOffset = x;
+ }
+
+ @Override
+ public void setVerticalOffset(int y) {
+ mInitYOffset = y;
+ }
+
+ @Override
+ public void setShowTitle(boolean showTitle) {
+ mShowTitle = showTitle;
+ }
+
+ private static class CascadingMenuInfo {
+ public final MenuPopupWindow window;
+ public final MenuBuilder menu;
+ public final int position;
+
+ public CascadingMenuInfo(@NonNull MenuPopupWindow window, @NonNull MenuBuilder menu,
+ int position) {
+ this.window = window;
+ this.menu = menu;
+ this.position = position;
+ }
+
+ public ListView getListView() {
+ return window.getListView();
+ }
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java b/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java
index 388e78f..2d6def1 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java
@@ -17,9 +17,9 @@
package android.support.v7.view.menu;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.v7.appcompat.R;
+import android.support.v7.widget.TintTypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -37,7 +37,6 @@
* @hide
*/
public class ListMenuItemView extends LinearLayout implements MenuView.ItemView {
-
private static final String TAG = "ListMenuItemView";
private MenuItemImpl mItemData;
@@ -46,25 +45,29 @@
private TextView mTitleView;
private CheckBox mCheckBox;
private TextView mShortcutView;
+ private ImageView mSubMenuArrowView;
private Drawable mBackground;
private int mTextAppearance;
private Context mTextAppearanceContext;
private boolean mPreserveIconSpacing;
+ private Drawable mSubMenuArrow;
private int mMenuType;
- private Context mContext;
private LayoutInflater mInflater;
private boolean mForceShowIcon;
- public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs);
- mContext = context;
+ public ListMenuItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.listMenuViewStyle);
+ }
- final TypedArray a = context.obtainStyledAttributes(
- attrs, R.styleable.MenuView, defStyle, 0);
+ public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs);
+
+ final TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(),
+ attrs, R.styleable.MenuView, defStyleAttr, 0);
mBackground = a.getDrawable(R.styleable.MenuView_android_itemBackground);
mTextAppearance = a.getResourceId(R.styleable.
@@ -72,14 +75,11 @@
mPreserveIconSpacing = a.getBoolean(
R.styleable.MenuView_preserveIconSpacing, false);
mTextAppearanceContext = context;
+ mSubMenuArrow = a.getDrawable(R.styleable.MenuView_subMenuArrow);
a.recycle();
}
- public ListMenuItemView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -93,6 +93,10 @@
}
mShortcutView = (TextView) findViewById(R.id.shortcut);
+ mSubMenuArrowView = (ImageView) findViewById(R.id.submenuarrow);
+ if (mSubMenuArrowView != null) {
+ mSubMenuArrowView.setImageDrawable(mSubMenuArrow);
+ }
}
public void initialize(MenuItemImpl itemData, int menuType) {
@@ -106,6 +110,7 @@
setShortcut(itemData.shouldShowShortcut(), itemData.getShortcut());
setIcon(itemData.getIcon());
setEnabled(itemData.isEnabled());
+ setSubMenuArrowVisible(itemData.hasSubMenu());
}
public void setForceShowIcon(boolean forceShow) {
@@ -190,6 +195,12 @@
compoundButton.setChecked(checked);
}
+ private void setSubMenuArrowVisible(boolean hasSubmenu) {
+ if (mSubMenuArrowView != null) {
+ mSubMenuArrowView.setVisibility(hasSubmenu ? View.VISIBLE : View.GONE);
+ }
+ }
+
public void setShortcut(boolean showShortcut, char shortcutKey) {
final int newVisibility = (showShortcut && mItemData.shouldShowShortcut())
? VISIBLE : GONE;
@@ -274,7 +285,7 @@
private LayoutInflater getInflater() {
if (mInflater == null) {
- mInflater = LayoutInflater.from(mContext);
+ mInflater = LayoutInflater.from(getContext());
}
return mInflater;
}
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuAdapter.java b/v7/appcompat/src/android/support/v7/view/menu/MenuAdapter.java
new file mode 100644
index 0000000..08ec36d
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuAdapter.java
@@ -0,0 +1,117 @@
+/*
+ * 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.view.menu;
+
+import android.support.v7.appcompat.R;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class MenuAdapter extends BaseAdapter {
+ static final int ITEM_LAYOUT = R.layout.abc_popup_menu_item_layout;
+
+ MenuBuilder mAdapterMenu;
+
+ private int mExpandedIndex = -1;
+
+ private boolean mForceShowIcon;
+ private final boolean mOverflowOnly;
+ private final LayoutInflater mInflater;
+
+ public MenuAdapter(MenuBuilder menu, LayoutInflater inflater, boolean overflowOnly) {
+ mOverflowOnly = overflowOnly;
+ mInflater = inflater;
+ mAdapterMenu = menu;
+ findExpandedIndex();
+ }
+
+ public boolean getForceShowIcon() {
+ return mForceShowIcon;
+ }
+
+ public void setForceShowIcon(boolean forceShow) {
+ mForceShowIcon = forceShow;
+ }
+
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ if (mExpandedIndex < 0) {
+ return items.size();
+ }
+ return items.size() - 1;
+ }
+
+ public MenuBuilder getAdapterMenu() {
+ return mAdapterMenu;
+ }
+
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
+ position++;
+ }
+ return items.get(position);
+ }
+
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ if (mForceShowIcon) {
+ ((ListMenuItemView) convertView).setForceShowIcon(true);
+ }
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+
+ void findExpandedIndex() {
+ final MenuItemImpl expandedItem = mAdapterMenu.getExpandedItem();
+ if (expandedItem != null) {
+ final ArrayList<MenuItemImpl> items = mAdapterMenu.getNonActionItems();
+ final int count = items.size();
+ for (int i = 0; i < count; i++) {
+ final MenuItemImpl item = items.get(i);
+ if (item == expandedItem) {
+ mExpandedIndex = i;
+ return;
+ }
+ }
+ }
+ mExpandedIndex = -1;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ findExpandedIndex();
+ super.notifyDataSetChanged();
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java b/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
index b976655..f163215 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
@@ -26,6 +26,7 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v4.internal.view.SupportMenu;
import android.support.v4.internal.view.SupportMenuItem;
@@ -69,8 +70,8 @@
};
private final Context mContext;
-
private final Resources mResources;
+ private final boolean mShowCascadingMenus;
/**
* Whether the shortcuts should be qwerty-accessible. Use isQwertyMode() instead of accessing
@@ -214,14 +215,16 @@
public MenuBuilder(Context context) {
mContext = context;
mResources = context.getResources();
+ mShowCascadingMenus = context.getResources().getBoolean(
+ R.bool.abc_config_enableCascadingSubmenus);
- mItems = new ArrayList<MenuItemImpl>();
+ mItems = new ArrayList<>();
- mVisibleItems = new ArrayList<MenuItemImpl>();
+ mVisibleItems = new ArrayList<>();
mIsVisibleItemsStale = true;
- mActionItems = new ArrayList<MenuItemImpl>();
- mNonActionItems = new ArrayList<MenuItemImpl>();
+ mActionItems = new ArrayList<>();
+ mNonActionItems = new ArrayList<>();
mIsActionItemsStale = true;
setShortcutsVisibleInner(true);
@@ -842,7 +845,7 @@
}
if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) {
- close(true);
+ close(true /* closeAllMenus */);
}
return handled;
@@ -961,9 +964,13 @@
final boolean providerHasSubMenu = provider != null && provider.hasSubMenu();
if (itemImpl.hasCollapsibleActionView()) {
invoked |= itemImpl.expandActionView();
- if (invoked) close(true);
+ if (invoked) {
+ close(true /* closeAllMenus */);
+ }
} else if (itemImpl.hasSubMenu() || providerHasSubMenu) {
- close(false);
+ if (!mShowCascadingMenus) {
+ close(false /* closeAllMenus */);
+ }
if (!itemImpl.hasSubMenu()) {
itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl));
@@ -974,10 +981,12 @@
provider.onPrepareSubMenu(subMenu);
}
invoked |= dispatchSubMenuSelected(subMenu, preferredPresenter);
- if (!invoked) close(true);
+ if (!invoked) {
+ close(true /* closeAllMenus */);
+ }
} else {
if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
- close(true);
+ close(true /* closeAllMenus */);
}
}
@@ -985,15 +994,14 @@
}
/**
- * Closes the visible menu.
+ * Closes the menu.
*
- * @param allMenusAreClosing Whether the menus are completely closing (true),
- * or whether there is another menu coming in this menu's place
- * (false). For example, if the menu is closing because a
- * sub menu is about to be shown, <var>allMenusAreClosing</var>
- * is false.
+ * @param closeAllMenus {@code true} if all displayed menus and submenus
+ * should be completely closed (as when a menu item is
+ * selected) or {@code false} if only this menu should
+ * be closed
*/
- public final void close(boolean allMenusAreClosing) {
+ public final void close(boolean closeAllMenus) {
if (mIsClosing) return;
mIsClosing = true;
@@ -1002,7 +1010,7 @@
if (presenter == null) {
mPresenters.remove(ref);
} else {
- presenter.onCloseMenu(this, allMenusAreClosing);
+ presenter.onCloseMenu(this, closeAllMenus);
}
}
mIsClosing = false;
@@ -1010,7 +1018,7 @@
@Override
public void close() {
- close(true);
+ close(true /* closeAllMenus */);
}
/**
@@ -1076,6 +1084,7 @@
onItemsChanged(true);
}
+ @NonNull
public ArrayList<MenuItemImpl> getVisibleItems() {
if (!mIsVisibleItemsStale) return mVisibleItems;
@@ -1284,7 +1293,6 @@
/**
* Gets the root menu (if this is a submenu, find its root menu).
- *
* @return The root menu.
*/
public MenuBuilder getRootMenu() {
@@ -1302,7 +1310,7 @@
mCurrentMenuInfo = menuInfo;
}
- void setOptionalIconsVisible(boolean visible) {
+ public void setOptionalIconsVisible(boolean visible) {
mOptionalIconsVisible = visible;
}
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuHelper.java b/v7/appcompat/src/android/support/v7/view/menu/MenuHelper.java
new file mode 100644
index 0000000..b861643
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuHelper.java
@@ -0,0 +1,25 @@
+/*
+ * 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.view.menu;
+
+/**
+ * Interface for a helper capable of presenting a menu.
+ */
+interface MenuHelper {
+ void setPresenterCallback(MenuPresenter.Callback cb);
+ void dismiss();
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuPopup.java b/v7/appcompat/src/android/support/v7/view/menu/MenuPopup.java
new file mode 100644
index 0000000..0dbf5cf
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuPopup.java
@@ -0,0 +1,184 @@
+/*
+ * 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.view.menu;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.FrameLayout;
+import android.widget.HeaderViewListAdapter;
+import android.widget.ListAdapter;
+import android.widget.PopupWindow;
+
+/**
+ * Base class for a menu popup abstraction - i.e., some type of menu, housed in a popup window
+ * environment.
+ */
+abstract class MenuPopup implements ShowableListMenu, MenuPresenter,
+ AdapterView.OnItemClickListener {
+ private Rect mEpicenterBounds;
+
+ public abstract void setForceShowIcon(boolean forceShow);
+
+ /**
+ * Adds the given menu to the popup, if it is capable of displaying submenus within itself.
+ * If menu is the first menu shown, it won't be displayed until show() is called.
+ * If the popup was already showing, adding a submenu via this method will cause that new
+ * submenu to be shown immediately (that is, if this MenuPopup implementation is capable of
+ * showing its own submenus).
+ *
+ * @param menu
+ */
+ public abstract void addMenu(MenuBuilder menu);
+
+ public abstract void setGravity(int dropDownGravity);
+
+ public abstract void setAnchorView(View anchor);
+
+ public abstract void setHorizontalOffset(int x);
+
+ public abstract void setVerticalOffset(int y);
+
+ /**
+ * Specifies the anchor-relative bounds of the popup's transition
+ * epicenter.
+ *
+ * @param bounds anchor-relative bounds
+ */
+ public void setEpicenterBounds(Rect bounds) {
+ mEpicenterBounds = bounds;
+ }
+
+ /**
+ * @return anchor-relative bounds of the popup's transition epicenter
+ */
+ public Rect getEpicenterBounds() {
+ return mEpicenterBounds;
+ }
+
+ /**
+ * Set whether a title entry should be shown in the popup menu (if a title exists for the
+ * menu).
+ *
+ * @param showTitle
+ */
+ public abstract void setShowTitle(boolean showTitle);
+
+ /**
+ * Set a listener to receive a callback when the popup is dismissed.
+ *
+ * @param listener Listener that will be notified when the popup is dismissed.
+ */
+ public abstract void setOnDismissListener(PopupWindow.OnDismissListener listener);
+
+ @Override
+ public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
+ // Don't need to do anything; we added as a presenter in the constructor.
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ throw new UnsupportedOperationException("MenuPopups manage their own views");
+ }
+
+ @Override
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ @Override
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ @Override
+ public int getId() {
+ return 0;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ ListAdapter outerAdapter = (ListAdapter) parent.getAdapter();
+ MenuAdapter wrappedAdapter = toMenuAdapter(outerAdapter);
+
+ // Use the position from the outer adapter so that if a header view was added, we don't get
+ // an off-by-1 error in position.
+ wrappedAdapter.mAdapterMenu.performItemAction((MenuItem) outerAdapter.getItem(position), 0);
+ }
+
+ /**
+ * Measures the width of the given menu view.
+ *
+ * @param view The view to measure.
+ * @return The width.
+ */
+ protected static int measureIndividualMenuWidth(ListAdapter adapter, ViewGroup parent,
+ Context context, int maxAllowedWidth) {
+ // Menus don't tend to be long, so this is more sane than it looks.
+ int maxWidth = 0;
+ View itemView = null;
+ int itemType = 0;
+
+ final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int count = adapter.getCount();
+ for (int i = 0; i < count; i++) {
+ final int positionType = adapter.getItemViewType(i);
+ if (positionType != itemType) {
+ itemType = positionType;
+ itemView = null;
+ }
+
+ if (parent == null) {
+ parent = new FrameLayout(context);
+ }
+
+ itemView = adapter.getView(i, itemView, parent);
+ itemView.measure(widthMeasureSpec, heightMeasureSpec);
+
+ final int itemWidth = itemView.getMeasuredWidth();
+ if (itemWidth >= maxAllowedWidth) {
+ return maxAllowedWidth;
+ } else if (itemWidth > maxWidth) {
+ maxWidth = itemWidth;
+ }
+ }
+
+ return maxWidth;
+ }
+
+ /**
+ * Converts the given ListAdapter originating from a menu, to a MenuAdapter, accounting for
+ * the possibility of the parameter adapter actually wrapping the MenuAdapter. (That could
+ * happen if a header view was added on the menu.)
+ *
+ * @param adapter
+ * @return
+ */
+ protected static MenuAdapter toMenuAdapter(ListAdapter adapter) {
+ if (adapter instanceof HeaderViewListAdapter) {
+ return (MenuAdapter) ((HeaderViewListAdapter) adapter).getWrappedAdapter();
+ }
+ return (MenuAdapter) adapter;
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java b/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java
index c0e0fb5..4a912d2 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java
@@ -17,110 +17,111 @@
package android.support.v7.view.menu;
import android.content.Context;
-import android.content.res.Resources;
-import android.os.Parcelable;
+import android.graphics.Rect;
+import android.support.annotation.AttrRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
-import android.support.v7.widget.ListPopupWindow;
import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ListAdapter;
-import android.widget.PopupWindow;
-
-import java.util.ArrayList;
+import android.widget.PopupWindow.OnDismissListener;
/**
* Presents a menu as a small, simple popup anchored to another view.
*
* @hide
*/
-public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
- ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
- MenuPresenter {
-
- private static final String TAG = "MenuPopupHelper";
-
- static final int ITEM_LAYOUT = R.layout.abc_popup_menu_item_layout;
+public class MenuPopupHelper implements MenuHelper {
+ private static final int TOUCH_EPICENTER_SIZE_DP = 48;
private final Context mContext;
- private final LayoutInflater mInflater;
+
+ // Immutable cached popup menu properties.
private final MenuBuilder mMenu;
- private final MenuAdapter mAdapter;
private final boolean mOverflowOnly;
- private final int mPopupMaxWidth;
private final int mPopupStyleAttr;
private final int mPopupStyleRes;
+ // Mutable cached popup menu properties.
private View mAnchorView;
- private ListPopupWindow mPopup;
- private ViewTreeObserver mTreeObserver;
- private Callback mPresenterCallback;
+ private int mDropDownGravity = Gravity.START;
+ private boolean mForceShowIcon;
+ private MenuPresenter.Callback mPresenterCallback;
- boolean mForceShowIcon;
+ private MenuPopup mPopup;
+ private OnDismissListener mOnDismissListener;
- private ViewGroup mMeasureParent;
-
- /** Whether the cached content width value is valid. */
- private boolean mHasContentWidth;
-
- /** Cached content width from {@link #measureContentWidth}. */
- private int mContentWidth;
-
- private int mDropDownGravity = Gravity.NO_GRAVITY;
-
- public MenuPopupHelper(Context context, MenuBuilder menu) {
- this(context, menu, null, false, R.attr.popupMenuStyle);
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu) {
+ this(context, menu, null, false, R.attr.popupMenuStyle, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
- this(context, menu, anchorView, false, R.attr.popupMenuStyle);
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView) {
+ this(context, menu, anchorView, false, R.attr.popupMenuStyle, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
- boolean overflowOnly, int popupStyleAttr) {
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView,
+ boolean overflowOnly, @AttrRes int popupStyleAttr) {
this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
- boolean overflowOnly, int popupStyleAttr, int popupStyleRes) {
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView, boolean overflowOnly, @AttrRes int popupStyleAttr,
+ @StyleRes int popupStyleRes) {
mContext = context;
- mInflater = LayoutInflater.from(context);
mMenu = menu;
- mAdapter = new MenuAdapter(mMenu);
+ mAnchorView = anchorView;
mOverflowOnly = overflowOnly;
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
-
- final Resources res = context.getResources();
- mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
- res.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth));
-
- mAnchorView = anchorView;
-
- // Present the menu using our context, not the menu builder's context.
- menu.addMenuPresenter(this, context);
}
- public void setAnchorView(View anchor) {
+ public void setOnDismissListener(@Nullable OnDismissListener listener) {
+ mOnDismissListener = listener;
+ }
+
+ /**
+ * Sets the view to which the popup window is anchored.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param anchor the view to which the popup window should be anchored
+ */
+ public void setAnchorView(@NonNull View anchor) {
mAnchorView = anchor;
}
- public void setForceShowIcon(boolean forceShow) {
- mForceShowIcon = forceShow;
+ /**
+ * Sets whether the popup menu's adapter is forced to show icons in the
+ * menu item views.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param forceShowIcon {@code true} to force icons to be shown, or
+ * {@code false} for icons to be optionally shown
+ */
+ public void setForceShowIcon(boolean forceShowIcon) {
+ mForceShowIcon = forceShowIcon;
}
+ /**
+ * Sets the alignment of the popup window relative to the anchor view.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param gravity alignment of the popup relative to the anchor
+ */
public void setGravity(int gravity) {
mDropDownGravity = gravity;
}
+ /**
+ * @return alignment of the popup relative to the anchor
+ */
public int getGravity() {
return mDropDownGravity;
}
@@ -131,53 +132,161 @@
}
}
- public ListPopupWindow getPopup() {
+ public void show(int x, int y) {
+ if (!tryShow(x, y)) {
+ throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
+ }
+ }
+
+ @NonNull
+ public MenuPopup getPopup() {
+ if (mPopup == null) {
+ mPopup = createPopup();
+ }
return mPopup;
}
+ /**
+ * Attempts to show the popup anchored to the view specified by {@link #setAnchorView(View)}.
+ *
+ * @return {@code true} if the popup was shown or was already showing prior to calling this
+ * method, {@code false} otherwise
+ */
public boolean tryShow() {
- mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
- mPopup.setOnDismissListener(this);
- mPopup.setOnItemClickListener(this);
- mPopup.setAdapter(mAdapter);
- mPopup.setModal(true);
+ if (isShowing()) {
+ return true;
+ }
- View anchor = mAnchorView;
- if (anchor != null) {
- final boolean addGlobalListener = mTreeObserver == null;
- mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
- if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
- mPopup.setAnchorView(anchor);
- mPopup.setDropDownGravity(mDropDownGravity);
- } else {
+ if (mAnchorView == null) {
return false;
}
- if (!mHasContentWidth) {
- mContentWidth = measureContentWidth();
- mHasContentWidth = true;
- }
-
- mPopup.setContentWidth(mContentWidth);
- mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
- mPopup.show();
- mPopup.getListView().setOnKeyListener(this);
+ showPopup(0, 0, false, false);
return true;
}
+ /**
+ * Shows the popup menu and makes a best-effort to anchor it to the
+ * specified (x,y) coordinate relative to the anchor view.
+ * <p>
+ * Additionally, the popup's transition epicenter (see
+ * {@link android.widget.PopupWindow#setEpicenterBounds(Rect)} will be
+ * centered on the specified coordinate, rather than using the bounds of
+ * the anchor view.
+ * <p>
+ * If the popup's resolved gravity is {@link Gravity#LEFT}, this will
+ * display the popup with its top-left corner at (x,y) relative to the
+ * anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
+ * popup's top-right corner will be at (x,y).
+ * <p>
+ * If the popup cannot be displayed fully on-screen, this method will
+ * attempt to scroll the anchor view's ancestors and/or offset the popup
+ * such that it may be displayed fully on-screen.
+ *
+ * @param x x coordinate relative to the anchor view
+ * @param y y coordinate relative to the anchor view
+ * @return {@code true} if the popup was shown or was already showing prior
+ * to calling this method, {@code false} otherwise
+ */
+ public boolean tryShow(int x, int y) {
+ if (isShowing()) {
+ return true;
+ }
+
+ if (mAnchorView == null) {
+ return false;
+ }
+
+ showPopup(x, y, true, true);
+ return true;
+ }
+
+ /**
+ * Creates the popup and assigns cached properties.
+ *
+ * @return an initialized popup
+ */
+ @NonNull
+ private MenuPopup createPopup() {
+ final boolean enableCascadingSubmenus = mContext.getResources().getBoolean(
+ R.bool.abc_config_enableCascadingSubmenus);
+
+ final MenuPopup popup;
+ if (enableCascadingSubmenus) {
+ popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
+ mPopupStyleRes, mOverflowOnly);
+ } else {
+ popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
+ mPopupStyleRes, mOverflowOnly);
+ }
+
+ // Assign immutable properties.
+ popup.addMenu(mMenu);
+ popup.setOnDismissListener(mInternalOnDismissListener);
+
+ // Assign mutable properties. These may be reassigned later.
+ popup.setAnchorView(mAnchorView);
+ popup.setCallback(mPresenterCallback);
+ popup.setForceShowIcon(mForceShowIcon);
+ popup.setGravity(mDropDownGravity);
+
+ return popup;
+ }
+
+ private void showPopup(int xOffset, int yOffset, boolean useOffsets, boolean showTitle) {
+ final MenuPopup popup = getPopup();
+ popup.setShowTitle(showTitle);
+
+ if (useOffsets) {
+ // If the resolved drop-down gravity is RIGHT, the popup's right
+ // edge will be aligned with the anchor view. Adjust by the anchor
+ // width such that the top-right corner is at the X offset.
+ final int hgrav = GravityCompat.getAbsoluteGravity(mDropDownGravity,
+ ViewCompat.getLayoutDirection(mAnchorView)) & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if (hgrav == Gravity.RIGHT) {
+ xOffset -= mAnchorView.getWidth();
+ }
+
+ popup.setHorizontalOffset(xOffset);
+ popup.setVerticalOffset(yOffset);
+
+ // Set the transition epicenter to be roughly finger (or mouse
+ // cursor) sized and centered around the offset position. This
+ // will give the appearance that the window is emerging from
+ // the touch point.
+ final float density = mContext.getResources().getDisplayMetrics().density;
+ final int halfSize = (int) (TOUCH_EPICENTER_SIZE_DP * density / 2);
+ final Rect epicenter = new Rect(xOffset - halfSize, yOffset - halfSize,
+ xOffset + halfSize, yOffset + halfSize);
+ popup.setEpicenterBounds(epicenter);
+ }
+
+ popup.show();
+ }
+
+ /**
+ * Dismisses the popup, if showing.
+ */
+ @Override
public void dismiss() {
if (isShowing()) {
mPopup.dismiss();
}
}
- public void onDismiss() {
+ /**
+ * Called after the popup has been dismissed.
+ * <p>
+ * <strong>Note:</strong> Subclasses should call the super implementation
+ * last to ensure that any necessary tear down has occurred before the
+ * listener specified by {@link #setOnDismissListener(OnDismissListener)}
+ * is called.
+ */
+ protected void onDismiss() {
mPopup = null;
- mMenu.close();
- if (mTreeObserver != null) {
- if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
- mTreeObserver.removeGlobalOnLayoutListener(this);
- mTreeObserver = null;
+
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss();
}
}
@@ -186,223 +295,20 @@
}
@Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- MenuAdapter adapter = mAdapter;
- adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
- }
-
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
- dismiss();
- return true;
- }
- return false;
- }
-
- private int measureContentWidth() {
- // Menus don't tend to be long, so this is more sane than it looks.
- int maxWidth = 0;
- View itemView = null;
- int itemType = 0;
-
- final ListAdapter adapter = mAdapter;
- final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final int count = adapter.getCount();
- for (int i = 0; i < count; i++) {
- final int positionType = adapter.getItemViewType(i);
- if (positionType != itemType) {
- itemType = positionType;
- itemView = null;
- }
-
- if (mMeasureParent == null) {
- mMeasureParent = new FrameLayout(mContext);
- }
-
- itemView = adapter.getView(i, itemView, mMeasureParent);
- itemView.measure(widthMeasureSpec, heightMeasureSpec);
-
- final int itemWidth = itemView.getMeasuredWidth();
- if (itemWidth >= mPopupMaxWidth) {
- return mPopupMaxWidth;
- } else if (itemWidth > maxWidth) {
- maxWidth = itemWidth;
- }
- }
-
- return maxWidth;
- }
-
- @Override
- public void onGlobalLayout() {
- if (isShowing()) {
- final View anchor = mAnchorView;
- if (anchor == null || !anchor.isShown()) {
- dismiss();
- } else if (isShowing()) {
- // Recompute window size and position
- mPopup.show();
- }
- }
- }
-
- @Override
- public void initForMenu(Context context, MenuBuilder menu) {
- // Don't need to do anything; we added as a presenter in the constructor.
- }
-
- @Override
- public MenuView getMenuView(ViewGroup root) {
- throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
- }
-
- @Override
- public void updateMenuView(boolean cleared) {
- mHasContentWidth = false;
-
- if (mAdapter != null) {
- mAdapter.notifyDataSetChanged();
- }
- }
-
- @Override
- public void setCallback(Callback cb) {
+ public void setPresenterCallback(@Nullable MenuPresenter.Callback cb) {
mPresenterCallback = cb;
- }
-
- @Override
- public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
- if (subMenu.hasVisibleItems()) {
- MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView);
- subPopup.setCallback(mPresenterCallback);
-
- boolean preserveIconSpacing = false;
- final int count = subMenu.size();
- for (int i = 0; i < count; i++) {
- MenuItem childItem = subMenu.getItem(i);
- if (childItem.isVisible() && childItem.getIcon() != null) {
- preserveIconSpacing = true;
- break;
- }
- }
- subPopup.setForceShowIcon(preserveIconSpacing);
-
- if (subPopup.tryShow()) {
- if (mPresenterCallback != null) {
- mPresenterCallback.onOpenSubMenu(subMenu);
- }
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
- // Only care about the (sub)menu we're presenting.
- if (menu != mMenu) return;
-
- dismiss();
- if (mPresenterCallback != null) {
- mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+ if (mPopup != null) {
+ mPopup.setCallback(cb);
}
}
- @Override
- public boolean flagActionItems() {
- return false;
- }
-
- public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
- return false;
- }
-
- public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
- return false;
- }
-
- @Override
- public int getId() {
- return 0;
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- return null;
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
- }
-
- private class MenuAdapter extends BaseAdapter {
- private MenuBuilder mAdapterMenu;
- private int mExpandedIndex = -1;
-
- public MenuAdapter(MenuBuilder menu) {
- mAdapterMenu = menu;
- findExpandedIndex();
- }
-
- public int getCount() {
- ArrayList<MenuItemImpl> items = mOverflowOnly ?
- mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
- if (mExpandedIndex < 0) {
- return items.size();
- }
- return items.size() - 1;
- }
-
- public MenuItemImpl getItem(int position) {
- ArrayList<MenuItemImpl> items = mOverflowOnly ?
- mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
- if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
- position++;
- }
- return items.get(position);
- }
-
- public long getItemId(int position) {
- // Since a menu item's ID is optional, we'll use the position as an
- // ID for the item in the AdapterView
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
- }
-
- MenuView.ItemView itemView = (MenuView.ItemView) convertView;
- if (mForceShowIcon) {
- ((ListMenuItemView) convertView).setForceShowIcon(true);
- }
- itemView.initialize(getItem(position), 0);
- return convertView;
- }
-
- void findExpandedIndex() {
- final MenuItemImpl expandedItem = mMenu.getExpandedItem();
- if (expandedItem != null) {
- final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
- final int count = items.size();
- for (int i = 0; i < count; i++) {
- final MenuItemImpl item = items.get(i);
- if (item == expandedItem) {
- mExpandedIndex = i;
- return;
- }
- }
- }
- mExpandedIndex = -1;
- }
-
+ /**
+ * Listener used to proxy dismiss callbacks to the helper's owner.
+ */
+ private final OnDismissListener mInternalOnDismissListener = new OnDismissListener() {
@Override
- public void notifyDataSetChanged() {
- findExpandedIndex();
- super.notifyDataSetChanged();
+ public void onDismiss() {
+ MenuPopupHelper.this.onDismiss();
}
- }
+ };
}
-
diff --git a/v7/appcompat/src/android/support/v7/view/menu/ShowableListMenu.java b/v7/appcompat/src/android/support/v7/view/menu/ShowableListMenu.java
new file mode 100644
index 0000000..addebd7
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/ShowableListMenu.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.view.menu;
+
+import android.widget.ListView;
+
+/**
+ * A list menu which can be shown and hidden and which is internally represented by a ListView.
+ *
+ * @hide
+ */
+public interface ShowableListMenu {
+ public void show();
+
+ public void dismiss();
+
+ public boolean isShowing();
+
+ /**
+ * @return The internal ListView for the visible menu.
+ */
+ public ListView getListView();
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/StandardMenuPopup.java b/v7/appcompat/src/android/support/v7/view/menu/StandardMenuPopup.java
new file mode 100644
index 0000000..2396651
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/StandardMenuPopup.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2015 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.view.menu;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Parcelable;
+import android.support.v7.appcompat.R;
+import android.support.v7.widget.MenuPopupWindow;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.View.OnKeyListener;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.FrameLayout;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
+import android.widget.TextView;
+
+/**
+ * A standard menu popup in which when a submenu is opened, it replaces its parent menu in the
+ * viewport.
+ */
+final class StandardMenuPopup extends MenuPopup implements OnDismissListener, OnItemClickListener,
+ MenuPresenter, OnKeyListener {
+
+ private final Context mContext;
+
+ private final MenuBuilder mMenu;
+ private final MenuAdapter mAdapter;
+ private final boolean mOverflowOnly;
+ private final int mPopupMaxWidth;
+ private final int mPopupStyleAttr;
+ private final int mPopupStyleRes;
+ // The popup window is final in order to couple its lifecycle to the lifecycle of the
+ // StandardMenuPopup.
+ private final MenuPopupWindow mPopup;
+
+ private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (isShowing()) {
+ final View anchor = mShownAnchorView;
+ if (anchor == null || !anchor.isShown()) {
+ dismiss();
+ } else if (isShowing()) {
+ // Recompute window size and position
+ mPopup.show();
+ }
+ }
+ }
+ };
+
+ private PopupWindow.OnDismissListener mOnDismissListener;
+
+ private View mAnchorView;
+ private View mShownAnchorView;
+ private Callback mPresenterCallback;
+ private ViewTreeObserver mTreeObserver;
+
+ /** Whether the popup has been dismissed. Once dismissed, it cannot be opened again. */
+ private boolean mWasDismissed;
+
+ /** Whether the cached content width value is valid. */
+ private boolean mHasContentWidth;
+
+ /** Cached content width. */
+ private int mContentWidth;
+
+ private int mDropDownGravity = Gravity.NO_GRAVITY;
+
+ private int mXOffset;
+ private int mYOffset;
+ private boolean mShowTitle;
+
+ public StandardMenuPopup(Context context, MenuBuilder menu, View anchorView, int popupStyleAttr,
+ int popupStyleRes, boolean overflowOnly) {
+ mContext = context;
+ mMenu = menu;
+ mOverflowOnly = overflowOnly;
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly);
+ mPopupStyleAttr = popupStyleAttr;
+ mPopupStyleRes = popupStyleRes;
+
+ final Resources res = context.getResources();
+ mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
+ res.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth));
+
+ mAnchorView = anchorView;
+
+ mPopup = new MenuPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
+
+ // Present the menu using our context, not the menu builder's context.
+ menu.addMenuPresenter(this, context);
+ }
+
+ @Override
+ public void setForceShowIcon(boolean forceShow) {
+ mAdapter.setForceShowIcon(forceShow);
+ }
+
+ @Override
+ public void setGravity(int gravity) {
+ mDropDownGravity = gravity;
+ }
+
+ private boolean tryShow() {
+ if (isShowing()) {
+ return true;
+ }
+
+ if (mWasDismissed || mAnchorView == null) {
+ return false;
+ }
+
+ mShownAnchorView = mAnchorView;
+
+ mPopup.setOnDismissListener(this);
+ mPopup.setOnItemClickListener(this);
+ mPopup.setModal(true);
+
+ final View anchor = mShownAnchorView;
+ final boolean addGlobalListener = mTreeObserver == null;
+ mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
+ if (addGlobalListener) {
+ mTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
+ }
+ mPopup.setAnchorView(anchor);
+ mPopup.setDropDownGravity(mDropDownGravity);
+
+ if (!mHasContentWidth) {
+ mContentWidth = measureIndividualMenuWidth(mAdapter, null, mContext, mPopupMaxWidth);
+ mHasContentWidth = true;
+ }
+
+ mPopup.setContentWidth(mContentWidth);
+ mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+ mPopup.setHorizontalOffset(mXOffset);
+ mPopup.setVerticalOffset(mYOffset);
+ mPopup.setEpicenterBounds(getEpicenterBounds());
+ mPopup.show();
+
+ final ListView listView = mPopup.getListView();
+ listView.setOnKeyListener(this);
+
+ if (mShowTitle && mMenu.getHeaderTitle() != null) {
+ FrameLayout titleItemView =
+ (FrameLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.abc_popup_menu_header_item_layout, listView, false);
+ TextView titleView = (TextView) titleItemView.findViewById(android.R.id.title);
+ if (titleView != null) {
+ titleView.setText(mMenu.getHeaderTitle());
+ }
+ titleItemView.setEnabled(false);
+ listView.addHeaderView(titleItemView, null, false);
+ }
+
+ // Since addHeaderView() needs to be called before setAdapter() pre-v14, we have to set the
+ // adapter as late as possible, and then call show again to update
+ mPopup.setAdapter(mAdapter);
+ mPopup.show();
+
+ return true;
+ }
+
+ @Override
+ public void show() {
+ if (!tryShow()) {
+ throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ if (isShowing()) {
+ mPopup.dismiss();
+ }
+ }
+
+ @Override
+ public void addMenu(MenuBuilder menu) {
+ // No-op: standard implementation has only one menu which is set in the constructor.
+ }
+
+ @Override
+ public boolean isShowing() {
+ return !mWasDismissed && mPopup.isShowing();
+ }
+
+ @Override
+ public void onDismiss() {
+ mWasDismissed = true;
+ mMenu.close();
+
+ if (mTreeObserver != null) {
+ if (!mTreeObserver.isAlive()) mTreeObserver = mShownAnchorView.getViewTreeObserver();
+ mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
+ mTreeObserver = null;
+ }
+ mOnDismissListener.onDismiss();
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ mHasContentWidth = false;
+
+ if (mAdapter != null) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ mPresenterCallback = cb;
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (subMenu.hasVisibleItems()) {
+ final MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu,
+ mShownAnchorView, mOverflowOnly, mPopupStyleAttr, mPopupStyleRes);
+ subPopup.setPresenterCallback(mPresenterCallback);
+ subPopup.setForceShowIcon(mAdapter.getForceShowIcon());
+
+ // Show the new sub-menu popup at the same location as this popup.
+ if (subPopup.tryShow(mXOffset, mYOffset)) {
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onOpenSubMenu(subMenu);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ // Only care about the (sub)menu we're presenting.
+ if (menu != mMenu) return;
+
+ dismiss();
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ return null;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ }
+
+ @Override
+ public void setAnchorView(View anchor) {
+ mAnchorView = anchor;
+ }
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
+ dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setOnDismissListener(OnDismissListener listener) {
+ mOnDismissListener = listener;
+ }
+
+ @Override
+ public ListView getListView() {
+ return mPopup.getListView();
+ }
+
+
+ @Override
+ public void setHorizontalOffset(int x) {
+ mXOffset = x;
+ }
+
+ @Override
+ public void setVerticalOffset(int y) {
+ mYOffset = y;
+ }
+
+ @Override
+ public void setShowTitle(boolean showTitle) {
+ mShowTitle = showTitle;
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java b/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
index e0e1a83..6e2b0df 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
@@ -22,6 +22,8 @@
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.ActionProvider;
import android.support.v4.view.GravityCompat;
@@ -34,6 +36,7 @@
import android.support.v7.view.menu.MenuItemImpl;
import android.support.v7.view.menu.MenuPopupHelper;
import android.support.v7.view.menu.MenuView;
+import android.support.v7.view.menu.ShowableListMenu;
import android.support.v7.view.menu.SubMenuBuilder;
import android.util.SparseBooleanArray;
import android.view.MenuItem;
@@ -86,7 +89,7 @@
}
@Override
- public void initForMenu(Context context, MenuBuilder menu) {
+ public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
super.initForMenu(context, menu);
final Resources res = context.getResources();
@@ -132,8 +135,7 @@
public void onConfigurationChanged(Configuration newConfig) {
if (!mMaxItemsSet) {
- mMaxItems = mContext.getResources().getInteger(
- R.integer.abc_max_action_buttons);
+ mMaxItems = ActionBarPolicy.get(mContext).getMaxActionButtons();
}
if (mMenu != null) {
mMenu.onItemsChanged(true);
@@ -180,8 +182,11 @@
@Override
public MenuView getMenuView(ViewGroup root) {
+ MenuView oldMenuView = mMenuView;
MenuView result = super.getMenuView(root);
- ((ActionMenuView) result).setPresenter(this);
+ if (oldMenuView != result) {
+ ((ActionMenuView) result).setPresenter(this);
+ }
return result;
}
@@ -288,14 +293,29 @@
}
View anchor = findViewForItem(topSubMenu.getItem());
if (anchor == null) {
- if (mOverflowButton == null) return false;
- anchor = mOverflowButton;
+ // This means the submenu was opened from an overflow menu item, indicating the
+ // MenuPopupHelper will handle opening the submenu via its MenuPopup. Return false to
+ // ensure that the MenuPopup acts as presenter for the submenu, and acts on its
+ // responsibility to display the new submenu.
+ return false;
}
mOpenSubMenuId = subMenu.getItem().getItemId();
- mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
- mActionButtonPopup.setAnchorView(anchor);
+
+ boolean preserveIconSpacing = false;
+ final int count = subMenu.size();
+ for (int i = 0; i < count; i++) {
+ MenuItem childItem = subMenu.getItem(i);
+ if (childItem.isVisible() && childItem.getIcon() != null) {
+ preserveIconSpacing = true;
+ break;
+ }
+ }
+
+ mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu, anchor);
+ mActionButtonPopup.setForceShowIcon(preserveIconSpacing);
mActionButtonPopup.show();
+
super.onSubMenuSelected(subMenu);
return true;
}
@@ -398,8 +418,16 @@
}
public boolean flagActionItems() {
- final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
- final int itemsSize = visibleItems.size();
+ final ArrayList<MenuItemImpl> visibleItems;
+ final int itemsSize;
+ if (mMenu != null) {
+ visibleItems = mMenu.getVisibleItems();
+ itemsSize = visibleItems.size();
+ } else {
+ visibleItems = null;
+ itemsSize = 0;
+ }
+
int maxActions = mMaxItems;
int widthLimit = mActionItemWidthLimit;
final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
@@ -561,8 +589,8 @@
if (isVisible) {
// Not a submenu, but treat it like one.
super.onSubMenuSelected(null);
- } else {
- mMenu.close(false);
+ } else if (mMenu != null) {
+ mMenu.close(false /* closeAllMenus */);
}
}
@@ -615,9 +643,9 @@
setVisibility(VISIBLE);
setEnabled(true);
- setOnTouchListener(new ListPopupWindow.ForwardingListener(this) {
+ setOnTouchListener(new ForwardingListener(this) {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
if (mOverflowPopup == null) {
return null;
}
@@ -691,31 +719,27 @@
}
private class OverflowPopup extends MenuPopupHelper {
-
public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
boolean overflowOnly) {
super(context, menu, anchorView, overflowOnly, R.attr.actionOverflowMenuStyle);
setGravity(GravityCompat.END);
- setCallback(mPopupPresenterCallback);
+ setPresenterCallback(mPopupPresenterCallback);
}
@Override
- public void onDismiss() {
- super.onDismiss();
+ protected void onDismiss() {
if (mMenu != null) {
mMenu.close();
}
mOverflowPopup = null;
+
+ super.onDismiss();
}
}
private class ActionButtonSubmenu extends MenuPopupHelper {
- private SubMenuBuilder mSubMenu;
-
- public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
- super(context, subMenu, null, false,
- R.attr.actionOverflowMenuStyle);
- mSubMenu = subMenu;
+ public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu, View anchorView) {
+ super(context, subMenu, anchorView, false, R.attr.actionOverflowMenuStyle);
MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
if (!item.isActionButton()) {
@@ -723,30 +747,19 @@
setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
}
- setCallback(mPopupPresenterCallback);
-
- boolean preserveIconSpacing = false;
- final int count = subMenu.size();
- for (int i = 0; i < count; i++) {
- MenuItem childItem = subMenu.getItem(i);
- if (childItem.isVisible() && childItem.getIcon() != null) {
- preserveIconSpacing = true;
- break;
- }
- }
- setForceShowIcon(preserveIconSpacing);
+ setPresenterCallback(mPopupPresenterCallback);
}
@Override
- public void onDismiss() {
- super.onDismiss();
+ protected void onDismiss() {
mActionButtonPopup = null;
mOpenSubMenuId = 0;
+
+ super.onDismiss();
}
}
private class PopupPresenterCallback implements Callback {
-
@Override
public boolean onOpenSubMenu(MenuBuilder subMenu) {
if (subMenu == null) return false;
@@ -759,7 +772,7 @@
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
if (menu instanceof SubMenuBuilder) {
- ((SubMenuBuilder) menu).getRootMenu().close(false);
+ menu.getRootMenu().close(false /* closeAllMenus */);
}
final Callback cb = getCallback();
if (cb != null) {
@@ -776,7 +789,9 @@
}
public void run() {
- mMenu.changeMenuMode();
+ if (mMenu != null) {
+ mMenu.changeMenuMode();
+ }
final View menuView = (View) mMenuView;
if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
mOverflowPopup = mPopup;
@@ -787,7 +802,7 @@
private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
}
}
diff --git a/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java b/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java
index 3ea3a2e..8d5c45d 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java
@@ -27,6 +27,7 @@
import android.support.v4.view.ActionProvider;
import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
+import android.support.v7.view.menu.ShowableListMenu;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -237,9 +238,9 @@
final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button);
expandButton.setOnClickListener(mCallbacks);
- expandButton.setOnTouchListener(new ListPopupWindow.ForwardingListener(expandButton) {
+ expandButton.setOnTouchListener(new ForwardingListener(expandButton) {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
return getListPopupWindow();
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
index 62c2ab7..9f35443 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
@@ -17,7 +17,6 @@
package android.support.v7.widget;
import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@@ -43,7 +42,7 @@
}
void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
- TypedArray a = mView.getContext().obtainStyledAttributes(attrs,
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
R.styleable.ViewBackgroundHelper, defStyleAttr, 0);
try {
if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) {
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatColorStateListInflater.java b/v7/appcompat/src/android/support/v7/widget/AppCompatColorStateListInflater.java
new file mode 100644
index 0000000..3024280
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatColorStateListInflater.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2015 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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.graphics.ColorUtils;
+import android.support.v7.appcompat.R;
+import android.util.AttributeSet;
+import android.util.StateSet;
+import android.util.Xml;
+
+import java.io.IOException;
+
+class AppCompatColorStateListInflater {
+
+ private static final int DEFAULT_COLOR = Color.RED;
+
+ /**
+ * Creates a ColorStateList from an XML document using given a set of
+ * {@link Resources} and a {@link Theme}.
+ *
+ * @param r Resources against which the ColorStateList should be inflated.
+ * @param parser Parser for the XML document defining the ColorStateList.
+ * @param theme Optional theme to apply to the color state list, may be
+ * {@code null}.
+ * @return A new color state list.
+ */
+ @NonNull
+ public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @Nullable Resources.Theme theme) throws XmlPullParserException, IOException {
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Seek parser to start tag.
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ return createFromXmlInner(r, parser, attrs, theme);
+ }
+
+ /**
+ * Create from inside an XML document. Called on a parser positioned at a
+ * tag in an XML document, tries to create a ColorStateList from that tag.
+ *
+ * @throws XmlPullParserException if the current tag is not <selector>
+ * @return A new color state list for the current tag.
+ */
+ @NonNull
+ private static ColorStateList createFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs,
+ @Nullable Resources.Theme theme)
+ throws XmlPullParserException, IOException {
+ final String name = parser.getName();
+ if (!name.equals("selector")) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription() + ": invalid color state list tag " + name);
+ }
+
+ return inflate(r, parser, attrs, theme);
+ }
+
+ /**
+ * Fill in this object based on the contents of an XML "selector" element.
+ */
+ private static ColorStateList inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Resources.Theme theme)
+ throws XmlPullParserException, IOException {
+ final int innerDepth = parser.getDepth() + 1;
+ int depth;
+ int type;
+ int defaultColor = DEFAULT_COLOR;
+
+ int[][] stateSpecList = new int[20][];
+ int[] colorList = new int[stateSpecList.length];
+ int listSize = 0;
+
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG || depth > innerDepth
+ || !parser.getName().equals("item")) {
+ continue;
+ }
+
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorStateListItem);
+ final int baseColor = a.getColor(R.styleable.ColorStateListItem_android_color,
+ Color.MAGENTA);
+
+ float alphaMod = 1.0f;
+ if (a.hasValue(R.styleable.ColorStateListItem_android_alpha)) {
+ alphaMod = a.getFloat(R.styleable.ColorStateListItem_android_alpha, alphaMod);
+ } else if (a.hasValue(R.styleable.ColorStateListItem_alpha)) {
+ alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, alphaMod);
+ }
+
+ a.recycle();
+
+ // Parse all unrecognized attributes as state specifiers.
+ int j = 0;
+ final int numAttrs = attrs.getAttributeCount();
+ int[] stateSpec = new int[numAttrs];
+ for (int i = 0; i < numAttrs; i++) {
+ final int stateResId = attrs.getAttributeNameResource(i);
+ if (stateResId != android.R.attr.color && stateResId != android.R.attr.alpha
+ && stateResId != R.attr.alpha) {
+ // Unrecognized attribute, add to state set
+ stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
+ ? stateResId : -stateResId;
+ }
+ }
+ stateSpec = StateSet.trimStateSet(stateSpec, j);
+
+ // Apply alpha modulation. If we couldn't resolve the color or
+ // alpha yet, the default values leave us enough information to
+ // modulate again during applyTheme().
+ final int color = modulateColorAlpha(baseColor, alphaMod);
+ if (listSize == 0 || stateSpec.length == 0) {
+ defaultColor = color;
+ }
+
+ colorList = GrowingArrayUtils.append(colorList, listSize, color);
+ stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
+ listSize++;
+ }
+
+ int[] colors = new int[listSize];
+ int[][] stateSpecs = new int[listSize][];
+ System.arraycopy(colorList, 0, colors, 0, listSize);
+ System.arraycopy(stateSpecList, 0, stateSpecs, 0, listSize);
+
+ return new ColorStateList(stateSpecs, colors);
+ }
+
+ private static TypedArray obtainAttributes(Resources res, Resources.Theme theme,
+ AttributeSet set, int[] attrs) {
+ return theme == null ? res.obtainAttributes(set, attrs)
+ : theme.obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
+ private static int modulateColorAlpha(int color, float alphaMod) {
+ return ColorUtils.setAlphaComponent(color, Math.round(Color.alpha(color) * alphaMod));
+ }
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
index 1c7dd75..e5c19ab 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
@@ -54,6 +54,7 @@
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
+import static android.support.v7.widget.ColorStateListUtils.getColorStateList;
import static android.support.v7.widget.ThemeUtils.getDisabledThemeAttrColor;
import static android.support.v7.widget.ThemeUtils.getThemeAttrColor;
import static android.support.v7.widget.ThemeUtils.getThemeAttrColorStateList;
@@ -463,11 +464,11 @@
if (tint == null) {
// ...if the cache did not contain a color state list, try and create one
if (resId == R.drawable.abc_edit_text_material) {
- tint = createEditTextColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_edittext);
} else if (resId == R.drawable.abc_switch_track_mtrl_alpha) {
- tint = createSwitchTrackColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_switch_track);
} else if (resId == R.drawable.abc_switch_thumb_material) {
- tint = createSwitchThumbColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_switch_thumb);
} else if (resId == R.drawable.abc_btn_default_mtrl_shape
|| resId == R.drawable.abc_btn_borderless_material) {
tint = createDefaultButtonColorStateList(context);
@@ -475,15 +476,15 @@
tint = createColoredButtonColorStateList(context);
} else if (resId == R.drawable.abc_spinner_mtrl_am_alpha
|| resId == R.drawable.abc_spinner_textfield_background_material) {
- tint = createSpinnerColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_spinner);
} else if (arrayContains(TINT_COLOR_CONTROL_NORMAL, resId)) {
tint = getThemeAttrColorStateList(context, R.attr.colorControlNormal);
} else if (arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)) {
- tint = createDefaultColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_default);
} else if (arrayContains(TINT_CHECKABLE_BUTTON_LIST, resId)) {
- tint = createCheckableButtonColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_btn_checkable);
} else if (resId == R.drawable.abc_seekbar_thumb_material) {
- tint = createSeekbarThumbColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_seek_thumb);
}
if (tint != null) {
@@ -514,164 +515,6 @@
themeTints.append(resId, tintList);
}
- private ColorStateList createDefaultColorStateList(Context context) {
- /**
- * Generate the default color state list which uses the colorControl attributes.
- * Order is important here. The default enabled state needs to go at the bottom.
- */
-
- final int colorControlNormal = getThemeAttrColor(context, R.attr.colorControlNormal);
- final int colorControlActivated = getThemeAttrColor(context, R.attr.colorControlActivated);
-
- final int[][] states = new int[7][];
- final int[] colors = new int[7];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- states[i] = ThemeUtils.FOCUSED_STATE_SET;
- colors[i] = colorControlActivated;
- i++;
-
- states[i] = ThemeUtils.ACTIVATED_STATE_SET;
- colors[i] = colorControlActivated;
- i++;
-
- states[i] = ThemeUtils.PRESSED_STATE_SET;
- colors[i] = colorControlActivated;
- i++;
-
- states[i] = ThemeUtils.CHECKED_STATE_SET;
- colors[i] = colorControlActivated;
- i++;
-
- states[i] = ThemeUtils.SELECTED_STATE_SET;
- colors[i] = colorControlActivated;
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = colorControlNormal;
- i++;
-
- return new ColorStateList(states, colors);
- }
-
- private ColorStateList createCheckableButtonColorStateList(Context context) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- states[i] = ThemeUtils.CHECKED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- return new ColorStateList(states, colors);
- }
-
- private ColorStateList createSwitchTrackColorStateList(Context context) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getThemeAttrColor(context, android.R.attr.colorForeground, 0.1f);
- i++;
-
- states[i] = ThemeUtils.CHECKED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated, 0.3f);
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, android.R.attr.colorForeground, 0.3f);
- i++;
-
- return new ColorStateList(states, colors);
- }
-
- private ColorStateList createSwitchThumbColorStateList(Context context) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
-
- final ColorStateList thumbColor = getThemeAttrColorStateList(context,
- R.attr.colorSwitchThumbNormal);
-
- if (thumbColor != null && thumbColor.isStateful()) {
- // If colorSwitchThumbNormal is a valid ColorStateList, extract the default and
- // disabled colors from it
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = thumbColor.getColorForState(states[i], 0);
- i++;
-
- states[i] = ThemeUtils.CHECKED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = thumbColor.getDefaultColor();
- i++;
- } else {
- // Else we'll use an approximation using the default disabled alpha
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorSwitchThumbNormal);
- i++;
-
- states[i] = ThemeUtils.CHECKED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorSwitchThumbNormal);
- i++;
- }
-
- return new ColorStateList(states, colors);
- }
-
- private ColorStateList createEditTextColorStateList(Context context) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- states[i] = ThemeUtils.NOT_PRESSED_OR_FOCUSED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- return new ColorStateList(states, colors);
- }
-
private ColorStateList createDefaultButtonColorStateList(Context context) {
return createButtonColorStateList(context, R.attr.colorButtonNormal);
}
@@ -709,44 +552,6 @@
return new ColorStateList(states, colors);
}
- private ColorStateList createSpinnerColorStateList(Context context) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- states[i] = ThemeUtils.NOT_PRESSED_OR_FOCUSED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- return new ColorStateList(states, colors);
- }
-
- private ColorStateList createSeekbarThumbColorStateList(Context context) {
- final int[][] states = new int[2][];
- final int[] colors = new int[2];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- return new ColorStateList(states, colors);
- }
-
private static class ColorFilterLruCache extends LruCache<Integer, PorterDuffColorFilter> {
public ColorFilterLruCache(int maxSize) {
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java b/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java
index 964ea76..28c1b31 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java
@@ -31,6 +31,7 @@
import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
import android.support.v7.view.ContextThemeWrapper;
+import android.support.v7.view.menu.ShowableListMenu;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -83,7 +84,7 @@
private Context mPopupContext;
/** Forwarding listener used to implement drag-to-open. */
- private ListPopupWindow.ForwardingListener mForwardingListener;
+ private ForwardingListener mForwardingListener;
/** Temporary holder for setAdapter() calls from the super constructor. */
private SpinnerAdapter mTempAdapter;
@@ -249,9 +250,9 @@
pa.recycle();
mPopup = popup;
- mForwardingListener = new ListPopupWindow.ForwardingListener(this) {
+ mForwardingListener = new ForwardingListener(this) {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
return popup;
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
index 16bb5cf..9d7f663 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
@@ -25,6 +25,7 @@
import android.support.v7.text.AllCapsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.widget.TextView;
class AppCompatTextHelper {
@@ -36,10 +37,13 @@
return new AppCompatTextHelper(textView);
}
- private static final int[] VIEW_ATTRS = {android.R.attr.textAppearance,
- android.R.attr.drawableLeft, android.R.attr.drawableTop,
- android.R.attr.drawableRight, android.R.attr.drawableBottom };
- private static final int[] TEXT_APPEARANCE_ATTRS = {R.attr.textAllCaps};
+ private static final int[] VIEW_ATTRS = {
+ android.R.attr.textAppearance,
+ android.R.attr.drawableLeft,
+ android.R.attr.drawableTop,
+ android.R.attr.drawableRight,
+ android.R.attr.drawableBottom
+ };
final TextView mView;
@@ -57,9 +61,9 @@
final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
// First read the TextAppearance style id
- TypedArray a = context.obtainStyledAttributes(attrs, VIEW_ATTRS, defStyleAttr, 0);
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
+ VIEW_ATTRS, defStyleAttr, 0);
final int ap = a.getResourceId(0, -1);
-
// Now read the compound drawable and grab any tints
if (a.hasValue(1)) {
mDrawableLeftTint = createTintInfo(context, drawableManager, a.getResourceId(1, 0));
@@ -75,45 +79,75 @@
}
a.recycle();
- if (!(mView.getTransformationMethod() instanceof PasswordTransformationMethod)) {
- // PasswordTransformationMethod wipes out all other TransformationMethod instances
- // in TextView's constructor, so we should only set a new transformation method
- // if we don't have a PasswordTransformationMethod currently...
+ // PasswordTransformationMethod wipes out all other TransformationMethod instances
+ // in TextView's constructor, so we should only set a new transformation method
+ // if we don't have a PasswordTransformationMethod currently...
+ final boolean hasPwdTm =
+ mView.getTransformationMethod() instanceof PasswordTransformationMethod;
+ boolean allCaps = false;
- boolean allCaps = false;
-
- // First check TextAppearance's textAllCaps value
- if (ap != -1) {
- TypedArray appearance = context
- .obtainStyledAttributes(ap, R.styleable.TextAppearance);
- if (appearance.hasValue(R.styleable.TextAppearance_textAllCaps)) {
- allCaps = appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
- }
- appearance.recycle();
+ // First check TextAppearance's textAllCaps value
+ if (ap != -1) {
+ a = TintTypedArray.obtainStyledAttributes(context, ap, R.styleable.TextAppearance);
+ if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
+ allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
}
-
- // Now read the style's value
- a = context.obtainStyledAttributes(attrs, TEXT_APPEARANCE_ATTRS, defStyleAttr, 0);
- if (a.hasValue(0)) {
- allCaps = a.getBoolean(0, false);
+ if (Build.VERSION.SDK_INT < 23 && TypedValue.TYPE_STRING
+ == a.getType(R.styleable.TextAppearance_android_textColor)) {
+ // If we're running on < API 23, have a color set and it's a String reference,
+ // it may contain theme references so let's re-set using our own inflater
+ final ColorStateList textColor
+ = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
+ if (textColor != null) {
+ mView.setTextColor(textColor);
+ }
}
a.recycle();
+ }
- if (allCaps) {
- setAllCaps(true);
+ // Now read the style's values
+ a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.TextAppearance,
+ defStyleAttr, 0);
+ if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
+ allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
+ }
+ if (Build.VERSION.SDK_INT < 23 && TypedValue.TYPE_STRING ==
+ a.getType(R.styleable.TextAppearance_android_textColor)) {
+ // If we're running on < API 23, have a color set and it's a String reference, it may
+ // contain theme references so let's re-set using our own inflater
+ final ColorStateList textColor =
+ a.getColorStateList(R.styleable.TextAppearance_android_textColor);
+ if (textColor != null) {
+ mView.setTextColor(textColor);
}
}
+ a.recycle();
+
+ if (!hasPwdTm && allCaps) {
+ setAllCaps(true);
+ }
}
void onSetTextAppearance(Context context, int resId) {
- TypedArray appearance = context.obtainStyledAttributes(resId, TEXT_APPEARANCE_ATTRS);
- if (appearance.getBoolean(0, false)) {
+ final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
+ resId, R.styleable.TextAppearance);
+ if (a.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
// This follows the logic in TextView.setTextAppearance that serves as an "overlay"
// on the current state of the TextView. Here we only allow turning all-caps on when
// the passed style has textAllCaps attribute set to true.
setAllCaps(true);
}
- appearance.recycle();
+ if (Build.VERSION.SDK_INT < 23 && TypedValue.TYPE_STRING
+ == a.getType(R.styleable.TextAppearance_android_textColor)) {
+ // If we're running on < API 23, have a color set and it's a String reference,
+ // it may contain theme references so let's re-set using our own inflater
+ final ColorStateList textColor
+ = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
+ if (textColor != null) {
+ mView.setTextColor(textColor);
+ }
+ }
+ a.recycle();
}
void setAllCaps(boolean allCaps) {
diff --git a/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
index 1b8c9a5..6ddc1de 100644
--- a/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
+++ b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
@@ -32,6 +32,9 @@
* @hide
*/
public class ButtonBarLayout extends LinearLayout {
+ // Whether to allow vertically stacked button bars. This is disabled for
+ // configurations with a small (e.g. less than 320dp) screen height. -->
+ private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
/** Whether the current configuration allows stacking. */
private boolean mAllowStacking;
@@ -39,8 +42,12 @@
public ButtonBarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
+ final boolean allowStackingDefault =
+ context.getResources().getConfiguration().screenHeightDp
+ >= ALLOW_STACKING_MIN_HEIGHT_DP;
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
- mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, false);
+ mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
+ allowStackingDefault);
ta.recycle();
}
@@ -124,4 +131,4 @@
private boolean isStacked() {
return getOrientation() == LinearLayout.VERTICAL;
}
-}
\ No newline at end of file
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/ColorStateListUtils.java b/v7/appcompat/src/android/support/v7/widget/ColorStateListUtils.java
new file mode 100644
index 0000000..d5ff6c9
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/ColorStateListUtils.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 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 org.xmlpull.v1.XmlPullParser;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.os.Build;
+import android.support.annotation.ColorRes;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import android.util.TypedValue;
+
+class ColorStateListUtils {
+
+ private static final String LOG_TAG = "ColorStateListUtils";
+ private static final ThreadLocal<TypedValue> TL_TYPED_VALUE = new ThreadLocal<>();
+
+ private ColorStateListUtils() {}
+
+ /**
+ * Returns the {@link ColorStateList} from the given resource. The resource can include
+ * themeable attributes.
+ */
+ static ColorStateList getColorStateList(@NonNull Context context, @ColorRes int resId) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ // On M+ we can use the framework
+ return context.getColorStateList(resId);
+ }
+ // Before that, we'll try and inflate it manually
+ final ColorStateList csl = inflateColorStateList(context, resId);
+ if (csl != null) {
+ return csl;
+ }
+ // If we reach here then we couldn't inflate it, so let the framework handle it
+ return ContextCompat.getColorStateList(context, resId);
+ }
+
+ /**
+ * Inflates a {@link ColorStateList} from resources, honouring theme attributes.
+ */
+ private static ColorStateList inflateColorStateList(Context context, int resId) {
+ if (isColorInt(context, resId)) {
+ // The resource is a color int, we can't handle it so return null
+ return null;
+ }
+
+ final Resources r = context.getResources();
+ final XmlPullParser xml = r.getXml(resId);
+ try {
+ return AppCompatColorStateListInflater.createFromXml(r, xml, context.getTheme());
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Failed to inflate ColorStateList, leaving it to the framework", e);
+ }
+ return null;
+ }
+
+ static boolean isColorInt(Context context, int resId) {
+ final Resources r = context.getResources();
+
+ final TypedValue value = getTypedValue();
+ r.getValue(resId, value, true);
+
+ return value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+ && value.type <= TypedValue.TYPE_LAST_COLOR_INT;
+ }
+
+ private static TypedValue getTypedValue() {
+ TypedValue tv = TL_TYPED_VALUE.get();
+ if (tv == null) {
+ tv = new TypedValue();
+ TL_TYPED_VALUE.set(tv);
+ }
+ return tv;
+ }
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/DropDownListView.java b/v7/appcompat/src/android/support/v7/widget/DropDownListView.java
new file mode 100644
index 0000000..f6a75f7
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/DropDownListView.java
@@ -0,0 +1,277 @@
+/*
+ * 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 android.content.Context;
+import android.os.Build;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.widget.ListViewAutoScrollHelper;
+import android.support.v7.appcompat.R;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
+ * make sure the list uses the appropriate drawables and states when
+ * displayed on screen within a drop down. The focus is never actually
+ * passed to the drop down in this mode; the list only looks focused.</p>
+ */
+class DropDownListView extends ListViewCompat {
+
+ /*
+ * WARNING: This is a workaround for a touch mode issue.
+ *
+ * Touch mode is propagated lazily to windows. This causes problems in
+ * the following scenario:
+ * - Type something in the AutoCompleteTextView and get some results
+ * - Move down with the d-pad to select an item in the list
+ * - Move up with the d-pad until the selection disappears
+ * - Type more text in the AutoCompleteTextView *using the soft keyboard*
+ * and get new results; you are now in touch mode
+ * - The selection comes back on the first item in the list, even though
+ * the list is supposed to be in touch mode
+ *
+ * Using the soft keyboard triggers the touch mode change but that change
+ * is propagated to our window only after the first list layout, therefore
+ * after the list attempts to resurrect the selection.
+ *
+ * The trick to work around this issue is to pretend the list is in touch
+ * mode when we know that the selection should not appear, that is when
+ * we know the user moved the selection away from the list.
+ *
+ * This boolean is set to true whenever we explicitly hide the list's
+ * selection and reset to false whenever we know the user moved the
+ * selection back to the list.
+ *
+ * When this boolean is true, isInTouchMode() returns true, otherwise it
+ * returns super.isInTouchMode().
+ */
+ private boolean mListSelectionHidden;
+
+ /**
+ * True if this wrapper should fake focus.
+ */
+ private boolean mHijackFocus;
+
+ /** Whether to force drawing of the pressed state selector. */
+ private boolean mDrawsInPressedState;
+
+ /** Current drag-to-open click animation, if any. */
+ private ViewPropertyAnimatorCompat mClickAnimation;
+
+ /** Helper for drag-to-open auto scrolling. */
+ private ListViewAutoScrollHelper mScrollHelper;
+
+ /**
+ * <p>Creates a new list view wrapper.</p>
+ *
+ * @param context this view's context
+ */
+ public DropDownListView(Context context, boolean hijackFocus) {
+ super(context, null, R.attr.dropDownListViewStyle);
+ mHijackFocus = hijackFocus;
+ setCacheColorHint(0); // Transparent, since the background drawable could be anything.
+ }
+
+ /**
+ * Handles forwarded events.
+ *
+ * @param activePointerId id of the pointer that activated forwarding
+ * @return whether the event was handled
+ */
+ public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
+ boolean handledEvent = true;
+ boolean clearPressedItem = false;
+
+ final int actionMasked = MotionEventCompat.getActionMasked(event);
+ switch (actionMasked) {
+ case MotionEvent.ACTION_CANCEL:
+ handledEvent = false;
+ break;
+ case MotionEvent.ACTION_UP:
+ handledEvent = false;
+ // $FALL-THROUGH$
+ case MotionEvent.ACTION_MOVE:
+ final int activeIndex = event.findPointerIndex(activePointerId);
+ if (activeIndex < 0) {
+ handledEvent = false;
+ break;
+ }
+
+ final int x = (int) event.getX(activeIndex);
+ final int y = (int) event.getY(activeIndex);
+ final int position = pointToPosition(x, y);
+ if (position == INVALID_POSITION) {
+ clearPressedItem = true;
+ break;
+ }
+
+ final View child = getChildAt(position - getFirstVisiblePosition());
+ setPressedItem(child, position, x, y);
+ handledEvent = true;
+
+ if (actionMasked == MotionEvent.ACTION_UP) {
+ clickPressedItem(child, position);
+ }
+ break;
+ }
+
+ // Failure to handle the event cancels forwarding.
+ if (!handledEvent || clearPressedItem) {
+ clearPressedItem();
+ }
+
+ // Manage automatic scrolling.
+ if (handledEvent) {
+ if (mScrollHelper == null) {
+ mScrollHelper = new ListViewAutoScrollHelper(this);
+ }
+ mScrollHelper.setEnabled(true);
+ mScrollHelper.onTouch(this, event);
+ } else if (mScrollHelper != null) {
+ mScrollHelper.setEnabled(false);
+ }
+
+ return handledEvent;
+ }
+
+ /**
+ * Starts an alpha animation on the selector. When the animation ends,
+ * the list performs a click on the item.
+ */
+ private void clickPressedItem(final View child, final int position) {
+ final long id = getItemIdAtPosition(position);
+ performItemClick(child, position, id);
+ }
+
+ /**
+ * Sets whether the list selection is hidden, as part of a workaround for a
+ * touch mode issue (see the declaration for mListSelectionHidden).
+ *
+ * @param hideListSelection {@code true} to hide list selection,
+ * {@code false} to show
+ */
+ void setListSelectionHidden(boolean hideListSelection) {
+ mListSelectionHidden = hideListSelection;
+ }
+
+ private void clearPressedItem() {
+ mDrawsInPressedState = false;
+ setPressed(false);
+ // This will call through to updateSelectorState()
+ drawableStateChanged();
+
+ final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
+ if (motionView != null) {
+ motionView.setPressed(false);
+ }
+
+ if (mClickAnimation != null) {
+ mClickAnimation.cancel();
+ mClickAnimation = null;
+ }
+ }
+
+ private void setPressedItem(View child, int position, float x, float y) {
+ mDrawsInPressedState = true;
+
+ // Ordering is essential. First, update the container's pressed state.
+ if (Build.VERSION.SDK_INT >= 21) {
+ drawableHotspotChanged(x, y);
+ }
+ if (!isPressed()) {
+ setPressed(true);
+ }
+
+ // Next, run layout to stabilize child positions.
+ layoutChildren();
+
+ // Manage the pressed view based on motion position. This allows us to
+ // play nicely with actual touch and scroll events.
+ if (mMotionPosition != INVALID_POSITION) {
+ final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
+ if (motionView != null && motionView != child && motionView.isPressed()) {
+ motionView.setPressed(false);
+ }
+ }
+ mMotionPosition = position;
+
+ // Offset for child coordinates.
+ final float childX = x - child.getLeft();
+ final float childY = y - child.getTop();
+ if (Build.VERSION.SDK_INT >= 21) {
+ child.drawableHotspotChanged(childX, childY);
+ }
+ if (!child.isPressed()) {
+ child.setPressed(true);
+ }
+
+ // Ensure that keyboard focus starts from the last touched position.
+ positionSelectorLikeTouchCompat(position, child, x, y);
+
+ // This needs some explanation. We need to disable the selector for this next call
+ // due to the way that ListViewCompat works. Otherwise both ListView and ListViewCompat
+ // will draw the selector and bad things happen.
+ setSelectorEnabled(false);
+
+ // Refresh the drawable state to reflect the new pressed state,
+ // which will also update the selector state.
+ refreshDrawableState();
+ }
+
+ @Override
+ protected boolean touchModeDrawsInPressedStateCompat() {
+ return mDrawsInPressedState || super.touchModeDrawsInPressedStateCompat();
+ }
+
+ @Override
+ public boolean isInTouchMode() {
+ // WARNING: Please read the comment where mListSelectionHidden is declared
+ return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
+ }
+
+ /**
+ * <p>Returns the focus state in the drop down.</p>
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean hasWindowFocus() {
+ return mHijackFocus || super.hasWindowFocus();
+ }
+
+ /**
+ * <p>Returns the focus state in the drop down.</p>
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean isFocused() {
+ return mHijackFocus || super.isFocused();
+ }
+
+ /**
+ * <p>Returns the focus state in the drop down.</p>
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean hasFocus() {
+ return mHijackFocus || super.hasFocus();
+ }
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java b/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java
new file mode 100644
index 0000000..1ad9218
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java
@@ -0,0 +1,306 @@
+/*
+ * 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 android.os.SystemClock;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v7.view.menu.ShowableListMenu;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+
+
+/**
+ * Abstract class that forwards touch events to a {@link ShowableListMenu}.
+ *
+ * @hide
+ */
+public abstract class ForwardingListener implements View.OnTouchListener {
+ /** Scaled touch slop, used for detecting movement outside bounds. */
+ private final float mScaledTouchSlop;
+
+ /** Timeout before disallowing intercept on the source's parent. */
+ private final int mTapTimeout;
+
+ /** Timeout before accepting a long-press to start forwarding. */
+ private final int mLongPressTimeout;
+
+ /** Source view from which events are forwarded. */
+ private final View mSrc;
+
+ /** Runnable used to prevent conflicts with scrolling parents. */
+ private Runnable mDisallowIntercept;
+
+ /** Runnable used to trigger forwarding on long-press. */
+ private Runnable mTriggerLongPress;
+
+ /** Whether this listener is currently forwarding touch events. */
+ private boolean mForwarding;
+
+ /** The id of the first pointer down in the current event stream. */
+ private int mActivePointerId;
+
+ /**
+ * Temporary Matrix instance
+ */
+ private final int[] mTmpLocation = new int[2];
+
+ public ForwardingListener(View src) {
+ mSrc = src;
+ mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
+ mTapTimeout = ViewConfiguration.getTapTimeout();
+ // Use a medium-press timeout. Halfway between tap and long-press.
+ mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2;
+ }
+
+ /**
+ * Returns the popup to which this listener is forwarding events.
+ * <p>
+ * Override this to return the correct popup. If the popup is displayed
+ * asynchronously, you may also need to override
+ * {@link #onForwardingStopped} to prevent premature cancelation of
+ * forwarding.
+ *
+ * @return the popup to which this listener is forwarding events
+ */
+ public abstract ShowableListMenu getPopup();
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ final boolean wasForwarding = mForwarding;
+ final boolean forwarding;
+ if (wasForwarding) {
+ forwarding = onTouchForwarded(event) || !onForwardingStopped();
+ } else {
+ forwarding = onTouchObserved(event) && onForwardingStarted();
+
+ if (forwarding) {
+ // Make sure we cancel any ongoing source event stream.
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
+ 0.0f, 0.0f, 0);
+ mSrc.onTouchEvent(e);
+ e.recycle();
+ }
+ }
+
+ mForwarding = forwarding;
+ return forwarding || wasForwarding;
+ }
+
+ /**
+ * Called when forwarding would like to start.
+ * <p>
+ * By default, this will show the popup returned by {@link #getPopup()}.
+ * It may be overridden to perform another action, like clicking the
+ * source view or preparing the popup before showing it.
+ *
+ * @return true to start forwarding, false otherwise
+ */
+ protected boolean onForwardingStarted() {
+ final ShowableListMenu popup = getPopup();
+ if (popup != null && !popup.isShowing()) {
+ popup.show();
+ }
+ return true;
+ }
+
+ /**
+ * Called when forwarding would like to stop.
+ * <p>
+ * By default, this will dismiss the popup returned by
+ * {@link #getPopup()}. It may be overridden to perform some other
+ * action.
+ *
+ * @return true to stop forwarding, false otherwise
+ */
+ protected boolean onForwardingStopped() {
+ final ShowableListMenu popup = getPopup();
+ if (popup != null && popup.isShowing()) {
+ popup.dismiss();
+ }
+ return true;
+ }
+
+ /**
+ * Observes motion events and determines when to start forwarding.
+ *
+ * @param srcEvent motion event in source view coordinates
+ * @return true to start forwarding motion events, false otherwise
+ */
+ private boolean onTouchObserved(MotionEvent srcEvent) {
+ final View src = mSrc;
+ if (!src.isEnabled()) {
+ return false;
+ }
+
+ final int actionMasked = MotionEventCompat.getActionMasked(srcEvent);
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = srcEvent.getPointerId(0);
+
+ if (mDisallowIntercept == null) {
+ mDisallowIntercept = new DisallowIntercept();
+ }
+ src.postDelayed(mDisallowIntercept, mTapTimeout);
+
+ if (mTriggerLongPress == null) {
+ mTriggerLongPress = new TriggerLongPress();
+ }
+ src.postDelayed(mTriggerLongPress, mLongPressTimeout);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
+ if (activePointerIndex >= 0) {
+ final float x = srcEvent.getX(activePointerIndex);
+ final float y = srcEvent.getY(activePointerIndex);
+
+ // Has the pointer moved outside of the view?
+ if (!pointInView(src, x, y, mScaledTouchSlop)) {
+ clearCallbacks();
+
+ // Don't let the parent intercept our events.
+ src.getParent().requestDisallowInterceptTouchEvent(true);
+ return true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ clearCallbacks();
+ break;
+ }
+
+ return false;
+ }
+
+ private void clearCallbacks() {
+ if (mTriggerLongPress != null) {
+ mSrc.removeCallbacks(mTriggerLongPress);
+ }
+
+ if (mDisallowIntercept != null) {
+ mSrc.removeCallbacks(mDisallowIntercept);
+ }
+ }
+
+ private void onLongPress() {
+ clearCallbacks();
+
+ final View src = mSrc;
+ if (!src.isEnabled() || src.isLongClickable()) {
+ // Ignore long-press if the view is disabled or has its own
+ // handler.
+ return;
+ }
+
+ if (!onForwardingStarted()) {
+ return;
+ }
+
+ // Don't let the parent intercept our events.
+ src.getParent().requestDisallowInterceptTouchEvent(true);
+
+ // Make sure we cancel any ongoing source event stream.
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+ src.onTouchEvent(e);
+ e.recycle();
+
+ mForwarding = true;
+ }
+
+ /**
+ * Handles forwarded motion events and determines when to stop
+ * forwarding.
+ *
+ * @param srcEvent motion event in source view coordinates
+ * @return true to continue forwarding motion events, false to cancel
+ */
+ private boolean onTouchForwarded(MotionEvent srcEvent) {
+ final View src = mSrc;
+ final ShowableListMenu popup = getPopup();
+ if (popup == null || !popup.isShowing()) {
+ return false;
+ }
+
+ final DropDownListView dst = (DropDownListView) popup.getListView();
+ if (dst == null || !dst.isShown()) {
+ return false;
+ }
+
+ // Convert event to destination-local coordinates.
+ final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
+ toGlobalMotionEvent(src, dstEvent);
+ toLocalMotionEvent(dst, dstEvent);
+
+ // Forward converted event to destination view, then recycle it.
+ final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
+ dstEvent.recycle();
+
+ // Always cancel forwarding when the touch stream ends.
+ final int action = MotionEventCompat.getActionMasked(srcEvent);
+ final boolean keepForwarding = action != MotionEvent.ACTION_UP
+ && action != MotionEvent.ACTION_CANCEL;
+
+ return handled && keepForwarding;
+ }
+
+ private static boolean pointInView(View view, float localX, float localY, float slop) {
+ return localX >= -slop && localY >= -slop &&
+ localX < ((view.getRight() - view.getLeft()) + slop) &&
+ localY < ((view.getBottom() - view.getTop()) + slop);
+ }
+
+ /**
+ * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
+ * (scaleX, scaleY, etc).
+ */
+ private boolean toLocalMotionEvent(View view, MotionEvent event) {
+ final int[] loc = mTmpLocation;
+ view.getLocationOnScreen(loc);
+ event.offsetLocation(-loc[0], -loc[1]);
+ return true;
+ }
+
+ /**
+ * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
+ * (scaleX, scaleY, etc).
+ */
+ private boolean toGlobalMotionEvent(View view, MotionEvent event) {
+ final int[] loc = mTmpLocation;
+ view.getLocationOnScreen(loc);
+ event.offsetLocation(loc[0], loc[1]);
+ return true;
+ }
+
+ private class DisallowIntercept implements Runnable {
+ @Override
+ public void run() {
+ final ViewParent parent = mSrc.getParent();
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+
+ private class TriggerLongPress implements Runnable {
+ @Override
+ public void run() {
+ onLongPress();
+ }
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/GrowingArrayUtils.java b/v7/appcompat/src/android/support/v7/widget/GrowingArrayUtils.java
new file mode 100644
index 0000000..1d97400
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/GrowingArrayUtils.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2015 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 java.lang.reflect.Array;
+
+/**
+ * A helper class that aims to provide comparable growth performance to ArrayList, but on primitive
+ * arrays. Common array operations are implemented for efficient use in dynamic containers.
+ *
+ * All methods in this class assume that the length of an array is equivalent to its capacity and
+ * NOT the number of elements in the array. The current size of the array is always passed in as a
+ * parameter.
+ */
+class GrowingArrayUtils {
+
+ /**
+ * Appends an element to the end of the array, growing the array if there is no more room.
+ * @param array The array to which to append the element. This must NOT be null.
+ * @param currentSize The number of elements in the array. Must be less than or equal to
+ * array.length.
+ * @param element The element to append.
+ * @return the array to which the element was appended. This may be different than the given
+ * array.
+ */
+ public static <T> T[] append(T[] array, int currentSize, T element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(),
+ growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Primitive int version of {@link #append(Object[], int, Object)}.
+ */
+ public static int[] append(int[] array, int currentSize, int element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ int[] newArray = new int[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Primitive long version of {@link #append(Object[], int, Object)}.
+ */
+ public static long[] append(long[] array, int currentSize, long element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ long[] newArray = new long[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Primitive boolean version of {@link #append(Object[], int, Object)}.
+ */
+ public static boolean[] append(boolean[] array, int currentSize, boolean element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ boolean[] newArray = new boolean[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Inserts an element into the array at the specified index, growing the array if there is no
+ * more room.
+ *
+ * @param array The array to which to append the element. Must NOT be null.
+ * @param currentSize The number of elements in the array. Must be less than or equal to
+ * array.length.
+ * @param element The element to insert.
+ * @return the array to which the element was appended. This may be different than the given
+ * array.
+ */
+ public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(),
+ growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Primitive int version of {@link #insert(Object[], int, int, Object)}.
+ */
+ public static int[] insert(int[] array, int currentSize, int index, int element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ int[] newArray = new int[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Primitive long version of {@link #insert(Object[], int, int, Object)}.
+ */
+ public static long[] insert(long[] array, int currentSize, int index, long element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ long[] newArray = new long[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Primitive boolean version of {@link #insert(Object[], int, int, Object)}.
+ */
+ public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ boolean[] newArray = new boolean[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Given the current size of an array, returns an ideal size to which the array should grow.
+ * This is typically double the given size, but should not be relied upon to do so in the
+ * future.
+ */
+ public static int growSize(int currentSize) {
+ return currentSize <= 4 ? 8 : currentSize * 2;
+ }
+
+ // Uninstantiable
+ private GrowingArrayUtils() {}
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
index ce36d87..a75215b 100644
--- a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
+++ b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
@@ -23,14 +23,15 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
-import android.os.SystemClock;
-import android.support.v4.text.TextUtilsCompat;
-import android.support.v4.view.MotionEventCompat;
+import android.support.annotation.AttrRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.v4.os.BuildCompat;
import android.support.v4.view.ViewCompat;
-import android.support.v4.view.ViewPropertyAnimatorCompat;
-import android.support.v4.widget.ListViewAutoScrollHelper;
import android.support.v4.widget.PopupWindowCompat;
import android.support.v7.appcompat.R;
+import android.support.v7.view.menu.ShowableListMenu;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
@@ -39,19 +40,18 @@
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.PopupWindow;
import java.lang.reflect.Method;
-import java.util.Locale;
/**
* Static library support version of the framework's {@link android.widget.ListPopupWindow}.
@@ -62,7 +62,7 @@
*
* @see android.widget.ListPopupWindow
*/
-public class ListPopupWindow {
+public class ListPopupWindow implements ShowableListMenu {
private static final String TAG = "ListPopupWindow";
private static final boolean DEBUG = false;
@@ -75,6 +75,7 @@
private static Method sClipToWindowEnabledMethod;
private static Method sGetMaxAvailableHeightMethod;
+ private static Method sSetEpicenterBoundsMethod;
static {
try {
@@ -90,10 +91,15 @@
Log.i(TAG, "Could not find method getMaxAvailableHeight(View, int, boolean)"
+ " on PopupWindow. Oh well.");
}
+ try {
+ sSetEpicenterBoundsMethod = PopupWindow.class.getDeclaredMethod(
+ "setEpicenterBounds", Rect.class);
+ } catch (NoSuchMethodException e) {
+ Log.i(TAG, "Could not find method setEpicenterBounds(Rect) on PopupWindow. Oh well.");
+ }
}
private Context mContext;
- private PopupWindow mPopup;
private ListAdapter mAdapter;
private DropDownListView mDropDownList;
@@ -103,6 +109,7 @@
private int mDropDownVerticalOffset;
private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
private boolean mDropDownVerticalOffsetSet;
+ private boolean mIsAnimatedFromAnchor = true;
private int mDropDownGravity = Gravity.NO_GRAVITY;
@@ -120,7 +127,7 @@
private Drawable mDropDownListHighlight;
private AdapterView.OnItemClickListener mItemClickListener;
- private AdapterView.OnItemSelectedListener mItemSelectedListener;
+ private OnItemSelectedListener mItemSelectedListener;
private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
@@ -130,11 +137,17 @@
private final Handler mHandler;
- private Rect mTempRect = new Rect();
+ private final Rect mTempRect = new Rect();
+
+ /**
+ * Optional anchor-relative bounds to be used as the transition epicenter.
+ * When {@code null}, the anchor bounds are used as the epicenter.
+ */
+ private Rect mEpicenterBounds;
private boolean mModal;
- private int mLayoutDirection;
+ PopupWindow mPopup;
/**
* The provided prompt view should appear above list content.
@@ -197,7 +210,7 @@
*
* @param context Context used for contained views.
*/
- public ListPopupWindow(Context context) {
+ public ListPopupWindow(@NonNull Context context) {
this(context, null, R.attr.listPopupWindowStyle);
}
@@ -208,7 +221,7 @@
* @param context Context used for contained views.
* @param attrs Attributes from inflating parent views used to style the popup.
*/
- public ListPopupWindow(Context context, AttributeSet attrs) {
+ public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.listPopupWindowStyle);
}
@@ -220,7 +233,8 @@
* @param attrs Attributes from inflating parent views used to style the popup.
* @param defStyleAttr Default style attribute to use for popup content.
*/
- public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
+ public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
@@ -233,7 +247,8 @@
* @param defStyleAttr Style attribute to read for default styling of popup content.
* @param defStyleRes Style resource ID to use for default styling of popup content.
*/
- public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
mContext = context;
mHandler = new Handler(context.getMainLooper());
@@ -250,10 +265,6 @@
mPopup = new AppCompatPopupWindow(context, attrs, defStyleAttr);
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
-
- // Set the default layout direction to match the default locale one
- final Locale locale = mContext.getResources().getConfiguration().locale;
- mLayoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(locale);
}
/**
@@ -262,7 +273,7 @@
*
* @param adapter The adapter to use to create this window's content.
*/
- public void setAdapter(ListAdapter adapter) {
+ public void setAdapter(@Nullable ListAdapter adapter) {
if (mObserver == null) {
mObserver = new PopupDataSetObserver();
} else if (mAdapter != null) {
@@ -395,7 +406,7 @@
/**
* @return The background drawable for the popup window.
*/
- public Drawable getBackground() {
+ public @Nullable Drawable getBackground() {
return mPopup.getBackground();
}
@@ -404,7 +415,7 @@
*
* @param d A drawable to set as the background.
*/
- public void setBackgroundDrawable(Drawable d) {
+ public void setBackgroundDrawable(@Nullable Drawable d) {
mPopup.setBackgroundDrawable(d);
}
@@ -413,16 +424,17 @@
*
* @param animationStyle Animation style to use.
*/
- public void setAnimationStyle(int animationStyle) {
+ public void setAnimationStyle(@StyleRes int animationStyle) {
mPopup.setAnimationStyle(animationStyle);
}
/**
- * Returns the animation style that will be used when the popup window is shown or dismissed.
+ * Returns the animation style that will be used when the popup window is
+ * shown or dismissed.
*
* @return Animation style that will be used.
*/
- public int getAnimationStyle() {
+ public @StyleRes int getAnimationStyle() {
return mPopup.getAnimationStyle();
}
@@ -431,17 +443,17 @@
*
* @return The popup's anchor view
*/
- public View getAnchorView() {
+ public @Nullable View getAnchorView() {
return mDropDownAnchorView;
}
/**
- * Sets the popup's anchor view. This popup will always be positioned relative to the anchor
- * view when shown.
+ * Sets the popup's anchor view. This popup will always be positioned relative to
+ * the anchor view when shown.
*
* @param anchor The view to use as an anchor.
*/
- public void setAnchorView(View anchor) {
+ public void setAnchorView(@Nullable View anchor) {
mDropDownAnchorView = anchor;
}
@@ -482,6 +494,17 @@
}
/**
+ * Specifies the anchor-relative bounds of the popup's transition
+ * epicenter.
+ *
+ * @param bounds anchor-relative bounds
+ * @hide
+ */
+ public void setEpicenterBounds(Rect bounds) {
+ mEpicenterBounds = bounds;
+ }
+
+ /**
* Set the gravity of the dropdown list. This is commonly used to
* set gravity to START or END for alignment with the anchor.
*
@@ -560,7 +583,7 @@
*
* @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
*/
- public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) {
+ public void setOnItemClickListener(@Nullable AdapterView.OnItemClickListener clickListener) {
mItemClickListener = clickListener;
}
@@ -569,9 +592,9 @@
*
* @param selectedListener Listener to register.
*
- * @see ListView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
+ * @see ListView#setOnItemSelectedListener(OnItemSelectedListener)
*/
- public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener selectedListener) {
+ public void setOnItemSelectedListener(@Nullable OnItemSelectedListener selectedListener) {
mItemSelectedListener = selectedListener;
}
@@ -581,7 +604,7 @@
*
* @param prompt View to use as an informational prompt.
*/
- public void setPromptView(View prompt) {
+ public void setPromptView(@Nullable View prompt) {
boolean showing = isShowing();
if (showing) {
removePromptView();
@@ -603,10 +626,11 @@
* Show the popup list. If the list is already showing, this method
* will recalculate the popup's size and position.
*/
+ @Override
public void show() {
int height = buildDropDown();
- boolean noInputMethod = isInputMethodNotNeeded();
+ final boolean noInputMethod = isInputMethodNotNeeded();
PopupWindowCompat.setWindowLayoutType(mPopup, mDropDownWindowLayoutType);
if (mPopup.isShowing()) {
@@ -677,6 +701,13 @@
// only set this if the dropdown is not always visible
mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
mPopup.setTouchInterceptor(mTouchInterceptor);
+ if (sSetEpicenterBoundsMethod != null) {
+ try {
+ sSetEpicenterBoundsMethod.invoke(mPopup, mEpicenterBounds);
+ } catch (Exception e) {
+ Log.e(TAG, "Could not invoke setEpicenterBounds on PopupWindow", e);
+ }
+ }
PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, mDropDownGravity);
mDropDownList.setSelection(ListView.INVALID_POSITION);
@@ -693,6 +724,7 @@
/**
* Dismiss the popup window.
*/
+ @Override
public void dismiss() {
mPopup.dismiss();
removePromptView();
@@ -706,7 +738,7 @@
*
* @param listener Listener that will be notified when the popup is dismissed.
*/
- public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
+ public void setOnDismissListener(@Nullable PopupWindow.OnDismissListener listener) {
mPopup.setOnDismissListener(listener);
}
@@ -754,7 +786,7 @@
public void setSelection(int position) {
DropDownListView list = mDropDownList;
if (isShowing() && list != null) {
- list.mListSelectionHidden = false;
+ list.setListSelectionHidden(false);
list.setSelection(position);
if (Build.VERSION.SDK_INT >= 11) {
@@ -773,7 +805,7 @@
final DropDownListView list = mDropDownList;
if (list != null) {
// WARNING: Please read the comment where mListSelectionHidden is declared
- list.mListSelectionHidden = true;
+ list.setListSelectionHidden(true);
//list.hideSelector();
list.requestLayout();
}
@@ -782,6 +814,7 @@
/**
* @return {@code true} if the popup is currently showing, {@code false} otherwise.
*/
+ @Override
public boolean isShowing() {
return mPopup.isShowing();
}
@@ -817,7 +850,7 @@
/**
* @return The currently selected item or null if the popup is not showing.
*/
- public Object getSelectedItem() {
+ public @Nullable Object getSelectedItem() {
if (!isShowing()) {
return null;
}
@@ -856,7 +889,7 @@
*
* @see ListView#getSelectedView()
*/
- public View getSelectedView() {
+ public @Nullable View getSelectedView() {
if (!isShowing()) {
return null;
}
@@ -867,10 +900,15 @@
* @return The {@link ListView} displayed within the popup window.
* Only valid when {@link #isShowing()} == {@code true}.
*/
- public ListView getListView() {
+ @Override
+ public @Nullable ListView getListView() {
return mDropDownList;
}
+ @NonNull DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
+ return new DropDownListView(context, hijackFocus);
+ }
+
/**
* The maximum number of list items that can be visible and still have
* the list expand when touched.
@@ -891,7 +929,7 @@
*
* @see #setModal(boolean)
*/
- public boolean onKeyDown(int keyCode, KeyEvent event) {
+ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
// when the drop down is shown, we drive it directly
if (isShowing()) {
// the key events are forwarded to the list in the drop down view
@@ -932,7 +970,7 @@
} else {
// WARNING: Please read the comment where mListSelectionHidden
// is declared
- mDropDownList.mListSelectionHidden = false;
+ mDropDownList.setListSelectionHidden(false);
}
consumed = mDropDownList.onKeyDown(keyCode, event);
@@ -986,7 +1024,7 @@
*
* @see #setModal(boolean)
*/
- public boolean onKeyUp(int keyCode, KeyEvent event) {
+ public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
boolean consumed = mDropDownList.onKeyUp(keyCode, event);
if (consumed && isConfirmKey(keyCode)) {
@@ -1010,7 +1048,7 @@
*
* @see #setModal(boolean)
*/
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) {
// special case for the back key, we do not even try to send it
// to the drop down list but instead, consume it immediately
@@ -1093,7 +1131,7 @@
}
};
- mDropDownList = new DropDownListView(context, !mModal);
+ mDropDownList = createDropDownListView(context, !mModal);
if (mDropDownListHighlight != null) {
mDropDownList.setSelector(mDropDownListHighlight);
}
@@ -1101,7 +1139,7 @@
mDropDownList.setOnItemClickListener(mItemClickListener);
mDropDownList.setFocusable(true);
mDropDownList.setFocusableInTouchMode(true);
- mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ mDropDownList.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
@@ -1109,7 +1147,7 @@
DropDownListView dropDownList = mDropDownList;
if (dropDownList != null) {
- dropDownList.mListSelectionHidden = false;
+ dropDownList.setListSelectionHidden(false);
}
}
}
@@ -1240,529 +1278,6 @@
return listContent + otherHeights;
}
- /**
- * Abstract class that forwards touch events to a {@link ListPopupWindow}.
- *
- * @hide
- */
- public static abstract class ForwardingListener implements View.OnTouchListener {
- /** Scaled touch slop, used for detecting movement outside bounds. */
- private final float mScaledTouchSlop;
-
- /** Timeout before disallowing intercept on the source's parent. */
- private final int mTapTimeout;
- /** Timeout before accepting a long-press to start forwarding. */
- private final int mLongPressTimeout;
-
- /** Source view from which events are forwarded. */
- private final View mSrc;
-
- /** Runnable used to prevent conflicts with scrolling parents. */
- private Runnable mDisallowIntercept;
- /** Runnable used to trigger forwarding on long-press. */
- private Runnable mTriggerLongPress;
-
- /** Whether this listener is currently forwarding touch events. */
- private boolean mForwarding;
- /**
- * Whether forwarding was initiated by a long-press. If so, we won't
- * force the window to dismiss when the touch stream ends.
- */
- private boolean mWasLongPress;
-
- /** The id of the first pointer down in the current event stream. */
- private int mActivePointerId;
-
- /**
- * Temporary Matrix instance
- */
- private final int[] mTmpLocation = new int[2];
-
- public ForwardingListener(View src) {
- mSrc = src;
- mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
- mTapTimeout = ViewConfiguration.getTapTimeout();
- // Use a medium-press timeout. Halfway between tap and long-press.
- mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2;
- }
-
- /**
- * Returns the popup to which this listener is forwarding events.
- * <p>
- * Override this to return the correct popup. If the popup is displayed
- * asynchronously, you may also need to override
- * {@link #onForwardingStopped} to prevent premature cancelation of
- * forwarding.
- *
- * @return the popup to which this listener is forwarding events
- */
- public abstract ListPopupWindow getPopup();
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- final boolean wasForwarding = mForwarding;
- final boolean forwarding;
- if (wasForwarding) {
- if (mWasLongPress) {
- // If we started forwarding as a result of a long-press,
- // just silently stop forwarding events so that the window
- // stays open.
- forwarding = onTouchForwarded(event);
- } else {
- forwarding = onTouchForwarded(event) || !onForwardingStopped();
- }
- } else {
- forwarding = onTouchObserved(event) && onForwardingStarted();
-
- if (forwarding) {
- // Make sure we cancel any ongoing source event stream.
- final long now = SystemClock.uptimeMillis();
- final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
- 0.0f, 0.0f, 0);
- mSrc.onTouchEvent(e);
- e.recycle();
- }
- }
-
- mForwarding = forwarding;
- return forwarding || wasForwarding;
- }
-
- /**
- * Called when forwarding would like to start. <p> By default, this will show the popup
- * returned by {@link #getPopup()}. It may be overridden to perform another action, like
- * clicking the source view or preparing the popup before showing it.
- *
- * @return true to start forwarding, false otherwise
- */
- protected boolean onForwardingStarted() {
- final ListPopupWindow popup = getPopup();
- if (popup != null && !popup.isShowing()) {
- popup.show();
- }
- return true;
- }
-
- /**
- * Called when forwarding would like to stop. <p> By default, this will dismiss the popup
- * returned by {@link #getPopup()}. It may be overridden to perform some other action.
- *
- * @return true to stop forwarding, false otherwise
- */
- protected boolean onForwardingStopped() {
- final ListPopupWindow popup = getPopup();
- if (popup != null && popup.isShowing()) {
- popup.dismiss();
- }
- return true;
- }
-
- /**
- * Observes motion events and determines when to start forwarding.
- *
- * @param srcEvent motion event in source view coordinates
- * @return true to start forwarding motion events, false otherwise
- */
- private boolean onTouchObserved(MotionEvent srcEvent) {
- final View src = mSrc;
- if (!src.isEnabled()) {
- return false;
- }
-
- final int actionMasked = MotionEventCompat.getActionMasked(srcEvent);
- switch (actionMasked) {
- case MotionEvent.ACTION_DOWN:
- mActivePointerId = srcEvent.getPointerId(0);
- mWasLongPress = false;
-
- if (mDisallowIntercept == null) {
- mDisallowIntercept = new DisallowIntercept();
- }
- src.postDelayed(mDisallowIntercept, mTapTimeout);
- if (mTriggerLongPress == null) {
- mTriggerLongPress = new TriggerLongPress();
- }
- src.postDelayed(mTriggerLongPress, mLongPressTimeout);
- break;
- case MotionEvent.ACTION_MOVE:
- final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
- if (activePointerIndex >= 0) {
- final float x = srcEvent.getX(activePointerIndex);
- final float y = srcEvent.getY(activePointerIndex);
- if (!pointInView(src, x, y, mScaledTouchSlop)) {
- clearCallbacks();
-
- // Don't let the parent intercept our events.
- src.getParent().requestDisallowInterceptTouchEvent(true);
- return true;
- }
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- clearCallbacks();
- break;
- }
-
- return false;
- }
-
- private void clearCallbacks() {
- if (mTriggerLongPress != null) {
- mSrc.removeCallbacks(mTriggerLongPress);
- }
-
- if (mDisallowIntercept != null) {
- mSrc.removeCallbacks(mDisallowIntercept);
- }
- }
-
- private void onLongPress() {
- clearCallbacks();
-
- final View src = mSrc;
- if (!src.isEnabled() || src.isLongClickable()) {
- // Ignore long-press if the view is disabled or has its own
- // handler.
- return;
- }
-
- if (!onForwardingStarted()) {
- return;
- }
-
- // Don't let the parent intercept our events.
- src.getParent().requestDisallowInterceptTouchEvent(true);
-
- // Make sure we cancel any ongoing source event stream.
- final long now = SystemClock.uptimeMillis();
- final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
- src.onTouchEvent(e);
- e.recycle();
-
- mForwarding = true;
- mWasLongPress = true;
- }
-
- /**
- * Handled forwarded motion events and determines when to stop forwarding.
- *
- * @param srcEvent motion event in source view coordinates
- * @return true to continue forwarding motion events, false to cancel
- */
- private boolean onTouchForwarded(MotionEvent srcEvent) {
- final View src = mSrc;
- final ListPopupWindow popup = getPopup();
- if (popup == null || !popup.isShowing()) {
- return false;
- }
-
- final DropDownListView dst = popup.mDropDownList;
- if (dst == null || !dst.isShown()) {
- return false;
- }
-
- // Convert event to destination-local coordinates.
- final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
- toGlobalMotionEvent(src, dstEvent);
- toLocalMotionEvent(dst, dstEvent);
-
- // Forward converted event to destination view, then recycle it.
- final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
- dstEvent.recycle();
-
- // Always cancel forwarding when the touch stream ends.
- final int action = MotionEventCompat.getActionMasked(srcEvent);
- final boolean keepForwarding = action != MotionEvent.ACTION_UP
- && action != MotionEvent.ACTION_CANCEL;
-
- return handled && keepForwarding;
- }
-
- private static boolean pointInView(View view, float localX, float localY, float slop) {
- return localX >= -slop && localY >= -slop &&
- localX < ((view.getRight() - view.getLeft()) + slop) &&
- localY < ((view.getBottom() - view.getTop()) + slop);
- }
-
- /**
- * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
- * (scaleX, scaleY, etc).
- */
- private boolean toLocalMotionEvent(View view, MotionEvent event) {
- final int[] loc = mTmpLocation;
- view.getLocationOnScreen(loc);
- event.offsetLocation(-loc[0], -loc[1]);
- return true;
- }
-
- /**
- * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
- * (scaleX, scaleY, etc).
- */
- private boolean toGlobalMotionEvent(View view, MotionEvent event) {
- final int[] loc = mTmpLocation;
- view.getLocationOnScreen(loc);
- event.offsetLocation(loc[0], loc[1]);
- return true;
- }
-
- private class DisallowIntercept implements Runnable {
- @Override
- public void run() {
- final ViewParent parent = mSrc.getParent();
- parent.requestDisallowInterceptTouchEvent(true);
- }
- }
-
- private class TriggerLongPress implements Runnable {
- @Override
- public void run() {
- onLongPress();
- }
- }
- }
-
- /**
- * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
- * make sure the list uses the appropriate drawables and states when
- * displayed on screen within a drop down. The focus is never actually
- * passed to the drop down in this mode; the list only looks focused.</p>
- */
- private static class DropDownListView extends ListViewCompat {
-
- /*
- * WARNING: This is a workaround for a touch mode issue.
- *
- * Touch mode is propagated lazily to windows. This causes problems in
- * the following scenario:
- * - Type something in the AutoCompleteTextView and get some results
- * - Move down with the d-pad to select an item in the list
- * - Move up with the d-pad until the selection disappears
- * - Type more text in the AutoCompleteTextView *using the soft keyboard*
- * and get new results; you are now in touch mode
- * - The selection comes back on the first item in the list, even though
- * the list is supposed to be in touch mode
- *
- * Using the soft keyboard triggers the touch mode change but that change
- * is propagated to our window only after the first list layout, therefore
- * after the list attempts to resurrect the selection.
- *
- * The trick to work around this issue is to pretend the list is in touch
- * mode when we know that the selection should not appear, that is when
- * we know the user moved the selection away from the list.
- *
- * This boolean is set to true whenever we explicitly hide the list's
- * selection and reset to false whenever we know the user moved the
- * selection back to the list.
- *
- * When this boolean is true, isInTouchMode() returns true, otherwise it
- * returns super.isInTouchMode().
- */
- private boolean mListSelectionHidden;
-
- /**
- * True if this wrapper should fake focus.
- */
- private boolean mHijackFocus;
-
- /** Whether to force drawing of the pressed state selector. */
- private boolean mDrawsInPressedState;
-
- /** Current drag-to-open click animation, if any. */
- private ViewPropertyAnimatorCompat mClickAnimation;
-
- /** Helper for drag-to-open auto scrolling. */
- private ListViewAutoScrollHelper mScrollHelper;
-
- /**
- * <p>Creates a new list view wrapper.</p>
- *
- * @param context this view's context
- */
- public DropDownListView(Context context, boolean hijackFocus) {
- super(context, null, R.attr.dropDownListViewStyle);
- mHijackFocus = hijackFocus;
- setCacheColorHint(0); // Transparent, since the background drawable could be anything.
- }
-
- /**
- * Handles forwarded events.
- *
- * @param activePointerId id of the pointer that activated forwarding
- * @return whether the event was handled
- */
- public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
- boolean handledEvent = true;
- boolean clearPressedItem = false;
-
- final int actionMasked = MotionEventCompat.getActionMasked(event);
- switch (actionMasked) {
- case MotionEvent.ACTION_CANCEL:
- handledEvent = false;
- break;
- case MotionEvent.ACTION_UP:
- handledEvent = false;
- // $FALL-THROUGH$
- case MotionEvent.ACTION_MOVE:
- final int activeIndex = event.findPointerIndex(activePointerId);
- if (activeIndex < 0) {
- handledEvent = false;
- break;
- }
-
- final int x = (int) event.getX(activeIndex);
- final int y = (int) event.getY(activeIndex);
- final int position = pointToPosition(x, y);
- if (position == INVALID_POSITION) {
- clearPressedItem = true;
- break;
- }
-
- final View child = getChildAt(position - getFirstVisiblePosition());
- setPressedItem(child, position, x, y);
- handledEvent = true;
-
- if (actionMasked == MotionEvent.ACTION_UP) {
- clickPressedItem(child, position);
- }
- break;
- }
-
- // Failure to handle the event cancels forwarding.
- if (!handledEvent || clearPressedItem) {
- clearPressedItem();
- }
-
- // Manage automatic scrolling.
- if (handledEvent) {
- if (mScrollHelper == null) {
- mScrollHelper = new ListViewAutoScrollHelper(this);
- }
- mScrollHelper.setEnabled(true);
- mScrollHelper.onTouch(this, event);
- } else if (mScrollHelper != null) {
- mScrollHelper.setEnabled(false);
- }
-
- return handledEvent;
- }
-
- /**
- * Starts an alpha animation on the selector. When the animation ends,
- * the list performs a click on the item.
- */
- private void clickPressedItem(final View child, final int position) {
- final long id = getItemIdAtPosition(position);
- performItemClick(child, position, id);
- }
-
- private void clearPressedItem() {
- mDrawsInPressedState = false;
- setPressed(false);
- // This will call through to updateSelectorState()
- drawableStateChanged();
-
- final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
- if (motionView != null) {
- motionView.setPressed(false);
- }
-
- if (mClickAnimation != null) {
- mClickAnimation.cancel();
- mClickAnimation = null;
- }
- }
-
- private void setPressedItem(View child, int position, float x, float y) {
- mDrawsInPressedState = true;
-
- // Ordering is essential. First, update the container's pressed state.
- if (Build.VERSION.SDK_INT >= 21) {
- drawableHotspotChanged(x, y);
- }
- if (!isPressed()) {
- setPressed(true);
- }
-
- // Next, run layout to stabilize child positions.
- layoutChildren();
-
- // Manage the pressed view based on motion position. This allows us to
- // play nicely with actual touch and scroll events.
- if (mMotionPosition != INVALID_POSITION) {
- final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
- if (motionView != null && motionView != child && motionView.isPressed()) {
- motionView.setPressed(false);
- }
- }
- mMotionPosition = position;
-
- // Offset for child coordinates.
- final float childX = x - child.getLeft();
- final float childY = y - child.getTop();
- if (Build.VERSION.SDK_INT >= 21) {
- child.drawableHotspotChanged(childX, childY);
- }
- if (!child.isPressed()) {
- child.setPressed(true);
- }
-
- // Ensure that keyboard focus starts from the last touched position.
- positionSelectorLikeTouchCompat(position, child, x, y);
-
- // This needs some explanation. We need to disable the selector for this next call
- // due to the way that ListViewCompat works. Otherwise both ListView and ListViewCompat
- // will draw the selector and bad things happen.
- setSelectorEnabled(false);
-
- // Refresh the drawable state to reflect the new pressed state,
- // which will also update the selector state.
- refreshDrawableState();
- }
-
- @Override
- protected boolean touchModeDrawsInPressedStateCompat() {
- return mDrawsInPressedState || super.touchModeDrawsInPressedStateCompat();
- }
-
- @Override
- public boolean isInTouchMode() {
- // WARNING: Please read the comment where mListSelectionHidden is declared
- return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always if hijacking focus
- */
- @Override
- public boolean hasWindowFocus() {
- return mHijackFocus || super.hasWindowFocus();
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always if hijacking focus
- */
- @Override
- public boolean isFocused() {
- return mHijackFocus || super.isFocused();
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always if hijacking focus
- */
- @Override
- public boolean hasFocus() {
- return mHijackFocus || super.hasFocus();
- }
- }
-
private class PopupDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
diff --git a/v7/appcompat/src/android/support/v7/widget/MenuItemHoverListener.java b/v7/appcompat/src/android/support/v7/widget/MenuItemHoverListener.java
new file mode 100644
index 0000000..a1e5645
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/MenuItemHoverListener.java
@@ -0,0 +1,48 @@
+/*
+ * 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 android.support.annotation.NonNull;
+import android.support.v7.view.menu.MenuBuilder;
+import android.view.MenuItem;
+
+/**
+ * An interface notified when a menu item is hovered. Useful for cases when hover should trigger
+ * some behavior at a higher level, like managing the opening and closing of submenus.
+ *
+ * @hide
+ */
+public interface MenuItemHoverListener {
+ /**
+ * Called when hover exits a menu item.
+ * <p>
+ * If hover is moving to another item, this method will be called before
+ * {@link #onItemHoverEnter(MenuBuilder, MenuItem)} for the newly-hovered item.
+ *
+ * @param menu the item's parent menu
+ * @param item the hovered menu item
+ */
+ void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item);
+
+ /**
+ * Called when hover enters a menu item.
+ *
+ * @param menu the item's parent menu
+ * @param item the hovered menu item
+ */
+ void onItemHoverEnter(@NonNull MenuBuilder menu, @NonNull MenuItem item);
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/MenuPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/MenuPopupWindow.java
new file mode 100644
index 0000000..11de42e
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/MenuPopupWindow.java
@@ -0,0 +1,222 @@
+/*
+ * 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 android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.PopupWindowCompat;
+import android.support.v7.view.menu.ListMenuItemView;
+import android.support.v7.view.menu.MenuAdapter;
+import android.support.v7.view.menu.MenuBuilder;
+import android.transition.Transition;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.HeaderViewListAdapter;
+import android.widget.ListAdapter;
+import android.widget.PopupWindow;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * A MenuPopupWindow represents the popup window for menu.
+ *
+ * MenuPopupWindow is mostly same as ListPopupWindow, but it has customized
+ * behaviors specific to menus,
+ *
+ * @hide
+ */
+public class MenuPopupWindow extends ListPopupWindow implements MenuItemHoverListener {
+ private static final String TAG = "MenuPopupWindow";
+
+ private static Method sSetTouchModalMethod;
+
+ static {
+ try {
+ sSetTouchModalMethod = PopupWindow.class.getDeclaredMethod(
+ "setTouchModal", boolean.class);
+ } catch (NoSuchMethodException e) {
+ Log.i(TAG, "Could not find method setTouchModal() on PopupWindow. Oh well.");
+ }
+ }
+
+ private MenuItemHoverListener mHoverListener;
+
+ public MenuPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
+ MenuDropDownListView view = new MenuDropDownListView(context, hijackFocus);
+ view.setHoverListener(this);
+ return view;
+ }
+
+ public void setEnterTransition(Object enterTransition) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ mPopup.setEnterTransition((Transition) enterTransition);
+ }
+ }
+
+ public void setExitTransition(Object exitTransition) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ mPopup.setExitTransition((Transition) exitTransition);
+ }
+ }
+
+ public void setHoverListener(MenuItemHoverListener hoverListener) {
+ mHoverListener = hoverListener;
+ }
+
+ /**
+ * Set whether this window is touch modal or if outside touches will be sent to
+ * other windows behind it.
+ */
+ public void setTouchModal(final boolean touchModal) {
+ if (sSetTouchModalMethod != null) {
+ try {
+ sSetTouchModalMethod.invoke(mPopup, touchModal);
+ } catch (Exception e) {
+ Log.i(TAG, "Could not invoke setTouchModal() on PopupWindow. Oh well.");
+ }
+ }
+ }
+
+ @Override
+ public void onItemHoverEnter(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
+ // Forward up the chain
+ if (mHoverListener != null) {
+ mHoverListener.onItemHoverEnter(menu, item);
+ }
+ }
+
+ @Override
+ public void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
+ // Forward up the chain
+ if (mHoverListener != null) {
+ mHoverListener.onItemHoverExit(menu, item);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static class MenuDropDownListView extends DropDownListView {
+ final int mAdvanceKey;
+ final int mRetreatKey;
+
+ private MenuItemHoverListener mHoverListener;
+ private MenuItem mHoveredMenuItem;
+
+ public MenuDropDownListView(Context context, boolean hijackFocus) {
+ super(context, hijackFocus);
+
+ if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {
+ mAdvanceKey = KeyEvent.KEYCODE_DPAD_LEFT;
+ mRetreatKey = KeyEvent.KEYCODE_DPAD_RIGHT;
+ } else {
+ mAdvanceKey = KeyEvent.KEYCODE_DPAD_RIGHT;
+ mRetreatKey = KeyEvent.KEYCODE_DPAD_LEFT;
+ }
+ }
+
+ public void setHoverListener(MenuItemHoverListener hoverListener) {
+ mHoverListener = hoverListener;
+ }
+
+ public void clearSelection() {
+ setSelection(INVALID_POSITION);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ ListMenuItemView selectedItem = (ListMenuItemView) getSelectedView();
+ if (selectedItem != null && keyCode == mAdvanceKey) {
+ if (selectedItem.isEnabled() && selectedItem.getItemData().hasSubMenu()) {
+ performItemClick(
+ selectedItem,
+ getSelectedItemPosition(),
+ getSelectedItemId());
+ }
+ return true;
+ } else if (selectedItem != null && keyCode == mRetreatKey) {
+ setSelection(INVALID_POSITION);
+
+ // Close only the top-level menu.
+ ((MenuAdapter) getAdapter()).getAdapterMenu().close(false /* closeAllMenus */);
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent ev) {
+ // Dispatch any changes in hovered item index to the listener.
+ if (mHoverListener != null) {
+ // The adapter may be wrapped. Adjust the index if necessary.
+ final int headersCount;
+ final MenuAdapter menuAdapter;
+ final ListAdapter adapter = getAdapter();
+ if (adapter instanceof HeaderViewListAdapter) {
+ final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) adapter;
+ headersCount = headerAdapter.getHeadersCount();
+ menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter();
+ } else {
+ headersCount = 0;
+ menuAdapter = (MenuAdapter) adapter;
+ }
+
+ // Find the menu item for the view at the event coordinates.
+ MenuItem menuItem = null;
+ if (ev.getAction() != MotionEvent.ACTION_HOVER_EXIT) {
+ final int position = pointToPosition((int) ev.getX(), (int) ev.getY());
+ if (position != INVALID_POSITION) {
+ final int itemPosition = position - headersCount;
+ if (itemPosition >= 0 && itemPosition < menuAdapter.getCount()) {
+ menuItem = menuAdapter.getItem(itemPosition);
+ }
+ }
+ }
+
+ final MenuItem oldMenuItem = mHoveredMenuItem;
+ if (oldMenuItem != menuItem) {
+ final MenuBuilder menu = menuAdapter.getAdapterMenu();
+ if (oldMenuItem != null) {
+ mHoverListener.onItemHoverExit(menu, oldMenuItem);
+ }
+
+ mHoveredMenuItem = menuItem;
+
+ if (menuItem != null) {
+ mHoverListener.onItemHoverEnter(menu, menuItem);
+ }
+ }
+ }
+
+ return super.onHoverEvent(ev);
+ }
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
index 7f097c7..83e8f65 100644
--- a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
+++ b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
@@ -16,20 +16,19 @@
package android.support.v7.widget;
-
import android.content.Context;
import android.support.annotation.MenuRes;
import android.support.v7.appcompat.R;
import android.support.v7.view.SupportMenuInflater;
import android.support.v7.view.menu.MenuBuilder;
import android.support.v7.view.menu.MenuPopupHelper;
-import android.support.v7.view.menu.MenuPresenter;
-import android.support.v7.view.menu.SubMenuBuilder;
+import android.support.v7.view.menu.ShowableListMenu;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.widget.PopupWindow;
/**
* Static library support version of the framework's {@link android.widget.PopupMenu}.
@@ -38,33 +37,23 @@
* to switch to the framework's implementation. See the framework SDK
* documentation for a class overview.
*/
-public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
- private Context mContext;
- private MenuBuilder mMenu;
- private View mAnchor;
- private MenuPopupHelper mPopup;
+public class PopupMenu {
+ private final Context mContext;
+ private final MenuBuilder mMenu;
+ private final View mAnchor;
+ private final MenuPopupHelper mPopup;
+
private OnMenuItemClickListener mMenuItemClickListener;
- private OnDismissListener mDismissListener;
+ private OnDismissListener mOnDismissListener;
private View.OnTouchListener mDragListener;
/**
- * Callback interface used to notify the application that the menu has closed.
- */
- public interface OnDismissListener {
- /**
- * Called when the associated menu has been dismissed.
+ * Constructor to create a new popup menu with an anchor view.
*
- * @param menu The PopupMenu that was dismissed.
- */
- public void onDismiss(PopupMenu menu);
- }
-
- /**
- * Construct a new PopupMenu.
- *
- * @param context Context for the PopupMenu.
- * @param anchor Anchor view for this popup. The popup will appear below the anchor if there
- * is room, or above it if there is not.
+ * @param context Context the popup menu is running in, through which it
+ * can access the current theme, resources, etc.
+ * @param anchor Anchor view for this popup. The popup will appear below
+ * the anchor if there is room, or above it if there is not.
*/
public PopupMenu(Context context, View anchor) {
this(context, anchor, Gravity.NO_GRAVITY);
@@ -105,12 +94,33 @@
public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
int popupStyleRes) {
mContext = context;
- mMenu = new MenuBuilder(context);
- mMenu.setCallback(this);
mAnchor = anchor;
+
+ mMenu = new MenuBuilder(context);
+ mMenu.setCallback(new MenuBuilder.Callback() {
+ @Override
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ if (mMenuItemClickListener != null) {
+ return mMenuItemClickListener.onMenuItemClick(item);
+ }
+ return false;
+ }
+
+ @Override
+ public void onMenuModeChange(MenuBuilder menu) {
+ }
+ });
+
mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
mPopup.setGravity(gravity);
- mPopup.setCallback(this);
+ mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
+ @Override
+ public void onDismiss() {
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss(PopupMenu.this);
+ }
+ }
+ });
}
/**
@@ -120,7 +130,6 @@
* the next time the popup is shown.
*
* @param gravity the gravity used to align the popup window
- *
* @see #getGravity()
*/
public void setGravity(int gravity) {
@@ -129,7 +138,6 @@
/**
* @return the gravity used to align the popup window to its anchor view
- *
* @see #setGravity(int)
*/
public int getGravity() {
@@ -137,12 +145,12 @@
}
/**
- * Returns an {@link android.view.View.OnTouchListener} that can be added to the anchor view
+ * Returns an {@link View.OnTouchListener} that can be added to the anchor view
* to implement drag-to-open behavior.
* <p>
* When the listener is set on a view, touching that view and dragging
- * outside of its bounds will open the popup window. Lifting will select the
- * currently touched list item.
+ * outside of its bounds will open the popup window. Lifting will select
+ * the currently touched list item.
* <p>
* Example usage:
* <pre>
@@ -154,7 +162,7 @@
*/
public View.OnTouchListener getDragToOpenListener() {
if (mDragListener == null) {
- mDragListener = new ListPopupWindow.ForwardingListener(mAnchor) {
+ mDragListener = new ForwardingListener(mAnchor) {
@Override
protected boolean onForwardingStarted() {
show();
@@ -168,7 +176,7 @@
}
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
// This will be null until show() is called.
return mPopup.getPopup();
}
@@ -179,9 +187,10 @@
}
/**
- * @return the {@link Menu} associated with this popup. Populate the returned Menu with
- * items before calling {@link #show()}.
+ * Returns the {@link Menu} associated with this popup. Populate the
+ * returned Menu with items before calling {@link #show()}.
*
+ * @return the {@link Menu} associated with this popup
* @see #show()
* @see #getMenuInflater()
*/
@@ -190,9 +199,8 @@
}
/**
- * @return a {@link MenuInflater} that can be used to inflate menu items from XML into the
- * menu returned by {@link #getMenu()}.
- *
+ * @return a {@link MenuInflater} that can be used to inflate menu items
+ * from XML into the menu returned by {@link #getMenu()}
* @see #getMenu()
*/
public MenuInflater getMenuInflater() {
@@ -200,8 +208,9 @@
}
/**
- * Inflate a menu resource into this PopupMenu. This is equivalent to calling
- * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
+ * Inflate a menu resource into this PopupMenu. This is equivalent to
+ * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
+ *
* @param menuRes Menu resource to inflate
*/
public void inflate(@MenuRes int menuRes) {
@@ -210,6 +219,7 @@
/**
* Show the menu popup anchored to the view specified during construction.
+ *
* @see #dismiss()
*/
public void show() {
@@ -218,6 +228,7 @@
/**
* Dismiss the menu popup.
+ *
* @see #show()
*/
public void dismiss() {
@@ -225,81 +236,49 @@
}
/**
- * Set a listener that will be notified when the user selects an item from the menu.
+ * Sets a listener that will be notified when the user selects an item from
+ * the menu.
*
- * @param listener Listener to notify
+ * @param listener the listener to notify
*/
public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
mMenuItemClickListener = listener;
}
/**
- * Set a listener that will be notified when this menu is dismissed.
+ * Sets a listener that will be notified when this menu is dismissed.
*
- * @param listener Listener to notify
+ * @param listener the listener to notify
*/
public void setOnDismissListener(OnDismissListener listener) {
- mDismissListener = listener;
+ mOnDismissListener = listener;
}
/**
- * @hide
- */
- public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
- if (mMenuItemClickListener != null) {
- return mMenuItemClickListener.onMenuItemClick(item);
- }
- return false;
- }
-
- /**
- * @hide
- */
- public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
- if (mDismissListener != null) {
- mDismissListener.onDismiss(this);
- }
- }
-
- /**
- * @hide
- */
- public boolean onOpenSubMenu(MenuBuilder subMenu) {
- if (subMenu == null) return false;
-
- if (!subMenu.hasVisibleItems()) {
- return true;
- }
-
- // Current menu will be dismissed by the normal helper, submenu will be shown in its place.
- new MenuPopupHelper(mContext, subMenu, mAnchor).show();
- return true;
- }
-
- /**
- * @hide
- */
- public void onCloseSubMenu(SubMenuBuilder menu) {
- }
-
- /**
- * @hide
- */
- public void onMenuModeChange(MenuBuilder menu) {
- }
-
- /**
- * Interface responsible for receiving menu item click events if the items themselves
- * do not have individual item click listeners.
+ * Interface responsible for receiving menu item click events if the items
+ * themselves do not have individual item click listeners.
*/
public interface OnMenuItemClickListener {
- /**
- * This method will be invoked when a menu item is clicked if the item itself did
- * not already handle the event.
+ /**
+ * This method will be invoked when a menu item is clicked if the item
+ * itself did not already handle the event.
*
- * @param item {@link MenuItem} that was clicked
- * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
- */
- public boolean onMenuItemClick(MenuItem item);
+ * @param item the menu item that was clicked
+ * @return {@code true} if the event was handled, {@code false}
+ * otherwise
+ */
+ boolean onMenuItemClick(MenuItem item);
+ }
+
+ /**
+ * Callback interface used to notify the application that the menu has closed.
+ */
+ public interface OnDismissListener {
+ /**
+ * Called when the associated menu has been dismissed.
+ *
+ * @param menu the popup menu that was dismissed
+ */
+ void onDismiss(PopupMenu menu);
}
}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/SearchView.java b/v7/appcompat/src/android/support/v7/widget/SearchView.java
index e4644d0..e880ab9 100644
--- a/v7/appcompat/src/android/support/v7/widget/SearchView.java
+++ b/v7/appcompat/src/android/support/v7/widget/SearchView.java
@@ -48,7 +48,9 @@
import android.text.TextWatcher;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -1648,6 +1650,14 @@
mThreshold = getThreshold();
}
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
+ setMinWidth((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ getSearchViewTextMinWidthDp(), metrics));
+ }
+
void setSearchView(SearchView searchView) {
mSearchView = searchView;
}
@@ -1743,6 +1753,32 @@
}
return super.onKeyPreIme(keyCode, event);
}
+
+ /**
+ * Get minimum width of the search view text entry area.
+ */
+ private int getSearchViewTextMinWidthDp() {
+ final Configuration config = getResources().getConfiguration();
+
+ final int widthDp;
+ final int heightDp;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
+ widthDp = config.screenWidthDp;
+ heightDp = config.screenHeightDp;
+ } else {
+ final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ widthDp = (int) (metrics.widthPixels / metrics.density);
+ heightDp = (int) (metrics.heightPixels / metrics.density);
+ }
+
+ if (widthDp >= 960 && heightDp >= 720
+ && config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ return 256;
+ } else if (widthDp >= 600 || (widthDp >= 640 && heightDp >= 480)) {
+ return 192;
+ }
+ return 160;
+ }
}
private static class AutoCompleteTextViewReflector {
@@ -1821,4 +1857,4 @@
imm.showSoftInput(view, flags);
}
}
-}
\ No newline at end of file
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java b/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java
index 1c0151b..02ee49a 100644
--- a/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java
+++ b/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.v4.graphics.ColorUtils;
import android.util.TypedValue;
@@ -61,7 +60,7 @@
public static int getThemeAttrColor(Context context, int attr) {
TEMP_ARRAY[0] = attr;
- TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, null, TEMP_ARRAY);
try {
return a.getColor(0, 0);
} finally {
@@ -71,7 +70,7 @@
public static ColorStateList getThemeAttrColorStateList(Context context, int attr) {
TEMP_ARRAY[0] = attr;
- TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, null, TEMP_ARRAY);
try {
return a.getColorStateList(0);
} finally {
diff --git a/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java b/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
index 50e1046..78b3d94 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
@@ -21,6 +21,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
@@ -36,16 +37,21 @@
private final Context mContext;
private final TypedArray mWrapped;
+ private TypedValue mTypedValue;
+
public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set,
int[] attrs) {
- TypedArray array = context.obtainStyledAttributes(set, attrs);
- return new TintTypedArray(context, array);
+ return new TintTypedArray(context, context.obtainStyledAttributes(set, attrs));
}
public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set,
int[] attrs, int defStyleAttr, int defStyleRes) {
- TypedArray array = context.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes);
- return new TintTypedArray(context, array);
+ return new TintTypedArray(context,
+ context.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes));
+ }
+
+ public static TintTypedArray obtainStyledAttributes(Context context, int resid, int[] attrs) {
+ return new TintTypedArray(context, context.obtainStyledAttributes(resid, attrs));
}
private TintTypedArray(Context context, TypedArray array) {
@@ -118,6 +124,16 @@
}
public ColorStateList getColorStateList(int index) {
+ if (mWrapped.hasValue(index)) {
+ final int resourceId = mWrapped.getResourceId(index, 0);
+ if (resourceId != 0) {
+ final ColorStateList value =
+ ColorStateListUtils.getColorStateList(mContext, resourceId);
+ if (value != null) {
+ return value;
+ }
+ }
+ }
return mWrapped.getColorStateList(index);
}
@@ -162,7 +178,15 @@
}
public int getType(int index) {
- return mWrapped.getType(index);
+ if (Build.VERSION.SDK_INT >= 21) {
+ return mWrapped.getType(index);
+ } else {
+ if (mTypedValue == null) {
+ mTypedValue = new TypedValue();
+ }
+ mWrapped.getValue(index, mTypedValue);
+ return mTypedValue.type;
+ }
}
public boolean hasValue(int index) {
diff --git a/v7/appcompat/src/android/support/v7/widget/Toolbar.java b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
index a5f9612..eae7722 100644
--- a/v7/appcompat/src/android/support/v7/widget/Toolbar.java
+++ b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
@@ -105,6 +105,32 @@
* <p>In modern Android UIs developers should lean more on a visually distinct color scheme for
* toolbars than on their application icon. The use of application icon plus title as a standard
* layout is discouraged on API 21 devices and newer.</p>
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_buttonGravity
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_collapseContentDescription
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_collapseIcon
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetEnd
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetLeft
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetRight
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetStart
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_android_gravity
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_logo
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_logoDescription
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_maxButtonHeight
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_popupTheme
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_subtitle
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_subtitleTextAppearance
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_subtitleTextColor
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_title
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMargin
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginBottom
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginEnd
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginStart
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginTop
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleTextAppearance
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleTextColor
*/
public class Toolbar extends ViewGroup {
private static final String TAG = "Toolbar";
@@ -206,9 +232,15 @@
mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0);
mGravity = a.getInteger(R.styleable.Toolbar_android_gravity, mGravity);
- mButtonGravity = Gravity.TOP;
- mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom =
- a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, 0);
+ mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP);
+
+ // First read the correct attribute
+ int titleMargin = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargin, 0);
+ if (a.hasValue(R.styleable.Toolbar_titleMargins)) {
+ // Now read the deprecated attribute, if it has a value
+ titleMargin = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, titleMargin);
+ }
+ mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom = titleMargin;
final int marginStart = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginStart, -1);
if (marginStart >= 0) {
@@ -263,6 +295,7 @@
if (!TextUtils.isEmpty(subtitle)) {
setSubtitle(subtitle);
}
+
// Set the default context, since setPopupTheme() may be a no-op.
mPopupContext = getContext();
setPopupTheme(a.getResourceId(R.styleable.Toolbar_popupTheme, 0));
@@ -325,6 +358,116 @@
return mPopupTheme;
}
+ /**
+ * Sets the title margin.
+ *
+ * @param start the starting title margin in pixels
+ * @param top the top title margin in pixels
+ * @param end the ending title margin in pixels
+ * @param bottom the bottom title margin in pixels
+ * @see #getTitleMarginStart()
+ * @see #getTitleMarginTop()
+ * @see #getTitleMarginEnd()
+ * @see #getTitleMarginBottom()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMargin
+ */
+ public void setTitleMargin(int start, int top, int end, int bottom) {
+ mTitleMarginStart = start;
+ mTitleMarginTop = top;
+ mTitleMarginEnd = end;
+ mTitleMarginBottom = bottom;
+
+ requestLayout();
+ }
+
+ /**
+ * @return the starting title margin in pixels
+ * @see #setTitleMarginStart(int)
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginStart
+ */
+ public int getTitleMarginStart() {
+ return mTitleMarginStart;
+ }
+
+ /**
+ * Sets the starting title margin in pixels.
+ *
+ * @param margin the starting title margin in pixels
+ * @see #getTitleMarginStart()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginStart
+ */
+ public void setTitleMarginStart(int margin) {
+ mTitleMarginStart = margin;
+
+ requestLayout();
+ }
+
+ /**
+ * @return the top title margin in pixels
+ * @see #setTitleMarginTop(int)
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginTop
+ */
+ public int getTitleMarginTop() {
+ return mTitleMarginTop;
+ }
+
+ /**
+ * Sets the top title margin in pixels.
+ *
+ * @param margin the top title margin in pixels
+ * @see #getTitleMarginTop()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginTop
+ */
+ public void setTitleMarginTop(int margin) {
+ mTitleMarginTop = margin;
+
+ requestLayout();
+ }
+
+ /**
+ * @return the ending title margin in pixels
+ * @see #setTitleMarginEnd(int)
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginEnd
+ */
+ public int getTitleMarginEnd() {
+ return mTitleMarginEnd;
+ }
+
+ /**
+ * Sets the ending title margin in pixels.
+ *
+ * @param margin the ending title margin in pixels
+ * @see #getTitleMarginEnd()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginEnd
+ */
+ public void setTitleMarginEnd(int margin) {
+ mTitleMarginEnd = margin;
+
+ requestLayout();
+ }
+
+ /**
+ * @return the bottom title margin in pixels
+ * @see #setTitleMarginBottom(int)
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginBottom
+ */
+ public int getTitleMarginBottom() {
+ return mTitleMarginBottom;
+ }
+
+ /**
+ * Sets the bottom title margin in pixels.
+ *
+ * @param margin the bottom title margin in pixels
+ * @see #getTitleMarginBottom()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginBottom
+ */
+ public void setTitleMarginBottom(int margin) {
+ mTitleMarginBottom = margin;
+
+ requestLayout();
+ }
+
public void onRtlPropertiesChanged(int layoutDirection) {
if (Build.VERSION.SDK_INT >= 17) {
super.onRtlPropertiesChanged(layoutDirection);
@@ -721,6 +864,8 @@
* as screen readers or tooltips.
*
* @return The navigation button's content description
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
*/
@Nullable
public CharSequence getNavigationContentDescription() {
@@ -734,6 +879,8 @@
*
* @param resId Resource ID of a content description string to set, or 0 to
* clear the description
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
*/
public void setNavigationContentDescription(@StringRes int resId) {
setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null);
@@ -746,6 +893,8 @@
*
* @param description Content description to set, or <code>null</code> to
* clear the content description
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
*/
public void setNavigationContentDescription(@Nullable CharSequence description) {
if (!TextUtils.isEmpty(description)) {
@@ -767,6 +916,8 @@
* tooltips.</p>
*
* @param resId Resource ID of a drawable to set
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
*/
public void setNavigationIcon(@DrawableRes int resId) {
setNavigationIcon(mDrawableManager.getDrawable(getContext(), resId));
@@ -783,6 +934,8 @@
* tooltips.</p>
*
* @param icon Drawable to set, may be null to clear the icon
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
*/
public void setNavigationIcon(@Nullable Drawable icon) {
if (icon != null) {
@@ -803,6 +956,8 @@
* Return the current drawable used as the navigation icon.
*
* @return The navigation icon drawable
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
*/
@Nullable
public Drawable getNavigationIcon() {
diff --git a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
index e167c9b..91d93ca 100644
--- a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
+++ b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
@@ -27,6 +27,13 @@
<android.support.v7.custom.FitWindowsContentLayout
android:id="@+id/test_content"
android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:layout_height="match_parent">
+
+ <EditText
+ android:id="@+id/editText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ </android.support.v7.custom.FitWindowsContentLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
index f53b8e1..97383c3 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
@@ -25,9 +25,12 @@
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MenuItem;
+import android.view.View;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
public abstract class BaseKeyboardShortcutsTestCase<A extends BaseTestActivity>
extends BaseInstrumentationTestCase<A> {
@@ -61,4 +64,65 @@
assertEquals("Correct options item selected", selectedItem.getItemId(), expectedId);
}
+ @Test
+ @SmallTest
+ public void testAccessActionBar() throws Throwable {
+ final BaseTestActivity activity = getActivity();
+
+ final View editText = activity.findViewById(R.id.editText);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ editText.requestFocus();
+ }
+ });
+
+ getInstrumentation().waitForIdleSync();
+ sendControlChar('<');
+ getInstrumentation().waitForIdleSync();
+
+ // Should jump to the action bar after control-<
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(editText.hasFocus());
+ final View toolbar = activity.findViewById(R.id.toolbar);
+ assertTrue(toolbar.hasFocus());
+ }
+ });
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+
+ // Should jump to the first view again.
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(editText.hasFocus());
+ }
+ });
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
+ getInstrumentation().waitForIdleSync();
+
+ // Now it shouldn't go up to action bar -- it doesn't allow taking focus once left
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(editText.hasFocus());
+ }
+ });
+ }
+
+ private void sendControlChar(char key) throws Throwable {
+ KeyEvent tempEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A);
+ KeyCharacterMap map = tempEvent.getKeyCharacterMap();
+ KeyEvent[] events = map.getEvents(new char[] {key});
+ for (int i = 0; i < events.length; i++) {
+ long time = SystemClock.uptimeMillis();
+ KeyEvent event = events[i];
+ KeyEvent controlKey = new KeyEvent(time, time, event.getAction(), event.getKeyCode(),
+ event.getRepeatCount(), event.getMetaState() | KeyEvent.META_CTRL_ON);
+ getInstrumentation().sendKeySync(controlKey);
+ }
+ }
+
}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/MenuBuilderTest.java b/v7/appcompat/tests/src/android/support/v7/app/MenuBuilderTest.java
new file mode 100644
index 0000000..09ed7ab
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/MenuBuilderTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 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.app;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.view.menu.MenuBuilder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+public class MenuBuilderTest {
+
+ @Test
+ public void setOptionalIconsVisibleMethodShouldRemainPublic() throws Exception {
+ // This test is to verify workaround for bug in the ROM of Explay Fresh devices with 4.2.2 ROM.
+ // Manufacturer has modified ROM and added a public method setOptionalIconsVisible
+ // to android.view.Menu interface. Because of that the runtime can't load MenuBuilder class
+ // because it had no such public method (it was package local)
+ Method method = MenuBuilder.class
+ .getMethod("setOptionalIconsVisible", boolean.class);
+ assertNotNull(method);
+ assertTrue(Modifier.isPublic(method.getModifiers()));
+ }
+}
+
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java b/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java
index ff3af31..7ad44bd 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java
@@ -46,6 +46,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ overridePendingTransition(0, 0);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
final int contentView = getContentViewLayoutResId();
if (contentView > 0) {
@@ -55,6 +56,12 @@
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
+
protected abstract int getContentViewLayoutResId();
protected void onContentViewSet() {
diff --git a/v7/gridlayout/Android.mk b/v7/gridlayout/Android.mk
index 27ddfbd..38186cb 100644
--- a/v7/gridlayout/Android.mk
+++ b/v7/gridlayout/Android.mk
@@ -35,3 +35,5 @@
support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES) android-support-v7-gridlayout
support_module_java_packages := android.support.v7.widget
include $(SUPPORT_API_CHECK)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java b/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
index 8126b38..bb7384d 100644
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
+++ b/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
@@ -17,9 +17,21 @@
package android.support.v7.widget.test;
import android.app.Activity;
+import android.os.Bundle;
/**
* @hide
*/
public class GridLayoutTestActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ overridePendingTransition(0, 0);
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
}
diff --git a/v7/mediarouter/Android.mk b/v7/mediarouter/Android.mk
index 8af3b34..2a9bea9 100644
--- a/v7/mediarouter/Android.mk
+++ b/v7/mediarouter/Android.mk
@@ -61,6 +61,16 @@
support_module_src_files += $(LOCAL_SRC_FILES)
+# A helper sub-library that makes direct use of V24 APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-mediarouter-api24
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, api24)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-jellybean-mr2
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+support_module_src_files += $(LOCAL_SRC_FILES)
+
# Here is the final static library that apps can link against.
# The R class is automatically excluded from the generated library.
# Applications that use this library must specify LOCAL_RESOURCE_DIR
@@ -69,7 +79,7 @@
LOCAL_MODULE := android-support-v7-mediarouter
LOCAL_SDK_VERSION := 7
LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-jellybean-mr2
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-api24
LOCAL_JAVA_LIBRARIES := android-support-v4 android-support-v7-mediarouter-res \
android-support-v7-appcompat \
android-support-v7-palette
@@ -81,7 +91,19 @@
# ---------------------------------------------
support_module := $(LOCAL_MODULE)
support_module_api_dir := $(LOCAL_PATH)/api
-support_module_src_files := $(LOCAL_SRC_FILES)
+# We're asking doclava to generate stubs for android.support.v7.app in addition
+# to mediarouter, so we'll have to point doclava at the sources for
+# android.support.v7.app. Note that this API definition will overlap with that
+# of the android.support.v7.app package.
+support_module_src_files := $(LOCAL_SRC_FILES) \
+ ../appcompat/src/android/support/v7/app/AppCompatDelegate.java \
+ ../appcompat/src/android/support/v7/app/AppCompatCallback.java \
+ ../appcompat/src/android/support/v7/app/AppCompatDialog.java \
+ ../appcompat/src/android/support/v7/app/AlertDialog.java \
+ ../appcompat/src/android/support/v7/app/ActionBar.java \
+ ../appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java \
+
+
support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES)
support_module_java_packages := android.support.v7.app android.support.v7.media
include $(SUPPORT_API_CHECK)
diff --git a/v7/mediarouter/api/current.txt b/v7/mediarouter/api/current.txt
index 122eac5..7aa1507 100644
--- a/v7/mediarouter/api/current.txt
+++ b/v7/mediarouter/api/current.txt
@@ -3,46 +3,37 @@
public abstract class ActionBar {
ctor public ActionBar();
method public abstract void addOnMenuVisibilityListener(android.support.v7.app.ActionBar.OnMenuVisibilityListener);
- method public abstract void addTab(android.support.v7.app.ActionBar.Tab);
- method public abstract void addTab(android.support.v7.app.ActionBar.Tab, boolean);
- method public abstract void addTab(android.support.v7.app.ActionBar.Tab, int);
- method public abstract void addTab(android.support.v7.app.ActionBar.Tab, int, boolean);
- method public boolean collapseActionView();
- method public void dispatchMenuVisibilityChanged(boolean);
+ method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab);
+ method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab, boolean);
+ method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab, int);
+ method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab, int, boolean);
method public abstract android.view.View getCustomView();
method public abstract int getDisplayOptions();
method public float getElevation();
method public abstract int getHeight();
method public int getHideOffset();
- method public abstract int getNavigationItemCount();
- method public abstract int getNavigationMode();
- method public abstract int getSelectedNavigationIndex();
- method public abstract android.support.v7.app.ActionBar.Tab getSelectedTab();
+ method public abstract deprecated int getNavigationItemCount();
+ method public abstract deprecated int getNavigationMode();
+ method public abstract deprecated int getSelectedNavigationIndex();
+ method public abstract deprecated android.support.v7.app.ActionBar.Tab getSelectedTab();
method public abstract java.lang.CharSequence getSubtitle();
- method public abstract android.support.v7.app.ActionBar.Tab getTabAt(int);
- method public abstract int getTabCount();
+ method public abstract deprecated android.support.v7.app.ActionBar.Tab getTabAt(int);
+ method public abstract deprecated int getTabCount();
method public android.content.Context getThemedContext();
method public abstract java.lang.CharSequence getTitle();
method public abstract void hide();
- method public boolean invalidateOptionsMenu();
method public boolean isHideOnContentScrollEnabled();
method public abstract boolean isShowing();
- method public boolean isTitleTruncated();
- method public abstract android.support.v7.app.ActionBar.Tab newTab();
- method public void onConfigurationChanged(android.content.res.Configuration);
- method public boolean onKeyShortcut(int, android.view.KeyEvent);
- method public boolean onMenuKeyEvent(android.view.KeyEvent);
- method public boolean openOptionsMenu();
- method public abstract void removeAllTabs();
+ method public abstract deprecated android.support.v7.app.ActionBar.Tab newTab();
+ method public abstract deprecated void removeAllTabs();
method public abstract void removeOnMenuVisibilityListener(android.support.v7.app.ActionBar.OnMenuVisibilityListener);
- method public abstract void removeTab(android.support.v7.app.ActionBar.Tab);
- method public abstract void removeTabAt(int);
- method public abstract void selectTab(android.support.v7.app.ActionBar.Tab);
+ method public abstract deprecated void removeTab(android.support.v7.app.ActionBar.Tab);
+ method public abstract deprecated void removeTabAt(int);
+ method public abstract deprecated void selectTab(android.support.v7.app.ActionBar.Tab);
method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable);
method public abstract void setCustomView(android.view.View);
method public abstract void setCustomView(android.view.View, android.support.v7.app.ActionBar.LayoutParams);
method public abstract void setCustomView(int);
- method public void setDefaultDisplayHomeAsUpEnabled(boolean);
method public abstract void setDisplayHomeAsUpEnabled(boolean);
method public abstract void setDisplayOptions(int);
method public abstract void setDisplayOptions(int, int);
@@ -60,29 +51,26 @@
method public void setHomeButtonEnabled(boolean);
method public abstract void setIcon(int);
method public abstract void setIcon(android.graphics.drawable.Drawable);
- method public abstract void setListNavigationCallbacks(android.widget.SpinnerAdapter, android.support.v7.app.ActionBar.OnNavigationListener);
+ method public abstract deprecated void setListNavigationCallbacks(android.widget.SpinnerAdapter, android.support.v7.app.ActionBar.OnNavigationListener);
method public abstract void setLogo(int);
method public abstract void setLogo(android.graphics.drawable.Drawable);
- method public abstract void setNavigationMode(int);
- method public abstract void setSelectedNavigationItem(int);
- method public void setShowHideAnimationEnabled(boolean);
+ method public abstract deprecated void setNavigationMode(int);
+ method public abstract deprecated void setSelectedNavigationItem(int);
method public void setSplitBackgroundDrawable(android.graphics.drawable.Drawable);
method public void setStackedBackgroundDrawable(android.graphics.drawable.Drawable);
method public abstract void setSubtitle(java.lang.CharSequence);
method public abstract void setSubtitle(int);
method public abstract void setTitle(java.lang.CharSequence);
method public abstract void setTitle(int);
- method public void setWindowTitle(java.lang.CharSequence);
method public abstract void show();
- method public android.support.v7.view.ActionMode startActionMode(android.support.v7.view.ActionMode.Callback);
field public static final int DISPLAY_HOME_AS_UP = 4; // 0x4
field public static final int DISPLAY_SHOW_CUSTOM = 16; // 0x10
field public static final int DISPLAY_SHOW_HOME = 2; // 0x2
field public static final int DISPLAY_SHOW_TITLE = 8; // 0x8
field public static final int DISPLAY_USE_LOGO = 1; // 0x1
- field public static final int NAVIGATION_MODE_LIST = 1; // 0x1
- field public static final int NAVIGATION_MODE_STANDARD = 0; // 0x0
- field public static final int NAVIGATION_MODE_TABS = 2; // 0x2
+ field public static final deprecated int NAVIGATION_MODE_LIST = 1; // 0x1
+ field public static final deprecated int NAVIGATION_MODE_STANDARD = 0; // 0x0
+ field public static final deprecated int NAVIGATION_MODE_TABS = 2; // 0x2
}
public static class ActionBar.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
@@ -99,11 +87,11 @@
method public abstract void onMenuVisibilityChanged(boolean);
}
- public static abstract interface ActionBar.OnNavigationListener {
+ public static abstract deprecated interface ActionBar.OnNavigationListener {
method public abstract boolean onNavigationItemSelected(int, long);
}
- public static abstract class ActionBar.Tab {
+ public static abstract deprecated class ActionBar.Tab {
ctor public ActionBar.Tab();
method public abstract java.lang.CharSequence getContentDescription();
method public abstract android.view.View getCustomView();
@@ -125,7 +113,7 @@
field public static final int INVALID_POSITION = -1; // 0xffffffff
}
- public static abstract interface ActionBar.TabListener {
+ public static abstract deprecated interface ActionBar.TabListener {
method public abstract void onTabReselected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
method public abstract void onTabSelected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
method public abstract void onTabUnselected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
@@ -157,6 +145,10 @@
method public abstract void setActionBarUpIndicator(android.graphics.drawable.Drawable, int);
}
+ public static abstract interface ActionBarDrawerToggle.DelegateProvider {
+ method public abstract android.support.v7.app.ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
+ }
+
public class AlertDialog extends android.support.v7.app.AppCompatDialog implements android.content.DialogInterface {
ctor protected AlertDialog(android.content.Context);
ctor protected AlertDialog(android.content.Context, int);
@@ -174,6 +166,47 @@
method public void setView(android.view.View, int, int, int, int);
}
+ public static class AlertDialog.Builder {
+ ctor public AlertDialog.Builder(android.content.Context);
+ ctor public AlertDialog.Builder(android.content.Context, int);
+ method public android.support.v7.app.AlertDialog create();
+ method public android.content.Context getContext();
+ method public android.support.v7.app.AlertDialog.Builder setAdapter(android.widget.ListAdapter, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setCancelable(boolean);
+ method public android.support.v7.app.AlertDialog.Builder setCursor(android.database.Cursor, android.content.DialogInterface.OnClickListener, java.lang.String);
+ method public android.support.v7.app.AlertDialog.Builder setCustomTitle(android.view.View);
+ method public android.support.v7.app.AlertDialog.Builder setIcon(int);
+ method public android.support.v7.app.AlertDialog.Builder setIcon(android.graphics.drawable.Drawable);
+ method public android.support.v7.app.AlertDialog.Builder setIconAttribute(int);
+ method public android.support.v7.app.AlertDialog.Builder setInverseBackgroundForced(boolean);
+ method public android.support.v7.app.AlertDialog.Builder setItems(int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setItems(java.lang.CharSequence[], android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setMessage(int);
+ method public android.support.v7.app.AlertDialog.Builder setMessage(java.lang.CharSequence);
+ method public android.support.v7.app.AlertDialog.Builder setMultiChoiceItems(int, boolean[], android.content.DialogInterface.OnMultiChoiceClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setMultiChoiceItems(java.lang.CharSequence[], boolean[], android.content.DialogInterface.OnMultiChoiceClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setMultiChoiceItems(android.database.Cursor, java.lang.String, java.lang.String, android.content.DialogInterface.OnMultiChoiceClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setNegativeButton(int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setNegativeButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setNeutralButton(int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setNeutralButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setOnCancelListener(android.content.DialogInterface.OnCancelListener);
+ method public android.support.v7.app.AlertDialog.Builder setOnDismissListener(android.content.DialogInterface.OnDismissListener);
+ method public android.support.v7.app.AlertDialog.Builder setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener);
+ method public android.support.v7.app.AlertDialog.Builder setOnKeyListener(android.content.DialogInterface.OnKeyListener);
+ method public android.support.v7.app.AlertDialog.Builder setPositiveButton(int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setPositiveButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(int, int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(android.database.Cursor, int, java.lang.String, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(java.lang.CharSequence[], int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(android.widget.ListAdapter, int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setTitle(int);
+ method public android.support.v7.app.AlertDialog.Builder setTitle(java.lang.CharSequence);
+ method public android.support.v7.app.AlertDialog.Builder setView(int);
+ method public android.support.v7.app.AlertDialog.Builder setView(android.view.View);
+ method public android.support.v7.app.AlertDialog show();
+ }
+
public abstract interface AppCompatCallback {
method public abstract void onSupportActionModeFinished(android.support.v7.view.ActionMode);
method public abstract void onSupportActionModeStarted(android.support.v7.view.ActionMode);
diff --git a/v7/mediarouter/api24/android/support/v7/media/MediaRouterApi24.java b/v7/mediarouter/api24/android/support/v7/media/MediaRouterApi24.java
new file mode 100644
index 0000000..3734b59
--- /dev/null
+++ b/v7/mediarouter/api24/android/support/v7/media/MediaRouterApi24.java
@@ -0,0 +1,25 @@
+/*
+ * 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.media;
+
+final class MediaRouterApi24 {
+ public static final class RouteInfo {
+ public static int getDeviceType(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getDeviceType();
+ }
+ }
+}
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index 37224f0..883f939 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -16,6 +16,7 @@
jellybean.java.srcDir 'jellybean'
jellybeanmr1.java.srcDir 'jellybean-mr1'
jellybeanmr2.java.srcDir 'jellybean-mr2'
+ api24.java.srcDir 'api24'
}
// create a jar task for the code above
@@ -23,6 +24,7 @@
from sourceSets.jellybean.output
from sourceSets.jellybeanmr1.output
from sourceSets.jellybeanmr2.output
+ from sourceSets.api24.output
baseName "internal_impl"
}
@@ -33,10 +35,15 @@
jellybeanmr1Compile getAndroidPrebuilt('17')
jellybeanmr1Compile sourceSets.jellybean.output
- jellybeanmr2Compile getAndroidPrebuilt('current')
+ jellybeanmr2Compile getAndroidPrebuilt('18')
jellybeanmr2Compile sourceSets.jellybean.output
jellybeanmr2Compile sourceSets.jellybeanmr1.output
+ api24Compile getAndroidPrebuilt('current')
+ api24Compile sourceSets.jellybean.output
+ api24Compile sourceSets.jellybeanmr1.output
+ api24Compile sourceSets.jellybeanmr2.output
+
compile files(jar.archivePath)
}
@@ -105,6 +112,8 @@
sourcesJarTask.from project.sourceSets.jellybeanmr1.java.srcDirs
javadocTask.source project.sourceSets.jellybeanmr2.java
sourcesJarTask.from project.sourceSets.jellybeanmr2.java.srcDirs
+ javadocTask.source project.sourceSets.api24.java
+ sourcesJarTask.from project.sourceSets.api24.java.srcDirs
artifacts.add('archives', javadocJarTask);
artifacts.add('archives', sourcesJarTask);
diff --git a/v7/mediarouter/res/values-b+sr+Latn/strings.xml b/v7/mediarouter/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..c10c42d
--- /dev/null
+++ b/v7/mediarouter/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Uređaji"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Dugme Prebaci"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Prebacujte na"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Pronalaženje uređaja"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini vezu"</string>
+ <string name="mr_controller_stop" msgid="4570331844078181931">"Zaustavi prebacivanje"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Zatvori"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Pusti"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pauziraj"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Proširi"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skupi"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nema izabranih medija"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nisu dostupne nikakve informacije"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Prebacuje se ekran"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-fa/strings.xml b/v7/mediarouter/res/values-fa/strings.xml
index 87a81ba..2c812ae 100644
--- a/v7/mediarouter/res/values-fa/strings.xml
+++ b/v7/mediarouter/res/values-fa/strings.xml
@@ -25,7 +25,7 @@
<string name="mr_controller_stop" msgid="4570331844078181931">"توقف ارسال محتوا"</string>
<string name="mr_controller_close_description" msgid="7333862312480583260">"بستن"</string>
<string name="mr_controller_play" msgid="683634565969987458">"پخش"</string>
- <string name="mr_controller_pause" msgid="5451884435510905406">"توقف موقت"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"مکث"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"بزرگ کردن"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"کوچک کردن"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"رسانه انتخاب نشده است"</string>
diff --git a/v7/mediarouter/res/values-fr/strings.xml b/v7/mediarouter/res/values-fr/strings.xml
index de94f18..6e14ccc 100644
--- a/v7/mediarouter/res/values-fr/strings.xml
+++ b/v7/mediarouter/res/values-fr/strings.xml
@@ -19,7 +19,7 @@
<string name="mr_system_route_name" msgid="5441529851481176817">"Système"</string>
<string name="mr_user_route_category_name" msgid="7498112907524977311">"Appareils"</string>
<string name="mr_button_content_description" msgid="3698378085901466129">"Icône Cast"</string>
- <string name="mr_chooser_title" msgid="414301941546135990">"Diffuser sur"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Caster sur"</string>
<string name="mr_chooser_searching" msgid="6349900579507521956">"Recherche d\'appareils en cours…"</string>
<string name="mr_controller_disconnect" msgid="1227264889412989580">"Déconnecter"</string>
<string name="mr_controller_stop" msgid="4570331844078181931">"Arrêter de diffuser"</string>
diff --git a/v7/mediarouter/res/values-hy-rAM/strings.xml b/v7/mediarouter/res/values-hy-rAM/strings.xml
index 85bd488..b05c17c 100644
--- a/v7/mediarouter/res/values-hy-rAM/strings.xml
+++ b/v7/mediarouter/res/values-hy-rAM/strings.xml
@@ -19,7 +19,7 @@
<string name="mr_system_route_name" msgid="5441529851481176817">"Համակարգ"</string>
<string name="mr_user_route_category_name" msgid="7498112907524977311">"Սարքեր"</string>
<string name="mr_button_content_description" msgid="3698378085901466129">"Հեռարձակման կոճակ"</string>
- <string name="mr_chooser_title" msgid="414301941546135990">"Հեռարձակել դեպի"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Ընտրեք սարքը"</string>
<string name="mr_chooser_searching" msgid="6349900579507521956">"Սարքերի որոնում"</string>
<string name="mr_controller_disconnect" msgid="1227264889412989580">"Անջատել"</string>
<string name="mr_controller_stop" msgid="4570331844078181931">"Դադարեցնել հեռարձակումը"</string>
diff --git a/v7/mediarouter/res/values-in/strings.xml b/v7/mediarouter/res/values-in/strings.xml
index 3d01880..4d84852 100644
--- a/v7/mediarouter/res/values-in/strings.xml
+++ b/v7/mediarouter/res/values-in/strings.xml
@@ -19,7 +19,7 @@
<string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
<string name="mr_user_route_category_name" msgid="7498112907524977311">"Perangkat"</string>
<string name="mr_button_content_description" msgid="3698378085901466129">"Tombol transmisi"</string>
- <string name="mr_chooser_title" msgid="414301941546135990">"Transmisi ke"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Transmisikan ke"</string>
<string name="mr_chooser_searching" msgid="6349900579507521956">"Mencari perangkat"</string>
<string name="mr_controller_disconnect" msgid="1227264889412989580">"Putuskan sambungan"</string>
<string name="mr_controller_stop" msgid="4570331844078181931">"Hentikan transmisi"</string>
diff --git a/v7/mediarouter/res/values-ja/strings.xml b/v7/mediarouter/res/values-ja/strings.xml
index ea8d838..ffe9054 100644
--- a/v7/mediarouter/res/values-ja/strings.xml
+++ b/v7/mediarouter/res/values-ja/strings.xml
@@ -19,7 +19,7 @@
<string name="mr_system_route_name" msgid="5441529851481176817">"システム"</string>
<string name="mr_user_route_category_name" msgid="7498112907524977311">"端末"</string>
<string name="mr_button_content_description" msgid="3698378085901466129">"キャストアイコン"</string>
- <string name="mr_chooser_title" msgid="414301941546135990">"キャスト先"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"キャストするデバイス"</string>
<string name="mr_chooser_searching" msgid="6349900579507521956">"端末を検索しています"</string>
<string name="mr_controller_disconnect" msgid="1227264889412989580">"接続を解除"</string>
<string name="mr_controller_stop" msgid="4570331844078181931">"キャストを停止"</string>
diff --git a/v7/mediarouter/res/values-ko/strings.xml b/v7/mediarouter/res/values-ko/strings.xml
index 2b3455f..fc1dbac 100644
--- a/v7/mediarouter/res/values-ko/strings.xml
+++ b/v7/mediarouter/res/values-ko/strings.xml
@@ -19,7 +19,7 @@
<string name="mr_system_route_name" msgid="5441529851481176817">"시스템"</string>
<string name="mr_user_route_category_name" msgid="7498112907524977311">"기기"</string>
<string name="mr_button_content_description" msgid="3698378085901466129">"전송 버튼"</string>
- <string name="mr_chooser_title" msgid="414301941546135990">"전송 대상"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"전송할 기기"</string>
<string name="mr_chooser_searching" msgid="6349900579507521956">"기기를 찾는 중"</string>
<string name="mr_controller_disconnect" msgid="1227264889412989580">"연결 해제"</string>
<string name="mr_controller_stop" msgid="4570331844078181931">"전송 중지"</string>
diff --git a/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java
index 6a09650..21ff5b4 100644
--- a/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java
@@ -49,6 +49,9 @@
}
public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ return new Api24Impl(context, syncCallback);
+ }
if (Build.VERSION.SDK_INT >= 18) {
return new JellybeanMr2Impl(context, syncCallback);
}
@@ -836,4 +839,21 @@
return MediaRouterJellybeanMr2.RouteInfo.isConnecting(record.mRouteObj);
}
}
+
+ /**
+ * Api24 implementation.
+ */
+ private static class Api24Impl extends JellybeanMr2Impl {
+ public Api24Impl(Context context, SyncCallback syncCallback) {
+ super(context, syncCallback);
+ }
+
+ @Override
+ protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
+ MediaRouteDescriptor.Builder builder) {
+ super.onBuildSystemRouteDescriptor(record, builder);
+
+ builder.setDeviceType(MediaRouterApi24.RouteInfo.getDeviceType(record.mRouteObj));
+ }
+ }
}
diff --git a/v7/preference/Android.mk b/v7/preference/Android.mk
index d12b479..601d8bd 100644
--- a/v7/preference/Android.mk
+++ b/v7/preference/Android.mk
@@ -20,8 +20,8 @@
# contains will not be linked into the final static library.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v7-preference-res
+LOCAL_SRC_FILES := $(call all-java-files-under, constants)
LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
LOCAL_RESOURCE_DIR := \
frameworks/support/v7/appcompat/res \
frameworks/support/v7/recyclerview/res \
diff --git a/v7/preference/api/current.txt b/v7/preference/api/current.txt
index 26a3e12..4bed4dd 100644
--- a/v7/preference/api/current.txt
+++ b/v7/preference/api/current.txt
@@ -35,6 +35,14 @@
method public abstract android.support.v7.preference.Preference findPreference(java.lang.CharSequence);
}
+ public class DropDownPreference extends android.support.v7.preference.ListPreference {
+ ctor public DropDownPreference(android.content.Context);
+ ctor public DropDownPreference(android.content.Context, android.util.AttributeSet);
+ ctor public DropDownPreference(android.content.Context, android.util.AttributeSet, int);
+ ctor public DropDownPreference(android.content.Context, android.util.AttributeSet, int, int);
+ method protected android.widget.ArrayAdapter createAdapter();
+ }
+
public class EditTextPreference extends android.support.v7.preference.DialogPreference {
ctor public EditTextPreference(android.content.Context, android.util.AttributeSet, int, int);
ctor public EditTextPreference(android.content.Context, android.util.AttributeSet, int);
@@ -117,6 +125,7 @@
method public void onBindViewHolder(android.support.v7.preference.PreferenceViewHolder);
method protected void onClick();
method public void onDependencyChanged(android.support.v7.preference.Preference, boolean);
+ method public void onDetached();
method protected java.lang.Object onGetDefaultValue(android.content.res.TypedArray, int);
method public void onParentChanged(android.support.v7.preference.Preference, boolean);
method protected void onPrepareForRemoval();
diff --git a/v7/preference/build.gradle b/v7/preference/build.gradle
index 45b4f96..1693b3a 100644
--- a/v7/preference/build.gradle
+++ b/v7/preference/build.gradle
@@ -30,6 +30,7 @@
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
main.java.srcDir 'src'
+ main.java.srcDir 'constants'
main.res.srcDir 'res'
main.assets.srcDir 'assets'
main.resources.srcDir 'src'
diff --git a/v7/preference/constants/android/support/v7/preference/AndroidResources.java b/v7/preference/constants/android/support/v7/preference/AndroidResources.java
new file mode 100644
index 0000000..d529ad2
--- /dev/null
+++ b/v7/preference/constants/android/support/v7/preference/AndroidResources.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 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.preference;
+
+public class AndroidResources {
+
+ public static final int ANDROID_R_ICON_FRAME = android.R.id.icon_frame;
+ public static final int ANDROID_R_LIST_CONTAINER = android.R.id.list_container;
+ public static final int ANDROID_R_SWITCH_WIDGET = android.R.id.switch_widget;
+ public static final int ANDROID_R_PREFERENCE_FRAGMENT_STYLE
+ = android.R.attr.preferenceFragmentStyle;
+ public static final int ANDROID_R_EDITTEXT_PREFERENCE_STYLE
+ = android.R.attr.editTextPreferenceStyle;
+
+}
diff --git a/v7/preference/res/layout/preference_dropdown.xml b/v7/preference/res/layout/preference_dropdown.xml
new file mode 100644
index 0000000..09ea8fb
--- /dev/null
+++ b/v7/preference/res/layout/preference_dropdown.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:visibility="invisible" />
+
+ <include layout="@layout/preference" />
+
+</FrameLayout>
diff --git a/v7/preference/res/layout/preference_list_fragment.xml b/v7/preference/res/layout/preference_list_fragment.xml
index 44c5438..fe11fb2 100644
--- a/v7/preference/res/layout/preference_list_fragment.xml
+++ b/v7/preference/res/layout/preference_list_fragment.xml
@@ -21,7 +21,7 @@
android:layout_width="match_parent" >
<FrameLayout
- android:id="@+id/list_container"
+ android:id="@android:id/list_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
diff --git a/v7/preference/res/layout/preference_widget_checkbox.xml b/v7/preference/res/layout/preference_widget_checkbox.xml
index e53b7df..cc99443 100644
--- a/v7/preference/res/layout/preference_widget_checkbox.xml
+++ b/v7/preference/res/layout/preference_widget_checkbox.xml
@@ -18,7 +18,7 @@
<!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated
inside android.R.layout.preference. -->
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/checkbox"
+ android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
diff --git a/v7/preference/res/values/attrs.xml b/v7/preference/res/values/attrs.xml
index cd6f325..bd64109 100644
--- a/v7/preference/res/values/attrs.xml
+++ b/v7/preference/res/values/attrs.xml
@@ -50,6 +50,8 @@
<attr name="editTextPreferenceStyle" format="reference" />
<!-- Default style for RingtonePreference. -->
<attr name="ringtonePreferenceStyle" format="reference" />
+ <!-- Default style for DropDownPreference. -->
+ <attr name="dropdownPreferenceStyle" format="reference" />
<!-- The preference layout that has the child/tabbed effect. -->
<attr name="preferenceLayoutChild" format="reference" />
<!-- Preference panel style -->
@@ -245,4 +247,11 @@
<attr name="android:maxHeight" />
</declare-styleable>
+ <!-- Used to access some android attrs -->
+ <declare-styleable name="BackgroundStyle">
+ <attr name="android:selectableItemBackground" />
+ <!-- Need a non-android: attr here so that gradle doesn't remove it -->
+ <attr name="selectableItemBackground" />
+ </declare-styleable>
+
</resources>
diff --git a/v7/preference/res/values/styles.xml b/v7/preference/res/values/styles.xml
index 774c0c9..06b24fa 100644
--- a/v7/preference/res/values/styles.xml
+++ b/v7/preference/res/values/styles.xml
@@ -65,4 +65,8 @@
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">16dp</item>
</style>
+
+ <style name="Preference.DropDown">
+ <item name="android:layout">@layout/preference_dropdown</item>
+ </style>
</resources>
diff --git a/v7/preference/res/values/themes.xml b/v7/preference/res/values/themes.xml
index 8a7eaa3..bb7f496 100644
--- a/v7/preference/res/values/themes.xml
+++ b/v7/preference/res/values/themes.xml
@@ -27,5 +27,6 @@
<item name="dialogPreferenceStyle">@style/Preference.DialogPreference</item>
<item name="editTextPreferenceStyle">@style/Preference.DialogPreference.EditTextPreference</item>
<item name="preferenceFragmentListStyle">@style/PreferenceFragmentList</item>
+ <item name="dropdownPreferenceStyle">@style/Preference.DropDown</item>
</style>
</resources>
diff --git a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
index 61eb13c..94f0751 100644
--- a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
+++ b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
@@ -61,7 +61,8 @@
}
public CheckBoxPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.checkBoxPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.checkBoxPreferenceStyle,
+ android.R.attr.checkBoxPreferenceStyle));
}
public CheckBoxPreference(Context context) {
@@ -72,7 +73,7 @@
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- View checkboxView = holder.findViewById(R.id.checkbox);
+ View checkboxView = holder.findViewById(android.R.id.checkbox);
if (checkboxView != null && checkboxView instanceof Checkable) {
((Checkable) checkboxView).setChecked(mChecked);
}
diff --git a/v7/preference/src/android/support/v7/preference/DialogPreference.java b/v7/preference/src/android/support/v7/preference/DialogPreference.java
index bf0f4ef..158accb 100644
--- a/v7/preference/src/android/support/v7/preference/DialogPreference.java
+++ b/v7/preference/src/android/support/v7/preference/DialogPreference.java
@@ -90,7 +90,8 @@
}
public DialogPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.dialogPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
+ android.R.attr.dialogPreferenceStyle));
}
public DialogPreference(Context context) {
diff --git a/v7/preference/src/android/support/v7/preference/DropDownPreference.java b/v7/preference/src/android/support/v7/preference/DropDownPreference.java
new file mode 100644
index 0000000..e7a0514
--- /dev/null
+++ b/v7/preference/src/android/support/v7/preference/DropDownPreference.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 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.preference;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+
+public class DropDownPreference extends ListPreference {
+
+ private final Context mContext;
+ private final ArrayAdapter<String> mAdapter;
+
+ private Spinner mSpinner;
+
+ public DropDownPreference(Context context) {
+ this(context, null);
+ }
+
+ public DropDownPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.dropdownPreferenceStyle);
+ }
+
+ public DropDownPreference(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, 0);
+ }
+
+ public DropDownPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mContext = context;
+ mAdapter = createAdapter();
+
+ updateEntries();
+ }
+
+ @Override
+ protected void onClick() {
+ mSpinner.performClick();
+ }
+
+ @Override
+ public void setEntries(@NonNull CharSequence[] entries) {
+ super.setEntries(entries);
+ updateEntries();
+ }
+
+
+ /**
+ * By default, this class uses a simple {@link android.widget.ArrayAdapter}. But if you need
+ * a more complicated {@link android.widget.ArrayAdapter}, this method can be overriden to
+ * create a custom one.
+ * <p> Note: This method is called from the constructor. So, overriden methods will get called
+ * before any subclass initialization.
+ *
+ * @return The custom {@link android.widget.ArrayAdapter} that needs to be used with this class.
+ */
+ protected ArrayAdapter createAdapter() {
+ return new ArrayAdapter<>(mContext, android.R.layout.simple_spinner_dropdown_item);
+ }
+
+ private void updateEntries() {
+ mAdapter.clear();
+ if (getEntries() != null) {
+ for (CharSequence c : getEntries()) {
+ mAdapter.add(c.toString());
+ }
+ }
+ }
+
+ public void setValueIndex(int index) {
+ setValue(getEntryValues()[index].toString());
+ }
+
+ /**
+ * @hide
+ */
+ public int findSpinnerIndexOfValue(String value) {
+ CharSequence[] entryValues = getEntryValues();
+ if (value != null && entryValues != null) {
+ for (int i = entryValues.length - 1; i >= 0; i--) {
+ if (entryValues[i].equals(value)) {
+ return i;
+ }
+ }
+ }
+ return Spinner.INVALID_POSITION;
+ }
+
+ @Override
+ protected void notifyChanged() {
+ super.notifyChanged();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder view) {
+ mSpinner = (Spinner) view.itemView.findViewById(R.id.spinner);
+ mSpinner.setAdapter(mAdapter);
+ mSpinner.setOnItemSelectedListener(mItemSelectedListener);
+ mSpinner.setSelection(findSpinnerIndexOfValue(getValue()));
+ super.onBindViewHolder(view);
+ }
+
+ private final OnItemSelectedListener mItemSelectedListener = new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
+ if (position >= 0) {
+ String value = getEntryValues()[position].toString();
+ if (!value.equals(getValue()) && callChangeListener(value)) {
+ setValue(value);
+ }
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // noop
+ }
+ };
+}
+
diff --git a/v7/preference/src/android/support/v7/preference/EditTextPreference.java b/v7/preference/src/android/support/v7/preference/EditTextPreference.java
index c4941e6..423a0c9 100644
--- a/v7/preference/src/android/support/v7/preference/EditTextPreference.java
+++ b/v7/preference/src/android/support/v7/preference/EditTextPreference.java
@@ -20,6 +20,7 @@
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.v4.content.res.TypedArrayUtils;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.EditText;
@@ -46,7 +47,8 @@
}
public EditTextPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.editTextPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.editTextPreferenceStyle,
+ AndroidResources.ANDROID_R_EDITTEXT_PREFERENCE_STYLE));
}
public EditTextPreference(Context context) {
diff --git a/v7/preference/src/android/support/v7/preference/ListPreference.java b/v7/preference/src/android/support/v7/preference/ListPreference.java
index 848cfad..4507b26 100644
--- a/v7/preference/src/android/support/v7/preference/ListPreference.java
+++ b/v7/preference/src/android/support/v7/preference/ListPreference.java
@@ -74,7 +74,8 @@
}
public ListPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.dialogPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
+ android.R.attr.dialogPreferenceStyle));
}
public ListPreference(Context context) {
diff --git a/v7/preference/src/android/support/v7/preference/Preference.java b/v7/preference/src/android/support/v7/preference/Preference.java
index ebff2d7..6c7f909 100644
--- a/v7/preference/src/android/support/v7/preference/Preference.java
+++ b/v7/preference/src/android/support/v7/preference/Preference.java
@@ -278,7 +278,7 @@
mShouldDisableView =
TypedArrayUtils.getBoolean(a, R.styleable.Preference_shouldDisableView,
- R.styleable.Preference_shouldDisableView, true);
+ R.styleable.Preference_android_shouldDisableView, true);
a.recycle();
}
@@ -322,7 +322,8 @@
* @see #Preference(Context, AttributeSet, int)
*/
public Preference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.preferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
}
/**
@@ -512,7 +513,10 @@
imageView.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
}
- final View imageFrame = holder.findViewById(R.id.icon_frame);
+ View imageFrame = holder.findViewById(R.id.icon_frame);
+ if (imageFrame == null) {
+ imageFrame = holder.findViewById(AndroidResources.ANDROID_R_ICON_FRAME);
+ }
if (imageFrame != null) {
imageFrame.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
}
@@ -1105,6 +1109,16 @@
registerDependency();
}
+ /**
+ * Called when the Preference hierarchy has been detached from the
+ * list of preferences. This can also be called when this
+ * Preference has been removed from a group that was attached
+ * to the list of preferences.
+ */
+ public void onDetached() {
+ unregisterDependency();
+ }
+
private void registerDependency() {
if (TextUtils.isEmpty(mDependencyKey)) return;
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceCategory.java b/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
index bed7f7e..10a0753 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
@@ -17,6 +17,7 @@
package android.support.v7.preference;
import android.content.Context;
+import android.support.v4.content.res.TypedArrayUtils;
import android.util.AttributeSet;
/**
@@ -43,7 +44,8 @@
}
public PreferenceCategory(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.preferenceCategoryStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.preferenceCategoryStyle,
+ android.R.attr.preferenceCategoryStyle));
}
public PreferenceCategory(Context context) {
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java b/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
index 221d3ea..802fdb6 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
@@ -257,10 +257,10 @@
final View view = themedInflater.inflate(mLayoutResId, container, false);
- final View rawListContainer = view.findViewById(R.id.list_container);
+ final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER);
if (!(rawListContainer instanceof ViewGroup)) {
- throw new RuntimeException("Content has view with id attribute 'R.id.list_container' "
- + "that is not a ViewGroup class");
+ throw new RuntimeException("Content has view with id attribute "
+ + "'android.R.id.list_container' that is not a ViewGroup class");
}
final ViewGroup listContainer = (ViewGroup) rawListContainer;
@@ -309,14 +309,19 @@
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
if (mHavePrefs) {
bindPreferences();
}
mInitDone = true;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
@@ -345,9 +350,12 @@
@Override
public void onDestroyView() {
- mList = null;
mHandler.removeCallbacks(mRequestFocus);
mHandler.removeMessages(MSG_BIND_PREFERENCES);
+ if (mHavePrefs) {
+ unbindPreferences();
+ }
+ mList = null;
super.onDestroyView();
}
@@ -514,6 +522,15 @@
onBindPreferences();
}
+ private void unbindPreferences() {
+ final PreferenceScreen preferenceScreen = getPreferenceScreen();
+ if (preferenceScreen != null) {
+ preferenceScreen.onDetached();
+ getListView().setAdapter(null);
+ }
+ onUnbindPreferences();
+ }
+
/** @hide */
protected void onBindPreferences() {
}
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceGroup.java b/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
index a1ead97..2247962 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
@@ -233,6 +233,9 @@
mHandler.removeCallbacks(mClearRecycleCacheRunnable);
mHandler.post(mClearRecycleCacheRunnable);
}
+ if (mAttachedToHierarchy) {
+ preference.onDetached();
+ }
}
return success;
@@ -336,11 +339,17 @@
}
@Override
- protected void onPrepareForRemoval() {
- super.onPrepareForRemoval();
+ public void onDetached() {
+ super.onDetached();
// We won't be attached to the activity anymore
mAttachedToHierarchy = false;
+
+ // Dispatch to all contained preferences
+ final int preferenceCount = getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ getPreference(i).onDetached();
+ }
}
@Override
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java b/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
index d9f3dd6..e8bd872 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
@@ -16,6 +16,8 @@
package android.support.v7.preference;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
@@ -276,8 +278,18 @@
public PreferenceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final PreferenceLayout pl = mPreferenceLayouts.get(viewType);
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ TypedArray a
+ = parent.getContext().obtainStyledAttributes(null, R.styleable.BackgroundStyle);
+ Drawable background
+ = a.getDrawable(R.styleable.BackgroundStyle_android_selectableItemBackground);
+ if (background == null) {
+ background = parent.getContext().getResources()
+ .getDrawable(android.R.drawable.list_selector_background);
+ }
+ a.recycle();
final View view = inflater.inflate(pl.resId, parent, false);
+ view.setBackgroundDrawable(background);
final ViewGroup widgetFrame = (ViewGroup) view.findViewById(android.R.id.widget_frame);
if (widgetFrame != null) {
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceManager.java b/v7/preference/src/android/support/v7/preference/PreferenceManager.java
index 0c3e65f..1c460a1 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceManager.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceManager.java
@@ -238,6 +238,9 @@
*/
public boolean setPreferences(PreferenceScreen preferenceScreen) {
if (preferenceScreen != mPreferenceScreen) {
+ if (mPreferenceScreen != null) {
+ mPreferenceScreen.onDetached();
+ }
mPreferenceScreen = preferenceScreen;
return true;
}
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceScreen.java b/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
index 4efa58e..cbabf21 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
@@ -17,6 +17,7 @@
package android.support.v7.preference;
import android.content.Context;
+import android.support.v4.content.res.TypedArrayUtils;
import android.util.AttributeSet;
/**
@@ -81,7 +82,8 @@
* @hide-
*/
public PreferenceScreen(Context context, AttributeSet attrs) {
- super(context, attrs, R.attr.preferenceScreenStyle);
+ super(context, attrs, TypedArrayUtils.getAttr(context, R.attr.preferenceScreenStyle,
+ android.R.attr.preferenceScreenStyle));
}
@Override
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java b/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java
index 2b604bd..97a907a 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java
@@ -39,6 +39,8 @@
mCachedViews.put(android.R.id.summary, itemView.findViewById(android.R.id.summary));
mCachedViews.put(android.R.id.icon, itemView.findViewById(android.R.id.icon));
mCachedViews.put(R.id.icon_frame, itemView.findViewById(R.id.icon_frame));
+ mCachedViews.put(AndroidResources.ANDROID_R_ICON_FRAME,
+ itemView.findViewById(AndroidResources.ANDROID_R_ICON_FRAME));
}
/**
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java b/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
index 2e3c881..f8f6ecf 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
@@ -27,10 +27,18 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ overridePendingTransition(0, 0);
+
mContainer = new TestedFrameLayout(this);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(mContainer);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
+
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
}
diff --git a/v8/renderscript/Android.mk b/v8/renderscript/Android.mk
index 520c8a1..d695f72 100644
--- a/v8/renderscript/Android.mk
+++ b/v8/renderscript/Android.mk
@@ -24,7 +24,7 @@
LOCAL_CFLAGS += -std=c++11
LOCAL_MODULE := android-support-v8-renderscript
-LOCAL_SDK_VERSION := 22
+LOCAL_SDK_VERSION := 23
LOCAL_SRC_FILES := $(call all-java-files-under, java/src)
LOCAL_JAVA_LIBRARIES := android-support-annotations
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java b/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java
index 03244ff..c8a8854 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java
@@ -19,6 +19,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.nio.ByteBuffer;
+
import android.content.res.Resources;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
@@ -61,8 +63,10 @@
Type mType;
Bitmap mBitmap;
int mUsage;
- Allocation mAdaptedAllocation;
int mSize;
+ Allocation mAdaptedAllocation;
+ ByteBuffer mByteBuffer = null;
+ long mByteBufferStride = 0;
boolean mConstrainedLOD;
boolean mConstrainedFace;
@@ -468,6 +472,56 @@
}
/**
+ * @hide
+ * Get the ByteBuffer pointing to the raw data associated with Allocation.
+ * Note: The ByteBuffer will be Read-Only for devices before Lollopop (API 21).
+ */
+ public ByteBuffer getByteBuffer() {
+ int xBytesSize = mType.getX() * mType.getElement().getBytesSize();
+ // When running on devices before L, we need to construct the ByteBuffer
+ // and explicitly copy the data from the allocation to it.
+ if (mRS.getDispatchAPILevel() < 21) {
+ byte[] data = null;
+ if (mType.getZ() > 0) {
+ // TODO: add support for 3D allocations.
+ return null;
+ } else if (mType.getY() > 0) {
+ // 2D Allocation
+ data = new byte[xBytesSize * mType.getY()];
+ copy2DRangeToUnchecked(0, 0, mType.getX(), mType.getY(), data,
+ Element.DataType.SIGNED_8, xBytesSize * mType.getY());
+ } else {
+ // 1D Allocation
+ data = new byte[xBytesSize];
+ copy1DRangeToUnchecked(0, mType.getX(), data);
+ }
+ ByteBuffer bBuffer = ByteBuffer.wrap(data).asReadOnlyBuffer();
+ mByteBufferStride = xBytesSize;
+ return bBuffer;
+ }
+ // Create a new ByteBuffer if it is not initialized or using IO_INPUT.
+ if (mByteBuffer == null || (mUsage & USAGE_IO_INPUT) != 0) {
+ mByteBuffer = mRS.nAllocationGetByteBuffer(getID(mRS), xBytesSize, mType.getY(), mType.getZ());
+ }
+ return mByteBuffer;
+ }
+
+ /**
+ * @hide
+ * Get the Stride of raw data associated with this 2D Allocation.
+ */
+ public long getStride() {
+ if (mByteBufferStride ==0) {
+ if (mRS.getDispatchAPILevel() > 21) {
+ mByteBufferStride = mRS.nAllocationGetStride(getID(mRS));
+ } else {
+ mByteBufferStride = mType.getX() * mType.getElement().getBytesSize();
+ }
+ }
+ return mByteBufferStride;
+ }
+
+ /**
* Receive the latest input into the Allocation. This operation
* is only valid if {@link #USAGE_IO_INPUT} is set on the Allocation.
*
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
index 5dcb50b..d7b7789 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
@@ -20,6 +20,8 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.ArrayList;
+import java.nio.ByteBuffer;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -30,7 +32,6 @@
import android.os.Process;
import android.util.Log;
import android.view.Surface;
-import java.util.ArrayList;
/**
* This class provides access to a RenderScript context, which controls RenderScript
@@ -49,13 +50,18 @@
@SuppressWarnings({"UnusedDeclaration", "deprecation"})
static final boolean LOG_ENABLED = false;
static final int SUPPORT_LIB_API = 23;
+ static final int SUPPORT_LIB_VERSION = 2301;
static private ArrayList<RenderScript> mProcessContextList = new ArrayList<RenderScript>();
private boolean mIsProcessContext = false;
+ private boolean mEnableMultiInput = false;
+ // TODO: Update to set to true at the correct API level when reduce is added.
+ private boolean mEnableReduce = false;
+ private int mDispatchAPILevel = 0;
+
private int mContextFlags = 0;
private int mContextSdkVersion = 0;
-
private Context mApplicationContext;
private String mNativeLibDir;
@@ -97,7 +103,7 @@
static Object lock = new Object();
// Non-threadsafe functions.
- native boolean nLoadSO(boolean useNative, int deviceApi);
+ native boolean nLoadSO(boolean useNative, int deviceApi, String libPath);
native boolean nLoadIOSO();
native long nDeviceCreate();
native void nDeviceDestroy(long dev);
@@ -119,6 +125,9 @@
*/
public static final int CREATE_FLAG_NONE = 0x0000;
+ int getDispatchAPILevel() {
+ return mDispatchAPILevel;
+ }
boolean isUseNative() {
return useNative;
@@ -165,49 +174,42 @@
if (sNative == 1) {
// Workarounds that may disable thunking go here
- ApplicationInfo info = null;
+ ApplicationInfo info;
try {
info = ctx.getPackageManager().getApplicationInfo(ctx.getPackageName(),
PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
// assume no workarounds needed
+ return true;
+ }
+ long minorVersion = 0;
+
+ // load minorID from reflection
+ try {
+ Class<?> javaRS = Class.forName("android.renderscript.RenderScript");
+ Method getMinorID = javaRS.getDeclaredMethod("getMinorID");
+ minorVersion = ((java.lang.Long)getMinorID.invoke(null)).longValue();
+ } catch (Exception e) {
+ // minor version remains 0 on devices with no possible WARs
}
- if (info != null) {
- long minorVersion = 0;
- // load minorID from reflection
- try {
- Class<?> javaRS = Class.forName("android.renderscript.RenderScript");
- if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
- Method getMinorID = javaRS.getDeclaredMethod("getMinorID");
- minorVersion = ((java.lang.Long)getMinorID.invoke(null)).longValue();
- } else {
- //For M+
- Method getMinorVersion = javaRS.getDeclaredMethod("getMinorVersion");
- minorVersion = ((java.lang.Long)getMinorVersion.invoke(null)).longValue();
- }
- } catch (Exception e) {
- // minor version remains 0 on devices with no possible WARs
- }
-
- if (info.metaData != null) {
- // asynchronous teardown: minor version 1+
- if (info.metaData.getBoolean("com.android.support.v8.renderscript.EnableAsyncTeardown") == true) {
- if (minorVersion == 0) {
- sNative = 0;
- }
- }
-
- // blur issues on some drivers with 4.4
- if (info.metaData.getBoolean("com.android.support.v8.renderscript.EnableBlurWorkaround") == true) {
- if (android.os.Build.VERSION.SDK_INT <= 19) {
- //android.util.Log.e("rs", "war on");
- sNative = 0;
- }
+ if (info.metaData != null) {
+ // asynchronous teardown: minor version 1+
+ if (info.metaData.getBoolean("com.android.support.v8.renderscript.EnableAsyncTeardown") == true) {
+ if (minorVersion == 0) {
+ sNative = 0;
}
}
- // end of workarounds
+
+ // blur issues on some drivers with 4.4
+ if (info.metaData.getBoolean("com.android.support.v8.renderscript.EnableBlurWorkaround") == true) {
+ if (android.os.Build.VERSION.SDK_INT <= 19) {
+ //android.util.Log.e("rs", "war on");
+ sNative = 0;
+ }
+ }
}
+ // end of workarounds
}
}
@@ -435,7 +437,16 @@
validate();
rsnAllocationIoReceive(mContext, alloc);
}
-
+ native ByteBuffer rsnAllocationGetByteBuffer(long con, long alloc, int xBytesSize, int dimY, int dimZ);
+ synchronized ByteBuffer nAllocationGetByteBuffer(long alloc, int xBytesSize, int dimY, int dimZ) {
+ validate();
+ return rsnAllocationGetByteBuffer(mContext, alloc, xBytesSize, dimY, dimZ);
+ }
+ native long rsnAllocationGetStride(long con, long alloc);
+ synchronized long nAllocationGetStride(long alloc) {
+ validate();
+ return rsnAllocationGetStride(mContext, alloc);
+ }
native void rsnAllocationGenerateMipmaps(long con, long alloc);
synchronized void nAllocationGenerateMipmaps(long alloc) {
@@ -549,7 +560,7 @@
validate();
rsnAllocationRead1D(mContext, id, off, mip, count, d, sizeBytes, dt.mID, mSize, usePadding);
}
-
+
/*
native void rsnAllocationElementRead(long con,long id, int xoff, int yoff, int zoff,
int mip, int compIdx, byte[] d, int sizeBytes);
@@ -651,6 +662,31 @@
}
}
+ native void rsnScriptForEach(long con, long id, int slot, long[] ains,
+ long aout, byte[] params, int[] limits);
+
+ synchronized void nScriptForEach(long id, int slot, long[] ains, long aout,
+ byte[] params, int[] limits) {
+ if (!mEnableMultiInput) {
+ Log.e(LOG_TAG, "Multi-input kernels are not supported, please change targetSdkVersion to >= 23");
+ throw new RSRuntimeException("Multi-input kernels are not supported before API 23)");
+ }
+ validate();
+ rsnScriptForEach(mContext, id, slot, ains, aout, params, limits);
+ }
+
+ native void rsnScriptReduce(long con, long id, int slot, long ain, long aout,
+ int[] limits);
+ synchronized void nScriptReduce(long id, int slot, long ain, long aout, int[] limits) {
+ if (!mEnableReduce) {
+ // TODO: Update to include the API level when reduce is added.
+ Log.e(LOG_TAG, "Reduce kernels are not supported");
+ throw new RSRuntimeException("Reduce kernels are not supported");
+ }
+ validate();
+ rsnScriptReduce(mContext, id, slot, ain, aout, limits);
+ }
+
native void rsnScriptInvokeV(long con, long id, int slot, byte[] params, boolean mUseInc);
synchronized void nScriptInvokeV(long id, int slot, byte[] params, boolean mUseInc) {
validate();
@@ -749,7 +785,7 @@
Log.e(LOG_TAG, "Error loading RS Compat library for Incremental Intrinsic Support: " + e);
throw new RSRuntimeException("Error loading RS Compat library for Incremental Intrinsic Support: " + e);
}
- if (!nIncLoadSO(SUPPORT_LIB_API)) {
+ if (!nIncLoadSO(SUPPORT_LIB_API, mNativeLibDir + "/libRSSupport.so")) {
throw new RSRuntimeException("Error loading libRSSupport library for Incremental Intrinsic Support");
}
mIncLoaded = true;
@@ -945,7 +981,7 @@
// Additional Entry points For inc libRSSupport
- native boolean nIncLoadSO(int deviceApi);
+ native boolean nIncLoadSO(int deviceApi, String libPath);
native long nIncDeviceCreate();
native void nIncDeviceDestroy(long dev);
// Methods below are wrapped to protect the non-threadsafe
@@ -996,10 +1032,10 @@
validate();
return rsnIncTypeCreate(mIncCon, eid, x, y, z, mips, faces, yuv);
}
- native long rsnIncAllocationCreateTyped(long con, long incCon, long alloc, long type);
- synchronized long nIncAllocationCreateTyped(long alloc, long type) {
+ native long rsnIncAllocationCreateTyped(long con, long incCon, long alloc, long type, int xBytesSize);
+ synchronized long nIncAllocationCreateTyped(long alloc, long type, int xBytesSize) {
validate();
- return rsnIncAllocationCreateTyped(mContext, mIncCon, alloc, type);
+ return rsnIncAllocationCreateTyped(mContext, mIncCon, alloc, type, xBytesSize);
}
long mDev;
@@ -1185,6 +1221,14 @@
}
}
+ void validateObject(BaseObj o) {
+ if (o != null) {
+ if (o.mRS != this) {
+ throw new RSIllegalArgumentException("Attempting to use an object across contexts.");
+ }
+ }
+ }
+
void validate() {
if (mContext == 0) {
throw new RSInvalidStateException("Calling RS with no Context active.");
@@ -1293,7 +1337,10 @@
mContextType = ContextType.NORMAL;
if (ctx != null) {
mApplicationContext = ctx.getApplicationContext();
- mNativeLibDir = mApplicationContext.getApplicationInfo().nativeLibraryDir;
+ // Only set mNativeLibDir for API 9+.
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.FROYO) {
+ mNativeLibDir = mApplicationContext.getApplicationInfo().nativeLibraryDir;
+ }
}
mIncDev = 0;
mIncCon = 0;
@@ -1339,12 +1386,19 @@
sUseGCHooks = false;
}
try {
- System.loadLibrary("rsjni");
+ // For API 9 - 22, always use the absolute path of librsjni.so
+ // http://b/25226912
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M &&
+ rs.mNativeLibDir != null) {
+ System.load(rs.mNativeLibDir + "/librsjni.so");
+ } else {
+ System.loadLibrary("rsjni");
+ }
sInitialized = true;
sPointerSize = rsnSystemGetPointerSize();
} catch (UnsatisfiedLinkError e) {
Log.e(LOG_TAG, "Error loading RS jni library: " + e);
- throw new RSRuntimeException("Error loading RS jni library: " + e);
+ throw new RSRuntimeException("Error loading RS jni library: " + e + " Support lib API: " + SUPPORT_LIB_VERSION);
}
}
}
@@ -1359,24 +1413,39 @@
useIOlib = true;
}
+ // The target API level used to init dispatchTable.
int dispatchAPI = sdkVersion;
if (sdkVersion < android.os.Build.VERSION.SDK_INT) {
// If the device API is higher than target API level, init dispatch table based on device API.
dispatchAPI = android.os.Build.VERSION.SDK_INT;
}
- if (!rs.nLoadSO(useNative, dispatchAPI)) {
+
+ String rssupportPath = null;
+ // For API 9 - 22, always use the absolute path of libRSSupport.so
+ // http://b/25226912
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M &&
+ rs.mNativeLibDir != null) {
+ rssupportPath = rs.mNativeLibDir + "/libRSSupport.so";
+ }
+ if (!rs.nLoadSO(useNative, dispatchAPI, rssupportPath)) {
if (useNative) {
android.util.Log.v(LOG_TAG, "Unable to load libRS.so, falling back to compat mode");
useNative = false;
}
try {
- System.loadLibrary("RSSupport");
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M &&
+ rs.mNativeLibDir != null) {
+ System.load(rssupportPath);
+ } else {
+ System.loadLibrary("RSSupport");
+ }
} catch (UnsatisfiedLinkError e) {
- Log.e(LOG_TAG, "Error loading RS Compat library: " + e);
- throw new RSRuntimeException("Error loading RS Compat library: " + e);
+ Log.e(LOG_TAG, "Error loading RS Compat library: " + e + " Support lib version: " + SUPPORT_LIB_VERSION);
+ throw new RSRuntimeException("Error loading RS Compat library: " + e + " Support lib version: " + SUPPORT_LIB_VERSION);
}
- if (!rs.nLoadSO(false, dispatchAPI)) {
- throw new RSRuntimeException("Error loading libRSSupport library");
+ if (!rs.nLoadSO(false, dispatchAPI, rssupportPath)) {
+ Log.e(LOG_TAG, "Error loading RS Compat library: nLoadSO() failed; Support lib version: " + SUPPORT_LIB_VERSION);
+ throw new RSRuntimeException("Error loading libRSSupport library, Support lib version: " + SUPPORT_LIB_VERSION);
}
}
@@ -1392,11 +1461,24 @@
}
}
+ // For old APIs with dlopen bug, need to load blas lib in Java first.
+ // Only try load to blasV8 when the desired API level includes IntrinsicBLAS.
+ if (dispatchAPI >= 23) {
+ // Enable multi-input kernels only when diapatchAPI is M+.
+ rs.mEnableMultiInput = true;
+ try {
+ System.loadLibrary("blasV8");
+ } catch (UnsatisfiedLinkError e) {
+ Log.v(LOG_TAG, "Unable to load BLAS lib, ONLY BNNM will be supported: " + e);
+ }
+ }
+
rs.mDev = rs.nDeviceCreate();
rs.mContext = rs.nContextCreate(rs.mDev, 0, sdkVersion, ct.mID, rs.mNativeLibDir);
rs.mContextType = ct;
rs.mContextFlags = flags;
rs.mContextSdkVersion = sdkVersion;
+ rs.mDispatchAPILevel = dispatchAPI;
if (rs.mContext == 0) {
throw new RSDriverException("Failed to create RS context.");
}
@@ -1595,9 +1677,21 @@
}
nContextDeinitToClient(mContext);
mMessageThread.mRun = false;
- try {
- mMessageThread.join();
- } catch(InterruptedException e) {
+
+ // Wait for mMessageThread to join. Try in a loop, in case this thread gets interrupted
+ // during the wait. If interrupted, set the "interrupted" status of the current thread.
+ boolean hasJoined = false, interrupted = false;
+ while (!hasJoined) {
+ try {
+ mMessageThread.join();
+ hasJoined = true;
+ } catch (InterruptedException e) {
+ interrupted = true;
+ }
+ }
+ if (interrupted) {
+ Log.v(LOG_TAG, "Interrupted during wait for MessageThread to join");
+ Thread.currentThread().interrupt();
}
nContextDestroy();
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/Script.java b/v8/renderscript/java/src/android/support/v8/renderscript/Script.java
index cacd146..7cb52ae 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/Script.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/Script.java
@@ -44,9 +44,11 @@
long dInType = 0;
long dummyAlloc = 0;
if (ain != null) {
- dInElement = ain.getType().getElement().getDummyElement(mRS);
- dInType = ain.getType().getDummyType(mRS, dInElement);
- dummyAlloc = mRS.nIncAllocationCreateTyped(ain.getID(mRS), dInType);
+ Type inType = ain.getType();
+ dInElement = inType.getElement().getDummyElement(mRS);
+ dInType = inType.getDummyType(mRS, dInElement);
+ int xBytesSize = inType.getX() * inType.getElement().getBytesSize();
+ dummyAlloc = mRS.nIncAllocationCreateTyped(ain.getID(mRS), dInType, xBytesSize);
ain.setIncAllocID(dummyAlloc);
}
@@ -317,6 +319,101 @@
/**
* Only intended for use by generated reflected code.
*
+ * @hide
+ */
+ protected void forEach(int slot, Allocation[] ains, Allocation aout,
+ FieldPacker v) {
+ forEach(slot, ains, aout, v, null);
+ }
+
+ /**
+ * Only intended for use by generated reflected code.
+ *
+ * @hide
+ */
+ protected void forEach(int slot, Allocation[] ains, Allocation aout,
+ FieldPacker v, LaunchOptions sc) {
+ // TODO: Is this necessary if nScriptForEach calls validate as well?
+ mRS.validate();
+ if (ains != null) {
+ for (Allocation ain : ains) {
+ mRS.validateObject(ain);
+ }
+ }
+ mRS.validateObject(aout);
+
+ if (ains == null && aout == null) {
+ throw new RSIllegalArgumentException(
+ "At least one of ain or aout is required to be non-null.");
+ }
+
+ long[] in_ids;
+ if (ains != null) {
+ in_ids = new long[ains.length];
+ for (int index = 0; index < ains.length; ++index) {
+ in_ids[index] = ains[index].getID(mRS);
+ }
+ } else {
+ in_ids = null;
+ }
+
+ long out_id = 0;
+ if (aout != null) {
+ out_id = aout.getID(mRS);
+ }
+
+ byte[] params = null;
+ if (v != null) {
+ params = v.getData();
+ }
+
+ int[] limits = null;
+ if (sc != null) {
+ limits = new int[6];
+
+ limits[0] = sc.xstart;
+ limits[1] = sc.xend;
+ limits[2] = sc.ystart;
+ limits[3] = sc.yend;
+ limits[4] = sc.zstart;
+ limits[5] = sc.zend;
+ }
+
+ mRS.nScriptForEach(getID(mRS), slot, in_ids, out_id, params, limits);
+ }
+
+ /**
+ * Only intended for use by generated reflected code.
+ *
+ * @hide
+ */
+ protected void reduce(int slot, Allocation ain, Allocation aout, LaunchOptions sc) {
+ mRS.validate();
+ mRS.validateObject(ain);
+ mRS.validateObject(aout);
+
+ if (ain == null || aout == null) {
+ throw new RSIllegalArgumentException(
+ "Both ain and aout are required to be non-null.");
+ }
+
+ long in_id = ain.getID(mRS);
+ long out_id = aout.getID(mRS);
+
+ int[] limits = null;
+ if (sc != null) {
+ limits = new int[2];
+
+ limits[0] = sc.xstart;
+ limits[1] = sc.xend;
+ }
+
+ mRS.nScriptReduce(getID(mRS), slot, in_id, out_id, limits);
+ }
+
+ /**
+ * Only intended for use by generated reflected code.
+ *
* @param index
* @param v
*/
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java
index 15e8b55..065712a 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java
@@ -307,10 +307,10 @@
value = ((Long)obj).longValue();
size = 8;
} else if (obj instanceof Float) {
- value = ((Float)obj).longValue();
+ value = Float.floatToRawIntBits(((Float)obj).floatValue());
size = 4;
} else if (obj instanceof Double) {
- value = ((Double)obj).longValue();
+ value = Double.doubleToRawLongBits(((Double)obj).doubleValue());
size = 8;
}
}
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicBlur.java b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicBlur.java
index a5b046b..2d2923e 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicBlur.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicBlur.java
@@ -97,7 +97,7 @@
* type.
*/
public void forEach(Allocation aout) {
- forEach(0, null, aout, null);
+ forEach(0, (Allocation) null, aout, null);
}
/**
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve3x3.java b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve3x3.java
index ea45461..0d398b7 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve3x3.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve3x3.java
@@ -115,7 +115,7 @@
* type.
*/
public void forEach(Allocation aout) {
- forEach(0, null, aout, null);
+ forEach(0, (Allocation) null, aout, null);
}
/**
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve5x5.java b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve5x5.java
index bcd37f1..9dd9cba 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve5x5.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve5x5.java
@@ -117,7 +117,7 @@
* type.
*/
public void forEach(Allocation aout) {
- forEach(0, null, aout, null);
+ forEach(0, (Allocation) null, aout, null);
}
/**
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicYuvToRGB.java b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicYuvToRGB.java
index f3d0df7..6c84020 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicYuvToRGB.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicYuvToRGB.java
@@ -74,7 +74,7 @@
* type.
*/
public void forEach(Allocation aout) {
- forEach(0, null, aout, null);
+ forEach(0, (Allocation) null, aout, null);
}
/**
diff --git a/v8/renderscript/jni/Android.mk b/v8/renderscript/jni/Android.mk
index d2af4a9..72f8720 100644
--- a/v8/renderscript/jni/Android.mk
+++ b/v8/renderscript/jni/Android.mk
@@ -50,6 +50,6 @@
LOCAL_MODULE_TAGS := optional
LOCAL_REQUIRED_MODULES := libRSSupport
-LOCAL_LDFLAGS += -ldl
+LOCAL_LDFLAGS += -ldl -llog
include $(BUILD_SHARED_LIBRARY)
diff --git a/v8/renderscript/jni/android_renderscript_RenderScript.cpp b/v8/renderscript/jni/android_renderscript_RenderScript.cpp
index 9d69a2d..eea462c 100644
--- a/v8/renderscript/jni/android_renderscript_RenderScript.cpp
+++ b/v8/renderscript/jni/android_renderscript_RenderScript.cpp
@@ -30,6 +30,8 @@
//#define LOG_API ALOG
#define LOG_API(...)
+#define LOG_ERR(...) __android_log_print(ANDROID_LOG_ERROR, "RenderScript JNI", __VA_ARGS__);
+#define RS_JNI_VERSION 2301
#define NELEM(m) (sizeof(m) / sizeof((m)[0]))
@@ -269,23 +271,31 @@
// Incremental Support lib
static dispatchTable dispatchTabInc;
-static jboolean nLoadSO(JNIEnv *_env, jobject _this, jboolean useNative, jint targetApi) {
+static jboolean nLoadSO(JNIEnv *_env, jobject _this, jboolean useNative, jint targetApi, jstring libPath) {
void* handle = NULL;
if (useNative) {
handle = dlopen("libRS.so", RTLD_LAZY | RTLD_LOCAL);
} else {
- handle = dlopen("libRSSupport.so", RTLD_LAZY | RTLD_LOCAL);
+ // For API 9+, dlopen the full path of libRSSupport.
+ if (libPath != NULL) {
+ const char * libPathJni = _env->GetStringUTFChars(libPath, JNI_FALSE);
+ handle = dlopen(libPathJni, RTLD_LAZY | RTLD_LOCAL);
+ _env->ReleaseStringUTFChars(libPath, libPathJni);
+ } else {
+ handle = dlopen("libRSSupport.so", RTLD_LAZY | RTLD_LOCAL);
+ }
}
if (handle == NULL) {
- LOG_API("couldn't dlopen %s, %s", filename, dlerror());
+ LOG_ERR("couldn't dlopen %s; librsjni version: %d", dlerror(), RS_JNI_VERSION);
return false;
}
if (loadSymbols(handle, dispatchTab, targetApi) == false) {
- LOG_API("%s init failed!", filename);
+ LOG_ERR("Dispatch table init failed! librsjni version: %d", RS_JNI_VERSION);
+ dlclose(handle);
return false;
}
- LOG_API("Successfully loaded %s", filename);
+ LOG_API("Successfully loaded runtime");
return true;
}
@@ -294,11 +304,11 @@
void* handleIO = NULL;
handleIO = dlopen("libRSSupportIO.so", RTLD_LAZY | RTLD_LOCAL);
if (handleIO == NULL) {
- LOG_API("Couldn't load libRSSupportIO.so");
+ LOG_ERR("Couldn't load libRSSupportIO.so, librsjni version: %d", RS_JNI_VERSION);
return false;
}
if (loadIOSuppSyms(handleIO, ioDispatch) == false) {
- LOG_API("libRSSupportIO init failed!");
+ LOG_ERR("libRSSupportIO init failed! librsjni version: %d", RS_JNI_VERSION);
return false;
}
return true;
@@ -362,31 +372,30 @@
size_t numValues, numDependencies;
RsScriptFieldID* fieldIDs;
- uintptr_t* values;
RsClosure* depClosures;
RsScriptFieldID* depFieldIDs;
if (fieldIDs_length != values_length || values_length != sizes_length) {
- LOG_API("Unmatched field IDs, values, and sizes in closure creation.");
+ LOG_ERR("Unmatched field IDs, values, and sizes in closure creation.");
goto exit;
}
numValues = (size_t)fieldIDs_length;
if (depClosures_length != depFieldIDs_length) {
- LOG_API("Unmatched closures and field IDs for dependencies in closure creation.");
+ LOG_ERR("Unmatched closures and field IDs for dependencies in closure creation.");
goto exit;
}
numDependencies = (size_t)depClosures_length;
if (numDependencies > numValues) {
- LOG_API("Unexpected number of dependencies in closure creation");
+ LOG_ERR("Unexpected number of dependencies in closure creation");
goto exit;
}
if (numValues > RS_CLOSURE_MAX_NUMBER_ARGS_AND_BINDINGS) {
- LOG_API("Too many arguments or globals in closure creation");
+ LOG_ERR("Too many arguments or globals in closure creation");
goto exit;
}
@@ -399,15 +408,6 @@
fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
}
- values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
- if (values == nullptr) {
- goto exit;
- }
-
- for (size_t i = 0; i < numValues; i++) {
- values[i] = (uintptr_t)jValues[i];
- }
-
depClosures = (RsClosure*)alloca(sizeof(RsClosure) * numDependencies);
if (depClosures == nullptr) {
goto exit;
@@ -428,7 +428,7 @@
ret = (jlong)(uintptr_t)dispatchTab.ClosureCreate(
(RsContext)con, (RsScriptKernelID)kernelID, (RsAllocation)returnValue,
- fieldIDs, numValues, values, numValues,
+ fieldIDs, numValues, jValues, numValues,
(int*)jSizes, numValues,
depClosures, numDependencies,
depFieldIDs, numDependencies);
@@ -461,17 +461,16 @@
size_t numValues;
RsScriptFieldID* fieldIDs;
- uintptr_t* values;
if (fieldIDs_length != values_length || values_length != sizes_length) {
- LOG_API("Unmatched field IDs, values, and sizes in closure creation.");
+ LOG_ERR("Unmatched field IDs, values, and sizes in closure creation.");
goto exit;
}
numValues = (size_t) fieldIDs_length;
if (numValues > RS_CLOSURE_MAX_NUMBER_ARGS_AND_BINDINGS) {
- LOG_API("Too many arguments or globals in closure creation");
+ LOG_ERR("Too many arguments or globals in closure creation");
goto exit;
}
@@ -484,18 +483,9 @@
fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
}
- values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
- if (values == nullptr) {
- goto exit;
- }
-
- for (size_t i = 0; i < numValues; i++) {
- values[i] = (uintptr_t)jValues[i];
- }
-
ret = (jlong)(uintptr_t)dispatchTab.InvokeClosureCreate(
(RsContext)con, (RsScriptInvokeID)invokeID, jParams, jParamLength,
- fieldIDs, numValues, values, numValues,
+ fieldIDs, numValues, jValues, numValues,
(int*)jSizes, numValues);
exit:
@@ -511,15 +501,17 @@
static void
nClosureSetArg(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
jint index, jlong value, jint size) {
+ // Size is signed with -1 indicating the values is an Allocation
dispatchTab.ClosureSetArg((RsContext)con, (RsClosure)closureID, (uint32_t)index,
- (uintptr_t)value, (size_t)size);
+ (uintptr_t)value, size);
}
static void
nClosureSetGlobal(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
jlong fieldID, jlong value, jint size) {
+ // Size is signed with -1 indicating the values is an Allocation
dispatchTab.ClosureSetGlobal((RsContext)con, (RsClosure)closureID,
- (RsScriptFieldID)fieldID, (uintptr_t)value, (size_t)size);
+ (RsScriptFieldID)fieldID, (int64_t)value, size);
}
static long
@@ -536,7 +528,7 @@
RsClosure* closures;
if (numClosures > (jsize) RS_SCRIPT_GROUP_MAX_NUMBER_CLOSURES) {
- LOG_API("Too many closures in script group");
+ LOG_ERR("Too many closures in script group");
goto exit;
}
@@ -912,7 +904,7 @@
jint len = 0;
if (data) {
len = _env->GetArrayLength(data);
- jint *ptr = _env->GetIntArrayElements(data, NULL);
+ ptr = _env->GetIntArrayElements(data, NULL);
}
LOG_API("nContextSendMessage, con(%p), id(%i), len(%i)", (RsContext)con, id, len);
dispatchTab.ContextSendMessage((RsContext)con, id, (const uint8_t *)ptr, len * sizeof(int));
@@ -1546,6 +1538,7 @@
NULL, 0, NULL, 0);
}
}
+
static void
nScriptForEachV(JNIEnv *_env, jobject _this, jlong con, jlong incCon,
jlong script, jint slot, jlong ain, jlong aout, jbyteArray params, jboolean mUseInc)
@@ -1627,6 +1620,165 @@
_env->ReleaseByteArrayElements(params, ptr, JNI_ABORT);
}
+static void
+nScriptForEachMulti(JNIEnv *_env, jobject _this, jlong con, jlong script, jint slot,
+ jlongArray ains, jlong aout, jbyteArray params,
+ jintArray limits)
+{
+ LOG_API("nScriptForEach, con(%p), s(%p), slot(%i) ains(%p) aout(%" PRId64 ")", (RsContext)con, (void *)script, slot, ains, aout);
+
+ jint in_len = 0;
+ jlong *in_ptr = nullptr;
+
+ RsAllocation *in_allocs = nullptr;
+
+ if (ains != nullptr) {
+ in_len = _env->GetArrayLength(ains);
+ if (in_len > (jint)RS_KERNEL_MAX_ARGUMENTS) {
+ LOG_ERR("Too many arguments in kernel launch.");
+ // TODO (b/20758983): Report back to Java and throw an exception
+ return;
+ }
+
+ // TODO (b/20760800): Check in_ptr is not null
+ in_ptr = _env->GetLongArrayElements(ains, nullptr);
+ if (sizeof(RsAllocation) == sizeof(jlong)) {
+ in_allocs = (RsAllocation*)in_ptr;
+
+ } else {
+ // Convert from 64-bit jlong types to the native pointer type.
+
+ in_allocs = (RsAllocation*)alloca(in_len * sizeof(RsAllocation));
+ if (in_allocs == nullptr) {
+ LOG_ERR("Failed launching kernel for lack of memory.");
+ _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+ return;
+ }
+
+ for (int index = in_len; --index >= 0;) {
+ in_allocs[index] = (RsAllocation)in_ptr[index];
+ }
+ }
+ }
+
+ jint param_len = 0;
+ jbyte *param_ptr = nullptr;
+
+ if (params != nullptr) {
+ param_len = _env->GetArrayLength(params);
+ param_ptr = _env->GetByteArrayElements(params, nullptr);
+ }
+
+ RsScriptCall sc, *sca = nullptr;
+ uint32_t sc_size = 0;
+
+ jint limit_len = 0;
+ jint *limit_ptr = nullptr;
+
+ if (limits != nullptr) {
+ limit_len = _env->GetArrayLength(limits);
+ limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+
+ if (limit_len != 6) {
+ LOG_ERR("LaunchOptions cannot be recognized.");
+ goto exit;
+ }
+
+ sc.xStart = limit_ptr[0];
+ sc.xEnd = limit_ptr[1];
+ sc.yStart = limit_ptr[2];
+ sc.yEnd = limit_ptr[3];
+ sc.zStart = limit_ptr[4];
+ sc.zEnd = limit_ptr[5];
+ sc.strategy = RS_FOR_EACH_STRATEGY_DONT_CARE;
+ sc.arrayStart = 0;
+ sc.arrayEnd = 0;
+ sc.array2Start = 0;
+ sc.array2End = 0;
+ sc.array3Start = 0;
+ sc.array3End = 0;
+ sc.array4Start = 0;
+ sc.array4End = 0;
+
+ sca = ≻
+ }
+
+ dispatchTabInc.ScriptForEachMulti((RsContext)con, (RsScript)script, slot,
+ in_allocs, in_len, (RsAllocation)aout,
+ param_ptr, param_len, sca, sc_size);
+
+exit:
+
+ if (ains != nullptr) {
+ _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+ }
+
+ if (params != nullptr) {
+ _env->ReleaseByteArrayElements(params, param_ptr, JNI_ABORT);
+ }
+
+ if (limits != nullptr) {
+ _env->ReleaseIntArrayElements(limits, limit_ptr, JNI_ABORT);
+ }
+}
+
+static void
+nScriptReduce(JNIEnv *_env, jobject _this, jlong con, jlong script, jint slot,
+ jlong ain, jlong aout, jintArray limits)
+{
+ LOG_API("nScriptReduce, con(%p), s(%p), slot(%i) ain(%" PRId64 ") aout(%" PRId64 ")", (RsContext)con, (void *)script, slot, ain, aout);
+
+ RsScriptCall sc, *sca = nullptr;
+ uint32_t sc_size = 0;
+
+ jint limit_len = 0;
+ jint *limit_ptr = nullptr;
+
+ bool limitLengthValid = true;
+
+ // If the caller passed limits, reflect them in the RsScriptCall.
+ if (limits != nullptr) {
+ limit_len = _env->GetArrayLength(limits);
+ limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+
+ // We expect to be passed an array [x1, x2] which specifies
+ // the sub-range for a 1-dimensional reduction.
+ if (limit_len == 2) {
+ sc.xStart = limit_ptr[0];
+ sc.xEnd = limit_ptr[1];
+ sc.yStart = 0;
+ sc.yEnd = 0;
+ sc.zStart = 0;
+ sc.zEnd = 0;
+ sc.strategy = RS_FOR_EACH_STRATEGY_DONT_CARE;
+ sc.arrayStart = 0;
+ sc.arrayEnd = 0;
+ sc.array2Start = 0;
+ sc.array2End = 0;
+ sc.array3Start = 0;
+ sc.array3End = 0;
+ sc.array4Start = 0;
+ sc.array4End = 0;
+
+ sca = ≻
+ sc_size = sizeof(sc);
+ } else {
+ LOG_ERR("LaunchOptions cannot be recognized.");
+ limitLengthValid = false;
+ }
+ }
+
+ if (limitLengthValid) {
+ dispatchTab.ScriptReduce((RsContext)con, (RsScript)script, slot,
+ (RsAllocation)ain, (RsAllocation)aout,
+ sca, sc_size);
+ }
+
+ if (limits != nullptr) {
+ _env->ReleaseIntArrayElements(limits, limit_ptr, JNI_ABORT);
+ }
+}
+
// -----------------------------------
static jlong
@@ -1831,19 +1983,34 @@
// ---------------------------------------------------------------------------
// For Incremental Intrinsic Support
-static jboolean nIncLoadSO(JNIEnv *_env, jobject _this, jint deviceApi) {
+static jboolean nIncLoadSO(JNIEnv *_env, jobject _this, jint deviceApi, jstring libPath) {
void* handle = NULL;
- handle = dlopen("libRSSupport.so", RTLD_LAZY | RTLD_LOCAL);
+ // For API 9+, dlopen the full path of libRSSupport.
+ if (libPath != NULL) {
+ const char * libPathJni = _env->GetStringUTFChars(libPath, JNI_FALSE);
+ handle = dlopen(libPathJni, RTLD_LAZY | RTLD_LOCAL);
+ _env->ReleaseStringUTFChars(libPath, libPathJni);
+ } else {
+ handle = dlopen("libRSSupport.so", RTLD_LAZY | RTLD_LOCAL);
+ }
+
if (handle == NULL) {
- LOG_API("couldn't dlopen %s, %s", filename, dlerror());
+ LOG_ERR("couldn't dlopen %s; librsjni version: %d", dlerror(), RS_JNI_VERSION);
return false;
}
if (loadSymbols(handle, dispatchTabInc, deviceApi) == false) {
- LOG_API("%s init failed!", filename);
+ LOG_ERR("Dispatch Table init failed! librsjni version: %d", RS_JNI_VERSION);
+ dlclose(handle);
return false;
}
- LOG_API("Successfully loaded %s", filename);
+ dispatchTabInc.AllocationCreateStrided = (AllocationCreateStridedFnPtr)dlsym(handle, "rsAllocationCreateStrided");
+ if (dispatchTabInc.AllocationCreateStrided == NULL) {
+ LOG_ERR("Couldn't initialize dispatchTabInc.AllocationCreateStrided");
+ dlclose(handle);
+ return false;
+ }
+ LOG_API("Successfully loaded compat runtime");
return true;
}
@@ -1921,7 +2088,7 @@
// -----------------------------------
// Create Allocation from pointer
static jlong
-nIncAllocationCreateTyped(JNIEnv *_env, jobject _this, jlong con, jlong incCon, jlong alloc, jlong type)
+nIncAllocationCreateTyped(JNIEnv *_env, jobject _this, jlong con, jlong incCon, jlong alloc, jlong type, jint xBytesSize)
{
LOG_API("nAllocationCreateTyped, con(%p), type(%p), mip(%i), usage(%i), ptr(%p)",
incCon, (RsElement)type, mips, usage, (void *)pointer);
@@ -1932,21 +2099,77 @@
pIn = dispatchTab.AllocationGetPointer((RsContext)con, (RsAllocation)alloc, 0,
RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X, 0, 0,
&strideIn, sizeof(size_t));
- ainI = dispatchTabInc.AllocationCreateTyped((RsContext)incCon, (RsType)type,
- RS_ALLOCATION_MIPMAP_NONE,
- RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED,
- (uintptr_t)pIn);
+ /*
+ * By definition stride is a roundup of xBytesSize with requiredAlignment, so requiredAlignment must
+ * be strictly larger than the difference of (stride - xBytesSize).
+ *
+ * We can prove that as long as requiredAlignment satisfies the following two conditions, the
+ * memory layout will be identical :
+ * 1. Smaller or equal than stride;
+ * 2. Larger than minRequiredAlignment.
+ *
+ * In this case we can simply choose the first power of 2 that satisfies both conditions.
+ */
+ size_t requiredAlignment = 16;
+ size_t minRequiredAlignment = strideIn - xBytesSize;
+ while (requiredAlignment <= minRequiredAlignment) {
+ requiredAlignment <<= 1;
+ }
+ ainI = dispatchTabInc.AllocationCreateStrided((RsContext)incCon, (RsType)type,
+ RS_ALLOCATION_MIPMAP_NONE,
+ RS_ALLOCATION_USAGE_INCREMENTAL_SUPPORT | RS_ALLOCATION_USAGE_SHARED,
+ (uintptr_t)pIn, requiredAlignment);
}
return (jlong)(uintptr_t) ainI;
}
+static jobject
+nAllocationGetByteBuffer(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jint xBytesSize, jint dimY, jint dimZ)
+{
+ LOG_API("nAllocationGetByteBuffer, con(%p), alloc(%p)", (RsContext)con, (RsAllocation)alloc);
+ size_t strideIn = xBytesSize;
+ void* ptr = NULL;
+ if (alloc != 0 && dispatchTab.AllocationGetPointer != nullptr) {
+ ptr = dispatchTab.AllocationGetPointer((RsContext)con, (RsAllocation)alloc, 0,
+ RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X, dimZ, 0,
+ &strideIn, sizeof(size_t));
+ }
+ if (ptr != NULL) {
+ size_t bufferSize = strideIn;
+ if (dimY > 0) {
+ bufferSize *= dimY;
+ }
+ if (dimZ > 0) {
+ bufferSize *= dimZ;
+ }
+ jobject byteBuffer = _env->NewDirectByteBuffer(ptr, (jlong) bufferSize);
+ return byteBuffer;
+ } else {
+ return NULL;
+ }
+}
+
+static jlong
+nAllocationGetStride(JNIEnv *_env, jobject _this, jlong con, jlong alloc)
+{
+ LOG_API("nAllocationGetStride, con(%p), alloc(%p)", (RsContext)con, (RsAllocation)alloc);
+ size_t strideIn;
+ void* ptr = NULL;
+ if (alloc != 0 && dispatchTab.AllocationGetPointer != nullptr) {
+ ptr = dispatchTab.AllocationGetPointer((RsContext)con, (RsAllocation)alloc, 0,
+ RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X, 0, 0,
+ &strideIn, sizeof(size_t));
+ }
+ return (jlong)strideIn;
+}
+
// ---------------------------------------------------------------------------
static const char *classPathName = "android/support/v8/renderscript/RenderScript";
static JNINativeMethod methods[] = {
-{"nLoadSO", "(ZI)Z", (bool*)nLoadSO },
+{"nLoadSO", "(ZILjava/lang/String;)Z", (bool*)nLoadSO },
{"nLoadIOSO", "()Z", (bool*)nLoadIOSO },
{"nDeviceCreate", "()J", (void*)nDeviceCreate },
{"nDeviceDestroy", "(J)V", (void*)nDeviceDestroy },
@@ -2010,8 +2233,10 @@
{"rsnScriptInvokeV", "(JJI[BZ)V", (void*)nScriptInvokeV },
{"rsnScriptForEach", "(JJJIJJZ)V", (void*)nScriptForEach },
{"rsnScriptForEach", "(JJJIJJ[BZ)V", (void*)nScriptForEachV },
+{"rsnScriptForEach", "(JJI[JJ[B[I)V", (void*)nScriptForEachMulti },
{"rsnScriptForEachClipped", "(JJJIJJIIIIIIZ)V", (void*)nScriptForEachClipped },
{"rsnScriptForEachClipped", "(JJJIJJ[BIIIIIIZ)V", (void*)nScriptForEachClippedV },
+{"rsnScriptReduce", "(JJIJJ[I)V", (void*)nScriptReduce },
{"rsnScriptSetVarI", "(JJIIZ)V", (void*)nScriptSetVarI },
{"rsnScriptSetVarJ", "(JJIJZ)V", (void*)nScriptSetVarJ },
{"rsnScriptSetVarF", "(JJIFZ)V", (void*)nScriptSetVarF },
@@ -2044,7 +2269,7 @@
{"rsnSystemGetPointerSize", "()I", (void*)nSystemGetPointerSize },
// Entry points for Inc libRSSupport
-{"nIncLoadSO", "(I)Z", (bool*)nIncLoadSO },
+{"nIncLoadSO", "(ILjava/lang/String;)Z", (bool*)nIncLoadSO },
{"nIncDeviceCreate", "()J", (void*)nIncDeviceCreate },
{"nIncDeviceDestroy", "(J)V", (void*)nIncDeviceDestroy },
{"rsnIncContextCreate", "(JIII)J", (void*)nIncContextCreate },
@@ -2053,7 +2278,9 @@
{"rsnIncObjDestroy", "(JJ)V", (void*)nIncObjDestroy },
{"rsnIncElementCreate", "(JJIZI)J", (void*)nIncElementCreate },
{"rsnIncTypeCreate", "(JJIIIZZI)J", (void*)nIncTypeCreate },
-{"rsnIncAllocationCreateTyped", "(JJJJ)J", (void*)nIncAllocationCreateTyped },
+{"rsnIncAllocationCreateTyped", "(JJJJI)J", (void*)nIncAllocationCreateTyped },
+{"rsnAllocationGetByteBuffer", "(JJIII)Ljava/nio/ByteBuffer;", (void*)nAllocationGetByteBuffer },
+{"rsnAllocationGetStride", "(JJ)J", (void*)nAllocationGetStride },
};
// ---------------------------------------------------------------------------
diff --git a/v8/renderscript/rs_support/Android.mk b/v8/renderscript/rs_support/Android.mk
index 2b8f0fe..27966de 100644
--- a/v8/renderscript/rs_support/Android.mk
+++ b/v8/renderscript/rs_support/Android.mk
@@ -29,7 +29,7 @@
rsg_generator.c
LOCAL_CXX_STL := none
-LOCAL_ADDRESS_SANITIZER := false
+LOCAL_SANITIZE := never
include $(BUILD_HOST_EXECUTABLE)
@@ -53,8 +53,8 @@
)
$(GEN) : PRIVATE_PATH := $(LOCAL_PATH)
-$(GEN) : PRIVATE_CUSTOM_TOOL = $(RSG_GENERATOR_SUPPORT) $< $@ <$(PRIVATE_PATH)/rs.spec
-$(GEN) : $(RSG_GENERATOR_SUPPORT) $(LOCAL_PATH)/rs.spec
+$(GEN) : PRIVATE_CUSTOM_TOOL = cat $(PRIVATE_PATH)/rs.spec $(PRIVATE_PATH)/rs_compat.spec | $(RSG_GENERATOR_SUPPORT) $< $@
+$(GEN) : $(RSG_GENERATOR_SUPPORT) $(LOCAL_PATH)/rs.spec $(LOCAL_PATH)/rs_compat.spec
$(GEN): $(generated_sources_dir)/%.h : $(LOCAL_PATH)/%.h.rsg
$(transform-generated-source)
@@ -70,8 +70,8 @@
)
$(GEN) : PRIVATE_PATH := $(LOCAL_PATH)
-$(GEN) : PRIVATE_CUSTOM_TOOL = $(RSG_GENERATOR_SUPPORT) $< $@ <$(PRIVATE_PATH)/rs.spec
-$(GEN) : $(RSG_GENERATOR_SUPPORT) $(LOCAL_PATH)/rs.spec
+$(GEN) : PRIVATE_CUSTOM_TOOL = cat $(PRIVATE_PATH)/rs.spec $(PRIVATE_PATH)/rs_compat.spec | $(RSG_GENERATOR_SUPPORT) $< $@
+$(GEN) : $(RSG_GENERATOR_SUPPORT) $(LOCAL_PATH)/rs.spec $(LOCAL_PATH)/rs_compat.spec
$(GEN): $(generated_sources_dir)/%.cpp : $(LOCAL_PATH)/%.cpp.rsg
$(transform-generated-source)
@@ -132,13 +132,12 @@
cpu_ref/rsCpuIntrinsicHistogram.cpp \
cpu_ref/rsCpuIntrinsicLUT.cpp \
cpu_ref/rsCpuIntrinsicResize.cpp \
- cpu_ref/rsCpuIntrinsicYuvToRGB.cpp \
- cpu_ref/rsCpuRuntimeMathFuncs.cpp
+ cpu_ref/rsCpuIntrinsicYuvToRGB.cpp
ifeq ($(ARCH_ARM_HAVE_ARMV7A),true)
LOCAL_CFLAGS_arm := -DARCH_ARM_HAVE_VFP -DARCH_ARM_USE_INTRINSICS
LOCAL_ASFLAGS_arm := -mfpu=neon
-# frameworks/rs/cpu_ref/rsCpuIntrinsics_neon_3DLUT.S does not compile.
+# Clang does not support nested .irp in *_Blur.S
LOCAL_CLANG_ASFLAGS_arm += -no-integrated-as
LOCAL_SRC_FILES_arm := \
cpu_ref/rsCpuIntrinsics_neon_3DLUT.S \
@@ -151,13 +150,15 @@
endif
LOCAL_REQUIRED_MODULES := libblasV8
+LOCAL_STATIC_LIBRARIES := libbnnmlowpV8
LOCAL_LDFLAGS += -llog -ldl
LOCAL_NDK_STL_VARIANT := stlport_static
LOCAL_C_INCLUDES += frameworks/compile/libbcc/include
LOCAL_C_INCLUDES += external/cblas/include
+LOCAL_C_INCLUDES += external/gemmlowp/eight_bit_int_gemm
-LOCAL_CFLAGS += $(rs_base_CFLAGS)
+LOCAL_CFLAGS += $(rs_base_CFLAGS) -DGEMMLOWP_USE_STLPORT
LOCAL_MODULE:= libRSSupport
LOCAL_MODULE_TAGS := optional