Merge "Import translations. DO NOT MERGE"
diff --git a/Android.mk b/Android.mk
index 0e8a344..42adbb0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -868,9 +868,11 @@
framework_docs_SDK_REL_ID:=1
framework_docs_LOCAL_DROIDDOC_OPTIONS += \
+ -hdf sdk.codename N \
+ -hdf sdk.preview.version 2 \
-hdf sdk.version $(framework_docs_SDK_VERSION) \
-hdf sdk.rel.id $(framework_docs_SDK_REL_ID) \
- -hdf sdk.preview 0
+ -hdf sdk.preview 1
# ==== the api stubs and current.xml ===========================
include $(CLEAR_VARS)
@@ -1025,23 +1027,24 @@
-offlinemode \
-title "Android SDK" \
-proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \
- -todo $(OUT_DOCS)/$(LOCAL_MODULE)-docs-todo.html \
-sdkvalues $(OUT_DOCS) \
- -hdf android.whichdoc offline
+ -hdf android.whichdoc offline \
+ -referenceonly
-LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
+LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk-refonly
include $(BUILD_DROIDDOC)
static_doc_index_redirect := $(out_dir)/index.html
$(static_doc_index_redirect): \
- $(LOCAL_PATH)/docs/docs-documentation-redirect.html | $(ACP)
+ $(LOCAL_PATH)/docs/docs-preview-index.html | $(ACP)
$(hide) mkdir -p $(dir $@)
$(hide) $(ACP) $< $@
$(full_target): $(static_doc_index_redirect)
$(full_target): $(framework_built)
+
# ==== docs for the web (on the androiddevdocs app engine server) =======================
include $(CLEAR_VARS)
diff --git a/api/current.txt b/api/current.txt
index 2e495ee..13a6dba 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21034,6 +21034,7 @@
field public static final java.lang.String KEY_DURATION = "durationUs";
field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
+ field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info";
field public static final java.lang.String KEY_HEIGHT = "height";
field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
field public static final java.lang.String KEY_IS_ADTS = "is-adts";
diff --git a/api/system-current.txt b/api/system-current.txt
index b412283..5156606 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -22526,6 +22526,7 @@
field public static final java.lang.String KEY_DURATION = "durationUs";
field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
+ field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info";
field public static final java.lang.String KEY_HEIGHT = "height";
field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
field public static final java.lang.String KEY_IS_ADTS = "is-adts";
diff --git a/api/test-current.txt b/api/test-current.txt
index 4b0e801..df1c30a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -21100,6 +21100,7 @@
field public static final java.lang.String KEY_DURATION = "durationUs";
field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
+ field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info";
field public static final java.lang.String KEY_HEIGHT = "height";
field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
field public static final java.lang.String KEY_IS_ADTS = "is-adts";
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index c469ae4..7bf073b 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -67,30 +67,6 @@
}
}
-static status_t vinfoToPixelFormat(const fb_var_screeninfo& vinfo,
- uint32_t* bytespp, uint32_t* f)
-{
-
- switch (vinfo.bits_per_pixel) {
- case 16:
- *f = PIXEL_FORMAT_RGB_565;
- *bytespp = 2;
- break;
- case 24:
- *f = PIXEL_FORMAT_RGB_888;
- *bytespp = 3;
- break;
- case 32:
- // TODO: do better decoding of vinfo here
- *f = PIXEL_FORMAT_RGBX_8888;
- *bytespp = 4;
- break;
- default:
- return BAD_VALUE;
- }
- return NO_ERROR;
-}
-
static status_t notifyMediaScanner(const char* fileName) {
String8 cmd("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://");
String8 fileUrl("\"");
@@ -147,7 +123,7 @@
png = true;
}
}
-
+
if (fd == -1) {
usage(pname);
return 1;
@@ -195,28 +171,6 @@
s = screenshot.getStride();
f = screenshot.getFormat();
size = screenshot.getSize();
- } else {
- const char* fbpath = "/dev/graphics/fb0";
- int fb = open(fbpath, O_RDONLY);
- if (fb >= 0) {
- struct fb_var_screeninfo vinfo;
- if (ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) == 0) {
- uint32_t bytespp;
- if (vinfoToPixelFormat(vinfo, &bytespp, &f) == NO_ERROR) {
- size_t offset = (vinfo.xoffset + vinfo.yoffset*vinfo.xres) * bytespp;
- w = vinfo.xres;
- h = vinfo.yres;
- s = vinfo.xres;
- size = w*h*bytespp;
- mapsize = offset + size;
- mapbase = mmap(0, mapsize, PROT_READ, MAP_PRIVATE, fb, 0);
- if (mapbase != MAP_FAILED) {
- base = (void const *)((char const *)mapbase + offset);
- }
- }
- }
- close(fb);
- }
}
if (base != NULL) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d8bc119..060ac5e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1793,24 +1793,9 @@
* Resources if one has already been created.
*/
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
- String[] libDirs, int displayId, Configuration overrideConfiguration,
- LoadedApk pkgInfo) {
- return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
- displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(),
- pkgInfo.getClassLoader());
- }
-
- /**
- * Creates a new top level resources for the given package. Will always create a new
- * Resources, regardless if one has already been created.
- */
- Resources getNewTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
- String[] libDirs, int displayId, Configuration overrideConfiguration,
- LoadedApk pkgInfo) {
- mResourcesManager.removeTopLevelResources(
- resDir, displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo());
- return getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
- displayId, overrideConfiguration, pkgInfo);
+ String[] libDirs, int displayId, LoadedApk pkgInfo) {
+ return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
+ displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader());
}
final Handler getHandler() {
@@ -2624,7 +2609,7 @@
}
ContextImpl appContext = ContextImpl.createActivityContext(
- this, r.packageInfo, displayId, r.overrideConfig);
+ this, r.packageInfo, r.token, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
@@ -3481,14 +3466,9 @@
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
- r.tmpConfig.setTo(r.newConfig);
- if (r.overrideConfig != null) {
- r.tmpConfig.updateFrom(r.overrideConfig);
- }
+ performConfigurationChangedForActivity(r, r.newConfig, REPORT_TO_ACTIVITY);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
- + r.activityInfo.name + " with newConfig " + r.tmpConfig);
- performConfigurationChanged(r.activity, r.tmpConfig, REPORT_TO_ACTIVITY);
- freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
+ + r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig);
r.newConfig = null;
}
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
@@ -3833,14 +3813,10 @@
}
}
if (r.newConfig != null) {
- r.tmpConfig.setTo(r.newConfig);
- if (r.overrideConfig != null) {
- r.tmpConfig.updateFrom(r.overrideConfig);
- }
+ performConfigurationChangedForActivity(r, r.newConfig, REPORT_TO_ACTIVITY);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis "
- + r.activityInfo.name + " with new config " + r.tmpConfig);
- performConfigurationChanged(r.activity, r.tmpConfig, REPORT_TO_ACTIVITY);
- freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
+ + r.activityInfo.name + " with new config "
+ + r.activity.mCurrentConfig);
r.newConfig = null;
}
} else {
@@ -4545,8 +4521,44 @@
return callbacks;
}
- private static void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config,
- boolean reportToActivity) {
+ /**
+ * Updates the configuration for an Activity. The ActivityClientRecord's
+ * {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
+ * that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
+ * the updated Configuration.
+ * @param r ActivityClientRecord representing the Activity.
+ * @param newBaseConfig The new configuration to use. This may be augmented with
+ * {@link ActivityClientRecord#overrideConfig}.
+ * @param reportToActivity true if the change should be reported to the Activity's callback.
+ */
+ private void performConfigurationChangedForActivity(ActivityClientRecord r,
+ Configuration newBaseConfig,
+ boolean reportToActivity) {
+ r.tmpConfig.setTo(newBaseConfig);
+ if (r.overrideConfig != null) {
+ r.tmpConfig.updateFrom(r.overrideConfig);
+ }
+ performConfigurationChanged(r.activity, r.token, r.tmpConfig, r.overrideConfig,
+ reportToActivity);
+ freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
+ }
+
+ /**
+ * Decides whether to update an Activity's configuration and whether to tell the
+ * Activity/Component about it.
+ * @param cb The component callback to notify of configuration change.
+ * @param activityToken The Activity binder token for which this configuration change happened.
+ * If the change is global, this is null.
+ * @param newConfig The new configuration.
+ * @param overrideConfig The override config that differentiates the Activity's configuration
+ * from the base global configuration.
+ * @param reportToActivity Notify the Activity of the change.
+ */
+ private void performConfigurationChanged(ComponentCallbacks2 cb,
+ IBinder activityToken,
+ Configuration newConfig,
+ Configuration overrideConfig,
+ boolean reportToActivity) {
// Only for Activity objects, check that they actually call up to their
// superclass implementation. ComponentCallbacks2 is an interface, so
// we check the runtime type and act accordingly.
@@ -4563,7 +4575,7 @@
// If the new config is the same as the config this Activity
// is already running with then don't bother calling
// onConfigurationChanged
- int diff = activity.mCurrentConfig.diff(config);
+ int diff = activity.mCurrentConfig.diff(newConfig);
if (diff != 0) {
// If this activity doesn't handle any of the config changes then don't bother
// calling onConfigurationChanged as we're going to destroy it.
@@ -4578,21 +4590,31 @@
}
}
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb
- + ": shouldChangeConfig=" + shouldChangeConfig);
+ if (DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Config callback " + cb + ": shouldChangeConfig=" + shouldChangeConfig);
+ }
+
if (shouldChangeConfig) {
+ if (activityToken != null) {
+ // We only update an Activity's configuration if this is not a global
+ // configuration change. This must also be done before the callback,
+ // or else we violate the contract that the new resources are available
+ // in {@link ComponentCallbacks2#onConfigurationChanged(Configuration)}.
+ mResourcesManager.updateResourcesForActivity(activityToken, overrideConfig);
+ }
+
if (reportToActivity) {
- cb.onConfigurationChanged(config);
+ cb.onConfigurationChanged(newConfig);
}
if (activity != null) {
if (reportToActivity && !activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + activity.getLocalClassName() +
- " did not call through to super.onConfigurationChanged()");
+ " did not call through to super.onConfigurationChanged()");
}
activity.mConfigChangeFlags = 0;
- activity.mCurrentConfig = new Configuration(config);
+ activity.mCurrentConfig = new Configuration(newConfig);
}
}
}
@@ -4609,7 +4631,8 @@
mCompatConfiguration = new Configuration();
}
mCompatConfiguration.setTo(mConfiguration);
- if (mResourcesManager.applyCompatConfiguration(displayDensity, mCompatConfiguration)) {
+ if (mResourcesManager.applyCompatConfigurationLocked(displayDensity,
+ mCompatConfiguration)) {
config = mCompatConfiguration;
}
return config;
@@ -4661,7 +4684,8 @@
if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i<N; i++) {
- performConfigurationChanged(callbacks.get(i), config, REPORT_TO_ACTIVITY);
+ performConfigurationChanged(callbacks.get(i), null, config, null,
+ REPORT_TO_ACTIVITY);
}
}
}
@@ -4687,15 +4711,8 @@
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
+ r.activityInfo.name + ", with callback=" + reportToActivity);
- r.tmpConfig.setTo(mCompatConfiguration);
- if (data.overrideConfig != null) {
- r.overrideConfig = data.overrideConfig;
- r.tmpConfig.updateFrom(data.overrideConfig);
- }
- performConfigurationChanged(r.activity, r.tmpConfig, reportToActivity);
-
- freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(mCompatConfiguration));
-
+ r.overrideConfig = data.overrideConfig;
+ performConfigurationChangedForActivity(r, mCompatConfiguration, reportToActivity);
mSomeActivitiesChanged = true;
}
@@ -5032,21 +5049,25 @@
*/
TimeZone.setDefault(null);
- /*
- * Initialize the default locales in this process for the reasons we set the time zone.
- *
- * We do this through ResourcesManager, since we need to do locale negotiation.
- */
- mResourcesManager.setDefaultLocalesLocked(data.config.getLocales());
+ synchronized (mResourcesManager) {
+ /*
+ * Initialize the default locales in this process for the reasons we set the time zone.
+ *
+ * We do this through ResourcesManager, since we need to do locale negotiation.
+ */
+ mResourcesManager.setDefaultLocalesLocked(data.config.getLocales());
- /*
- * Update the system configuration since its preloaded and might not
- * reflect configuration changes. The configuration object passed
- * in AppBindData can be safely assumed to be up to date
- */
- mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
- mCurDefaultDisplayDpi = data.config.densityDpi;
- applyCompatConfiguration(mCurDefaultDisplayDpi);
+ /*
+ * Update the system configuration since its preloaded and might not
+ * reflect configuration changes. The configuration object passed
+ * in AppBindData can be safely assumed to be up to date
+ */
+ mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
+ mCurDefaultDisplayDpi = data.config.densityDpi;
+
+ // This calls mResourcesManager so keep it within the synchronized block.
+ applyCompatConfiguration(mCurDefaultDisplayDpi);
+ }
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 455f869..b08142a 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -985,11 +985,16 @@
/**
* Gets information about the next alarm clock currently scheduled.
*
- * The alarm clocks considered are those scheduled by {@link #setAlarmClock}
- * from any package of the calling user.
+ * The alarm clocks considered are those scheduled by any application
+ * using the {@link #setAlarmClock} method.
+ *
+ * @return An {@link AlarmClockInfo} object describing the next upcoming alarm
+ * clock event that will occur. If there are no alarm clock events currently
+ * scheduled, this method will return {@code null}.
*
* @see #setAlarmClock
* @see AlarmClockInfo
+ * @see #ACTION_NEXT_ALARM_CLOCK_CHANGED
*/
public AlarmClockInfo getNextAlarmClock() {
return getNextAlarmClock(UserHandle.myUserId());
@@ -998,11 +1003,16 @@
/**
* Gets information about the next alarm clock currently scheduled.
*
- * The alarm clocks considered are those scheduled by {@link #setAlarmClock}
- * from any package of the given {@parm userId}.
+ * The alarm clocks considered are those scheduled by any application
+ * using the {@link #setAlarmClock} method within the given user.
+ *
+ * @return An {@link AlarmClockInfo} object describing the next upcoming alarm
+ * clock event that will occur within the given user. If there are no alarm clock
+ * events currently scheduled in that user, this method will return {@code null}.
*
* @see #setAlarmClock
* @see AlarmClockInfo
+ * @see #ACTION_NEXT_ALARM_CLOCK_CHANGED
*
* @hide
*/
@@ -1015,7 +1025,7 @@
}
/**
- * An immutable description of an alarm clock.
+ * An immutable description of a scheduled "alarm clock" event.
*
* @see AlarmManager#setAlarmClock
* @see AlarmManager#getNextAlarmClock
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0b44925..ca05091 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1182,7 +1182,7 @@
sameUid ? app.sourceDir : app.publicSourceDir,
sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
- null, mContext.mPackageInfo);
+ mContext.mPackageInfo);
if (r != null) {
return r;
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index e76f991..32ace14 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1994,9 +1994,10 @@
}
static ContextImpl createActivityContext(ActivityThread mainThread,
- LoadedApk packageInfo, int displayId, Configuration overrideConfiguration) {
+ LoadedApk packageInfo, IBinder activityToken, int displayId,
+ Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
- return new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+ return new ContextImpl(null, mainThread, packageInfo, activityToken, null, 0,
null, overrideConfiguration, displayId);
}
@@ -2054,10 +2055,16 @@
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
- resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
- packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
- packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
- overrideConfiguration, compatInfo, packageInfo.getClassLoader());
+ resources = mResourcesManager.getResources(
+ activityToken,
+ packageInfo.getResDir(),
+ packageInfo.getSplitResDirs(),
+ packageInfo.getOverlayDirs(),
+ packageInfo.getApplicationInfo().sharedLibraryFiles,
+ displayId,
+ overrideConfiguration,
+ compatInfo,
+ packageInfo.getClassLoader());
}
}
mResources = resources;
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index cd17078..b8fc323 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -283,9 +283,9 @@
synchronized (this) {
mClassLoader = createOrUpdateClassLoaderLocked(addedPaths);
if (mResources != null) {
- mResources = mActivityThread.getNewTopLevelResources(mResDir, mSplitResDirs,
+ mResources = mActivityThread.getTopLevelResources(mResDir, mSplitResDirs,
mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
- null /*overrideConfiguration*/, this);
+ this);
}
}
}
@@ -668,7 +668,7 @@
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
- mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
+ mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
}
return mResources;
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index bc44553d..54d813d 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -18,6 +18,8 @@
import static android.app.ActivityThread.DEBUG_CONFIGURATION;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
@@ -26,6 +28,7 @@
import android.content.res.ResourcesImpl;
import android.content.res.ResourcesKey;
import android.hardware.display.DisplayManagerGlobal;
+import android.os.IBinder;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.LocaleList;
@@ -34,11 +37,16 @@
import android.util.Slog;
import android.view.Display;
import android.view.DisplayAdjustments;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
-import java.util.List;
+import java.util.Objects;
+import java.util.WeakHashMap;
+import java.util.function.Predicate;
/** @hide */
public class ResourcesManager {
@@ -46,18 +54,56 @@
private static final boolean DEBUG = false;
private static ResourcesManager sResourcesManager;
- private final ArrayMap<ResourcesKey, WeakReference<Resources>> mActiveResources =
- new ArrayMap<>();
- private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays =
- new ArrayMap<>();
+
+ /**
+ * Predicate that returns true if a WeakReference is gc'ed.
+ */
+ private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
+ new Predicate<WeakReference<Resources>>() {
+ @Override
+ public boolean test(WeakReference<Resources> weakRef) {
+ return weakRef == null || weakRef.get() == null;
+ }
+ };
private String[] mSystemLocales = {};
- private final HashSet<String> mNonSystemLocales = new HashSet<String>();
+ private final HashSet<String> mNonSystemLocales = new HashSet<>();
private boolean mHasNonSystemLocales = false;
- CompatibilityInfo mResCompatibilityInfo;
+ /**
+ * The global compatibility settings.
+ */
+ private CompatibilityInfo mResCompatibilityInfo;
- Configuration mResConfiguration;
+ /**
+ * The global configuration upon which all Resources are based. Multi-window Resources
+ * apply their overrides to this configuration.
+ */
+ private final Configuration mResConfiguration = new Configuration();
+
+ /**
+ * A mapping of ResourceImpls and their configurations. These are heavy weight objects
+ * which should be reused as much as possible.
+ */
+ private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
+ new ArrayMap<>();
+
+ /**
+ * A list of Resource references that can be reused.
+ */
+ private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
+
+ /**
+ * Each Activity may have only one Resources object.
+ */
+ private final WeakHashMap<IBinder, WeakReference<Resources>> mActivityResourceReferences =
+ new WeakHashMap<>();
+
+ /**
+ * A cache of DisplayId to DisplayAdjustments.
+ */
+ private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays =
+ new ArrayMap<>();
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
@@ -76,7 +122,11 @@
return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
}
- DisplayMetrics getDisplayMetricsLocked(int displayId) {
+ /**
+ * Protected so that tests can override and returns something a fixed value.
+ */
+ @VisibleForTesting
+ protected DisplayMetrics getDisplayMetricsLocked(int displayId) {
DisplayMetrics dm = new DisplayMetrics();
final Display display =
getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
@@ -88,12 +138,12 @@
return dm;
}
- final void applyNonDefaultDisplayMetricsToConfigurationLocked(
- DisplayMetrics dm, Configuration config) {
+ private static void applyNonDefaultDisplayMetricsToConfiguration(
+ @NonNull DisplayMetrics dm, @NonNull Configuration config) {
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
config.densityDpi = dm.densityDpi;
- config.screenWidthDp = (int)(dm.widthPixels / dm.density);
- config.screenHeightDp = (int)(dm.heightPixels / dm.density);
+ config.screenWidthDp = (int) (dm.widthPixels / dm.density);
+ config.screenHeightDp = (int) (dm.heightPixels / dm.density);
int sl = Configuration.resetScreenLayout(config.screenLayout);
if (dm.widthPixels > dm.heightPixels) {
config.orientation = Configuration.ORIENTATION_LANDSCAPE;
@@ -110,8 +160,8 @@
config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
}
- public boolean applyCompatConfiguration(int displayDensity,
- Configuration compatConfiguration) {
+ public boolean applyCompatConfigurationLocked(int displayDensity,
+ @NonNull Configuration compatConfiguration) {
if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
return true;
@@ -126,7 +176,8 @@
* @param displayId display Id.
* @param displayAdjustments display adjustments.
*/
- public Display getAdjustedDisplay(final int displayId, DisplayAdjustments displayAdjustments) {
+ public Display getAdjustedDisplay(final int displayId,
+ @Nullable DisplayAdjustments displayAdjustments) {
final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null)
? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
final Pair<Integer, DisplayAdjustments> key =
@@ -153,76 +204,42 @@
}
/**
- * Creates the top level Resources for applications with the given compatibility info.
+ * Creates an AssetManager from the paths within the ResourcesKey.
*
- * @param resDir the resource directory.
- * @param splitResDirs split resource directories.
- * @param overlayDirs the resource overlay directories.
- * @param libDirs the shared library resource dirs this app references.
- * @param displayId display Id.
- * @param overrideConfiguration override configurations.
- * @param compatInfo the compatibility info. Must not be null.
- * @param classLoader the class loader for the resource package
- */
- Resources getTopLevelResources(String resDir, String[] splitResDirs,
- String[] overlayDirs, String[] libDirs, int displayId,
- Configuration overrideConfiguration, CompatibilityInfo compatInfo,
- ClassLoader classLoader) {
- final float scale = compatInfo.applicationScale;
- Configuration overrideConfigCopy = (overrideConfiguration != null)
- ? new Configuration(overrideConfiguration) : null;
- ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
- Resources r;
- final boolean findSystemLocales;
- final boolean hasNonSystemLocales;
- synchronized (this) {
- // Resources is app scale dependent.
- if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
-
- WeakReference<Resources> wr = mActiveResources.get(key);
- r = wr != null ? wr.get() : null;
- //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
- if (r != null && r.getAssets().isUpToDate()) {
- if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
- + ": appScale=" + r.getCompatibilityInfo().applicationScale
- + " key=" + key + " overrideConfig=" + overrideConfiguration);
- return r;
- }
- findSystemLocales = (mSystemLocales.length == 0);
- hasNonSystemLocales = mHasNonSystemLocales;
- }
-
- //if (r != null) {
- // Log.w(TAG, "Throwing away out-of-date resources!!!! "
- // + r + " " + resDir);
- //}
-
+ * This can be overridden in tests so as to avoid creating a real AssetManager with
+ * real APK paths.
+ * @param key The key containing the resource paths to add to the AssetManager.
+ * @return a new AssetManager.
+ */
+ @VisibleForTesting
+ protected AssetManager createAssetManager(@NonNull final ResourcesKey key) {
AssetManager assets = new AssetManager();
+
// resDir can be null if the 'android' package is creating a new Resources object.
// This is fine, since each AssetManager automatically loads the 'android' package
// already.
- if (resDir != null) {
- if (assets.addAssetPath(resDir) == 0) {
+ if (key.mResDir != null) {
+ if (assets.addAssetPath(key.mResDir) == 0) {
return null;
}
}
- if (splitResDirs != null) {
- for (String splitResDir : splitResDirs) {
+ if (key.mSplitResDirs != null) {
+ for (final String splitResDir : key.mSplitResDirs) {
if (assets.addAssetPath(splitResDir) == 0) {
return null;
}
}
}
- if (overlayDirs != null) {
- for (String idmapPath : overlayDirs) {
+ if (key.mOverlayDirs != null) {
+ for (final String idmapPath : key.mOverlayDirs) {
assets.addOverlayPath(idmapPath);
}
}
- if (libDirs != null) {
- for (String libDir : libDirs) {
+ if (key.mLibDirs != null) {
+ for (final String libDir : key.mLibDirs) {
if (libDir.endsWith(".apk")) {
// Avoid opening files we know do not have resources,
// like code-only .jar files.
@@ -233,16 +250,17 @@
}
}
}
+ return assets;
+ }
- //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
- DisplayMetrics dm = getDisplayMetricsLocked(displayId);
+ private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
Configuration config;
- final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+ final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
final boolean hasOverrideConfig = key.hasOverrideConfiguration();
if (!isDefaultDisplay || hasOverrideConfig) {
config = new Configuration(getConfiguration());
if (!isDefaultDisplay) {
- applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
+ applyNonDefaultDisplayMetricsToConfiguration(dm, config);
}
if (hasOverrideConfig) {
config.updateFrom(key.mOverrideConfiguration);
@@ -251,16 +269,212 @@
} else {
config = getConfiguration();
}
- r = new Resources(classLoader);
- r.setImpl(new ResourcesImpl(assets, dm, config, compatInfo));
- if (DEBUG) Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
- + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale);
+ return config;
+ }
- final String[] systemLocales = (
- findSystemLocales ?
- AssetManager.getSystem().getLocales() :
- null);
- final String[] nonSystemLocales = assets.getNonSystemLocales();
+
+ private ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
+ AssetManager assets = createAssetManager(key);
+ DisplayMetrics dm = getDisplayMetricsLocked(key.mDisplayId);
+ Configuration config = generateConfig(key, dm);
+ ResourcesImpl impl = new ResourcesImpl(assets, dm, config, key.mCompatInfo);
+ if (DEBUG) {
+ Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
+ }
+ return impl;
+ }
+
+ /**
+ * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
+ *
+ * @param key The key to match.
+ * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
+ */
+ private ResourcesImpl findResourcesImplForKey(@NonNull ResourcesKey key) {
+ WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
+ ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
+ if (impl != null && impl.getAssets().isUpToDate()) {
+ return impl;
+ }
+ return null;
+ }
+
+ /**
+ * Find the ResourcesKey that this ResourcesImpl object is associated with.
+ * @return the ResourcesKey or null if none was found.
+ */
+ private ResourcesKey findKeyForResourceImpl(@NonNull ResourcesImpl resourceImpl) {
+ final int refCount = mResourceImpls.size();
+ for (int i = 0; i < refCount; i++) {
+ WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
+ ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
+ if (impl != null && resourceImpl == impl) {
+ return mResourceImpls.keyAt(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
+ * or the class loader is different.
+ */
+ private Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
+ @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
+ // This is a request tied to an Activity, meaning we will need to update all
+ // Activity related Resources to match this configuration.
+ WeakReference<Resources> weakResourceRef = mActivityResourceReferences.get(activityToken);
+ Resources resources = weakResourceRef != null ? weakResourceRef.get() : null;
+ if (resources == null || !Objects.equals(resources.getClassLoader(), classLoader)) {
+ resources = new Resources(classLoader);
+ mActivityResourceReferences.put(activityToken, new WeakReference<>(resources));
+ if (DEBUG) {
+ Slog.d(TAG, "- creating new ref=" + resources);
+ }
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "- using existing ref=" + resources);
+ }
+ }
+
+ if (resources.getImpl() != impl) {
+ if (DEBUG) {
+ Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
+ }
+
+ // Setting an impl is expensive because we update all ThemeImpl references.
+ // too.
+ resources.setImpl(impl);
+ }
+ return resources;
+ }
+
+ /**
+ * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
+ * otherwise creates a new Resources object.
+ */
+ private Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
+ @NonNull ResourcesImpl impl) {
+ // Find an existing Resources that has this ResourcesImpl set.
+ final int refCount = mResourceReferences.size();
+ for (int i = 0; i < refCount; i++) {
+ WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
+ Resources resources = weakResourceRef != null ? weakResourceRef.get() : null;
+ if (resources != null &&
+ Objects.equals(resources.getClassLoader(), classLoader) &&
+ resources.getImpl() == impl) {
+ if (DEBUG) {
+ Slog.d(TAG, "- using existing ref=" + resources);
+ }
+ return resources;
+ }
+ }
+
+ // Create a new Resources reference and use the existing ResourcesImpl object.
+ Resources resources = new Resources(classLoader);
+ resources.setImpl(impl);
+ mResourceReferences.add(new WeakReference<>(resources));
+ if (DEBUG) {
+ Slog.d(TAG, "- creating new ref=" + resources);
+ Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
+ }
+ return resources;
+ }
+
+ /**
+ * Gets or creates a new Resources object associated with the IBinder token. References returned
+ * by this method live as long as the Activity, meaning they can be cached and used by the
+ * Activity even after a configuration change. If any other parameter is changed
+ * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
+ * is updated and handed back to the caller. However, changing the class loader will result in a
+ * new Resources object.
+ * <p/>
+ * If activityToken is null, a cached Resources object will be returned if it matches the
+ * input parameters. Otherwise a new Resources object that satisfies these parameters is
+ * returned.
+ *
+ * @param activityToken Represents an Activity. If null, global resources are assumed.
+ * @param resDir The base resource path. Can be null (only framework resources will be loaded).
+ * @param splitResDirs An array of split resource paths. Can be null.
+ * @param overlayDirs An array of overlay paths. Can be null.
+ * @param libDirs An array of resource library paths. Can be null.
+ * @param displayId The ID of the display for which to create the resources.
+ * @param overrideConfig The configuration to apply on top of the base configuration. Can be
+ * null. Mostly used with Activities that are in multi-window which may override width and
+ * height properties from the base config.
+ * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
+ * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
+ * @param classLoader The class loader to use when inflating Resources. If null, the
+ * {@link ClassLoader#getSystemClassLoader()} is used.
+ * @return a Resources object from which to access resources.
+ */
+ public Resources getResources(@Nullable IBinder activityToken,
+ @Nullable String resDir,
+ @Nullable String[] splitResDirs,
+ @Nullable String[] overlayDirs,
+ @Nullable String[] libDirs,
+ int displayId,
+ @Nullable Configuration overrideConfig,
+ @NonNull CompatibilityInfo compatInfo,
+ @Nullable ClassLoader classLoader) {
+ final ResourcesKey key = new ResourcesKey(
+ resDir,
+ splitResDirs,
+ overlayDirs,
+ libDirs,
+ displayId,
+ overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
+ compatInfo);
+
+ classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
+
+ final boolean findSystemLocales;
+ final boolean hasNonSystemLocales;
+ synchronized (this) {
+ findSystemLocales = (mSystemLocales.length == 0);
+ hasNonSystemLocales = mHasNonSystemLocales;
+
+ if (DEBUG) {
+ Throwable here = new Throwable();
+ here.fillInStackTrace();
+ Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
+ }
+
+ if (activityToken != null) {
+ ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
+ if (resourcesImpl != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "- using existing impl=" + resourcesImpl);
+ }
+ return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
+ resourcesImpl);
+ }
+
+ // We will create the ResourcesImpl object outside of holding this lock.
+
+ } else {
+ // Clean up any dead references so they don't pile up.
+ ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
+
+ // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
+ ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
+ if (resourcesImpl != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "- using existing impl=" + resourcesImpl);
+ }
+ return getOrCreateResourcesLocked(classLoader, resourcesImpl);
+ }
+
+ // We will create the ResourcesImpl object outside of holding this lock.
+ }
+ }
+
+ // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
+ ResourcesImpl resourcesImpl = createResourcesImpl(key);
+
+ final String[] systemLocales = findSystemLocales
+ ? AssetManager.getSystem().getLocales() : null;
+ final String[] nonSystemLocales = resourcesImpl.getAssets().getNonSystemLocales();
// Avoid checking for non-pseudo-locales if we already know there were some from a previous
// Resources. The default value (for when hasNonSystemLocales is true) doesn't matter,
// since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be
@@ -269,41 +483,75 @@
LocaleList.isPseudoLocalesOnly(nonSystemLocales);
synchronized (this) {
- WeakReference<Resources> wr = mActiveResources.get(key);
- Resources existing = wr != null ? wr.get() : null;
- if (existing != null && existing.getAssets().isUpToDate()) {
- // Someone else already created the resources while we were
- // unlocked; go ahead and use theirs.
- r.getAssets().close();
- return existing;
- }
-
- // XXX need to remove entries when weak references go away
- mActiveResources.put(key, new WeakReference<>(r));
if (mSystemLocales.length == 0) {
mSystemLocales = systemLocales;
}
mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales));
mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly;
- if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size());
- return r;
+
+ ResourcesImpl existingResourcesImpl = findResourcesImplForKey(key);
+ if (existingResourcesImpl != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
+ + " new impl=" + resourcesImpl);
+ }
+ resourcesImpl.getAssets().close();
+ resourcesImpl = existingResourcesImpl;
+ } else {
+ // Add this ResourcesImpl to the cache.
+ mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
+ }
+
+ final Resources resources;
+ if (activityToken != null) {
+ resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
+ resourcesImpl);
+ } else {
+ resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
+ }
+ return resources;
}
}
/**
- * Removes the top level Resources for applications with the given compatibility info.
- * @see #getTopLevelResources(String, String[], String[], String[], int, Configuration, CompatibilityInfo, ClassLoader)
+ * Updates an Activity's Resources object with overrideConfig. The Resources object
+ * that was previously returned by
+ * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
+ * CompatibilityInfo, ClassLoader)} is
+ * still valid and will have the updated configuration.
+ * @param activityToken The Activity token.
+ * @param overrideConfig The configuration override to update.
*/
- void removeTopLevelResources(String resDir, int displayId, Configuration overrideConfiguration,
- CompatibilityInfo compatInfo) {
- final float scale = compatInfo.applicationScale;
- final Configuration overrideConfigCopy = (overrideConfiguration != null)
- ? new Configuration(overrideConfiguration) : null;
- final ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
- mActiveResources.remove(key);
+ public void updateResourcesForActivity(@NonNull IBinder activityToken,
+ @Nullable Configuration overrideConfig) {
+ final ClassLoader classLoader;
+ final ResourcesKey oldKey;
+ synchronized (this) {
+ // Extract the ResourcesKey that was last used to create the Resources for this
+ // activity.
+ WeakReference<Resources> weakResRef = mActivityResourceReferences.get(activityToken);
+ final Resources resources = weakResRef != null ? weakResRef.get() : null;
+ if (resources == null) {
+ Slog.e(TAG, "can't update resources for uncached activity " + activityToken);
+ return;
+ }
+
+ classLoader = resources.getClassLoader();
+ oldKey = findKeyForResourceImpl(resources.getImpl());
+ if (oldKey == null) {
+ Slog.e(TAG, "can't find ResourcesKey for resources impl=" + resources.getImpl());
+ return;
+ }
+ }
+
+ // Update the Resources object with the new override config and all of the existing
+ // settings.
+ getResources(activityToken, oldKey.mResDir, oldKey.mSplitResDirs, oldKey.mOverlayDirs,
+ oldKey.mLibDirs, oldKey.mDisplayId, overrideConfig, oldKey.mCompatInfo,
+ classLoader);
}
- /* package */ void setDefaultLocalesLocked(LocaleList locales) {
+ /* package */ void setDefaultLocalesLocked(@NonNull LocaleList locales) {
final int bestLocale;
if (mHasNonSystemLocales) {
bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales);
@@ -317,11 +565,8 @@
LocaleList.setDefault(locales, bestLocale);
}
- final boolean applyConfigurationToResourcesLocked(Configuration config,
- CompatibilityInfo compat) {
- if (mResConfiguration == null) {
- mResConfiguration = new Configuration();
- }
+ public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
+ @Nullable CompatibilityInfo compat) {
if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
+ mResConfiguration.seq + ", newSeq=" + config.seq);
@@ -368,9 +613,9 @@
Configuration tmpConfig = null;
- for (int i = mActiveResources.size() - 1; i >= 0; i--) {
- ResourcesKey key = mActiveResources.keyAt(i);
- Resources r = mActiveResources.valueAt(i).get();
+ for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
+ ResourcesKey key = mResourceImpls.keyAt(i);
+ ResourcesImpl r = mResourceImpls.valueAt(i).get();
if (r != null) {
if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ r + " config to: " + localeAdjustedConfig);
@@ -385,7 +630,7 @@
tmpConfig.setTo(localeAdjustedConfig);
if (!isDefaultDisplay) {
dm = getDisplayMetricsLocked(displayId);
- applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig);
+ applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
}
if (hasOverrideConfiguration) {
tmpConfig.updateFrom(key.mOverrideConfiguration);
@@ -398,11 +643,10 @@
// + " " + r + ": " + r.getConfiguration());
} else {
//Slog.i(TAG, "Removing old resources " + v.getKey());
- mActiveResources.removeAt(i);
+ mResourceImpls.removeAt(i);
}
}
return changes != 0;
}
-
-}
+}
\ No newline at end of file
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index af9705f..53a6351 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3723,7 +3723,9 @@
* be hidden, it will not show up in recents, will not be able to show toasts or dialogs
* or ring the device.
*
- * <p>The package must already be installed.
+ * <p>The package must already be installed. If the package is uninstalled while suspended
+ * the package will no longer be suspended. The admin can block this by using
+ * {@link #setUninstallBlocked}.
*
* @param admin The name of the admin component to check.
* @param packageNames The package names to suspend or unsuspend.
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index ec536e0..1f603ef 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1000,8 +1000,8 @@
}
/**
- * Optionally set the URI where this package was downloaded from. Used for
- * verification purposes.
+ * Optionally set the URI where this package was downloaded from. This is
+ * informational and may be used as a signal for anti-malware purposes.
*
* @see Intent#EXTRA_ORIGINATING_URI
*/
@@ -1010,7 +1010,8 @@
}
/**
- * Sets the UID that initiated package installation. Used for verification purposes.
+ * Sets the UID that initiated package installation. This is informational
+ * and may be used as a signal for anti-malware purposes.
*
* @see PackageManager#EXTRA_VERIFICATION_INSTALLER_UID
*/
@@ -1019,8 +1020,8 @@
}
/**
- * Optionally set the URI that referred you to install this package. Used
- * for verification purposes.
+ * Optionally set the URI that referred you to install this package. This is
+ * informational and may be used as a signal for anti-malware purposes.
*
* @see Intent#EXTRA_REFERRER
*/
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f80f362..e1e8a07 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5367,6 +5367,9 @@
* will be hidden, the application will not show up in recents, will not be able to show
* toasts or dialogs or ring the device.
*
+ * <p>The package must already be installed. If the package is uninstalled while suspended
+ * the package will no longer be suspended.
+ *
* @param packageNames The names of the packages to set the suspended status.
* @param suspended If set to {@code true} than the packages will be suspended, if set to
* {@code false} the packages will be unsuspended.
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index f337fe6..fb706fc 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -54,6 +54,7 @@
import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.XmlUtils;
@@ -62,6 +63,8 @@
import java.io.IOException;
import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
/**
* Class for accessing an application's resources. This sits on top of the
@@ -113,6 +116,13 @@
final ClassLoader mClassLoader;
/**
+ * WeakReferences to Themes that were constructed from this Resources object.
+ * We keep track of these in case our underlying implementation is changed, in which case
+ * the Themes must also get updated ThemeImpls.
+ */
+ private final ArrayList<WeakReference<Theme>> mThemeRefs = new ArrayList<>();
+
+ /**
* Returns the most appropriate default theme for the specified target SDK version.
* <ul>
* <li>Below API 11: Gingerbread
@@ -231,10 +241,42 @@
}
/**
+ * Set the underlying implementation (containing all the resources and caches)
+ * and updates all Theme references to new implementations as well.
* @hide
*/
public void setImpl(ResourcesImpl impl) {
+ if (impl == mResourcesImpl) {
+ return;
+ }
+
mResourcesImpl = impl;
+
+ // Create new ThemeImpls that are identical to the ones we have.
+ synchronized (mThemeRefs) {
+ final int count = mThemeRefs.size();
+ for (int i = 0; i < count; i++) {
+ WeakReference<Theme> weakThemeRef = mThemeRefs.get(i);
+ Theme theme = weakThemeRef != null ? weakThemeRef.get() : null;
+ if (theme != null) {
+ theme.setImpl(mResourcesImpl.newThemeImpl(theme.getKey()));
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public ResourcesImpl getImpl() {
+ return mResourcesImpl;
+ }
+
+ /**
+ * @hide
+ */
+ public ClassLoader getClassLoader() {
+ return mClassLoader;
}
/**
@@ -1683,6 +1725,7 @@
public final Theme newTheme() {
Theme theme = new Theme();
theme.setImpl(mResourcesImpl.newThemeImpl());
+ mThemeRefs.add(new WeakReference<>(theme));
return theme;
}
@@ -1785,6 +1828,7 @@
* This is just for testing.
* @hide
*/
+ @VisibleForTesting
public void setCompatibilityInfo(CompatibilityInfo ci) {
if (ci != null) {
mResourcesImpl.updateConfiguration(null, null, ci);
@@ -2069,6 +2113,15 @@
}
/**
+ * Called by ConfigurationBoundResourceCacheTest.
+ * @hide
+ */
+ @VisibleForTesting
+ public int calcConfigChanges(Configuration config) {
+ return mResourcesImpl.calcConfigChanges(config);
+ }
+
+ /**
* Obtains styled attributes from the theme, if available, or unstyled
* resources if the theme is null.
*
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 2ffd372..0858cb8 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -48,6 +48,13 @@
import java.util.Locale;
/**
+ * The implementation of Resource access. This class contains the AssetManager and all caches
+ * associated with it.
+ *
+ * {@link Resources} is just a thing wrapper around this class. When a configuration change
+ * occurs, clients can retain the same {@link Resources} reference because the underlying
+ * {@link ResourcesImpl} object will be updated or re-created.
+ *
* @hide
*/
public class ResourcesImpl {
@@ -126,14 +133,14 @@
* @param compatInfo this resource's compatibility info. Must not be null.
*/
public ResourcesImpl(AssetManager assets, DisplayMetrics metrics, Configuration config,
- CompatibilityInfo compatInfo) {
+ CompatibilityInfo compatInfo) {
mAssets = assets;
mMetrics.setToDefaults();
updateConfiguration(config, metrics, compatInfo);
mAssets.ensureStringBlocks();
}
- AssetManager getAssets() {
+ public AssetManager getAssets() {
return mAssets;
}
@@ -174,7 +181,7 @@
}
void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
- boolean resolveRefs) throws NotFoundException {
+ boolean resolveRefs) throws NotFoundException {
boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
if (found) {
return;
@@ -298,8 +305,8 @@
return mStateListAnimatorCache;
}
- void updateConfiguration(Configuration config, DisplayMetrics metrics,
- CompatibilityInfo compat) {
+ public void updateConfiguration(Configuration config, DisplayMetrics metrics,
+ CompatibilityInfo compat) {
synchronized (mAccessLock) {
if (false) {
Slog.i(TAG, "**** Updating config of " + this + ": old config is "
@@ -388,9 +395,9 @@
}
/**
- * Called by ConfigurationBoundResourceCacheTest via reflection.
+ * Called by ConfigurationBoundResourceCacheTest.
*/
- private int calcConfigChanges(Configuration config) {
+ public int calcConfigChanges(Configuration config) {
int configChanges = 0xfffffff;
if (config != null) {
mTmpConfig.setTo(config);
@@ -460,7 +467,7 @@
@Nullable
Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
- boolean useCache) throws NotFoundException {
+ boolean useCache) throws NotFoundException {
try {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
@@ -553,7 +560,7 @@
}
private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
- Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
+ Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
final Drawable.ConstantState cs = dr.getConstantState();
if (cs == null) {
return;
@@ -587,7 +594,7 @@
}
private boolean verifyPreloadConfig(int changingConfigurations, int allowVarying,
- int resourceId, String name) {
+ int resourceId, String name) {
// We allow preloading of resources even if they vary by font scale (which
// doesn't impact resource selection) or density (which we handle specially by
// simply turning off all preloading), as well as any other configs specified
@@ -625,7 +632,7 @@
* Loads a drawable from XML or resources stream.
*/
private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,
- Resources.Theme theme) {
+ Resources.Theme theme) {
if (value.string == null) {
throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
+ Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
@@ -681,7 +688,7 @@
* Last, parse the XML and generate the CSL.
*/
private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme,
- TypedValue value, int id) {
+ TypedValue value, int id) {
final long key = (((long) value.assetCookie) << 32) | value.data;
final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
@@ -714,7 +721,7 @@
@Nullable
ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
- Resources.Theme theme) {
+ Resources.Theme theme) {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
if ((id >>> 24) == 0x1) {
@@ -755,7 +762,7 @@
@Nullable
ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
- Resources.Theme theme)
+ Resources.Theme theme)
throws NotFoundException {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
@@ -815,7 +822,7 @@
*/
@Nullable
private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
- Resources.Theme theme) {
+ Resources.Theme theme) {
if (value.string == null) {
throw new UnsupportedOperationException(
"Can't convert to ComplexColor: type=0x" + value.type);
@@ -893,8 +900,8 @@
* @throws NotFoundException if the file could not be loaded
*/
@NonNull
- XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id,
- int assetCookie, @NonNull String type)
+ XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
+ @NonNull String type)
throws NotFoundException {
if (id != 0) {
try {
@@ -975,6 +982,16 @@
return new ThemeImpl();
}
+ /**
+ * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey.
+ */
+ ThemeImpl newThemeImpl(Resources.ThemeKey key) {
+ ThemeImpl impl = new ThemeImpl();
+ impl.mKey.setTo(key);
+ impl.rebase();
+ return impl;
+ }
+
public class ThemeImpl {
/**
* Unique key for the series of styles applied to this theme.
@@ -1035,10 +1052,10 @@
@NonNull
TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
- AttributeSet set,
- @StyleableRes int[] attrs,
- @AttrRes int defStyleAttr,
- @StyleRes int defStyleRes) {
+ AttributeSet set,
+ @StyleableRes int[] attrs,
+ @AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes) {
synchronized (mKey) {
final int len = attrs.length;
final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
@@ -1060,8 +1077,8 @@
@NonNull
TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
- @NonNull int[] values,
- @NonNull int[] attrs) {
+ @NonNull int[] values,
+ @NonNull int[] attrs) {
synchronized (mKey) {
final int len = attrs.length;
if (values == null || len != values.length) {
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index 2620571..e894492 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -17,32 +17,59 @@
package android.content.res;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import java.util.Arrays;
import java.util.Objects;
/** @hide */
public final class ResourcesKey {
- private final String mResDir;
- private final float mScale;
- private final int mHash;
+ @Nullable
+ public final String mResDir;
+
+ @Nullable
+ public final String[] mSplitResDirs;
+
+ @Nullable
+ public final String[] mOverlayDirs;
+
+ @Nullable
+ public final String[] mLibDirs;
public final int mDisplayId;
+
@NonNull
public final Configuration mOverrideConfiguration;
- public ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration,
- float scale) {
+ @NonNull
+ public final CompatibilityInfo mCompatInfo;
+
+ private final int mHash;
+
+ public ResourcesKey(@Nullable String resDir,
+ @Nullable String[] splitResDirs,
+ @Nullable String[] overlayDirs,
+ @Nullable String[] libDirs,
+ int displayId,
+ @Nullable Configuration overrideConfig,
+ @Nullable CompatibilityInfo compatInfo) {
mResDir = resDir;
+ mSplitResDirs = splitResDirs;
+ mOverlayDirs = overlayDirs;
+ mLibDirs = libDirs;
mDisplayId = displayId;
- mOverrideConfiguration = overrideConfiguration != null
- ? overrideConfiguration : Configuration.EMPTY;
- mScale = scale;
+ mOverrideConfiguration = overrideConfig != null ? overrideConfig : Configuration.EMPTY;
+ mCompatInfo = compatInfo != null ? compatInfo : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
int hash = 17;
- hash = 31 * hash + (mResDir == null ? 0 : mResDir.hashCode());
+ hash = 31 * hash + Objects.hashCode(mResDir);
+ hash = 31 * hash + Arrays.hashCode(mSplitResDirs);
+ hash = 31 * hash + Arrays.hashCode(mOverlayDirs);
+ hash = 31 * hash + Arrays.hashCode(mLibDirs);
hash = 31 * hash + mDisplayId;
- hash = 31 * hash + mOverrideConfiguration.hashCode();
- hash = 31 * hash + Float.floatToIntBits(mScale);
+ hash = 31 * hash + Objects.hashCode(mOverrideConfiguration);
+ hash = 31 * hash + Objects.hashCode(mCompatInfo);
mHash = hash;
}
@@ -60,18 +87,32 @@
if (!(obj instanceof ResourcesKey)) {
return false;
}
+
ResourcesKey peer = (ResourcesKey) obj;
+ if (mHash != peer.mHash) {
+ // If the hashes don't match, the objects can't match.
+ return false;
+ }
if (!Objects.equals(mResDir, peer.mResDir)) {
return false;
}
+ if (!Arrays.equals(mSplitResDirs, peer.mSplitResDirs)) {
+ return false;
+ }
+ if (!Arrays.equals(mOverlayDirs, peer.mOverlayDirs)) {
+ return false;
+ }
+ if (!Arrays.equals(mLibDirs, peer.mLibDirs)) {
+ return false;
+ }
if (mDisplayId != peer.mDisplayId) {
return false;
}
- if (!mOverrideConfiguration.equals(peer.mOverrideConfiguration)) {
+ if (!Objects.equals(mOverrideConfiguration, peer.mOverrideConfiguration)) {
return false;
}
- if (mScale != peer.mScale) {
+ if (!Objects.equals(mCompatInfo, peer.mCompatInfo)) {
return false;
}
return true;
@@ -79,6 +120,29 @@
@Override
public String toString() {
- return Integer.toHexString(mHash);
+ StringBuilder builder = new StringBuilder().append("ResourcesKey{");
+ builder.append(" mHash=").append(Integer.toHexString(mHash));
+ builder.append(" mResDir=").append(mResDir);
+ builder.append(" mSplitDirs=[");
+ if (mSplitResDirs != null) {
+ builder.append(TextUtils.join(",", mSplitResDirs));
+ }
+ builder.append("]");
+ builder.append(" mOverlayDirs=[");
+ if (mOverlayDirs != null) {
+ builder.append(TextUtils.join(",", mOverlayDirs));
+ }
+ builder.append("]");
+ builder.append(" mLibDirs=[");
+ if (mLibDirs != null) {
+ builder.append(TextUtils.join(",", mLibDirs));
+ }
+ builder.append("]");
+ builder.append(" mDisplayId=").append(mDisplayId);
+ builder.append(" mOverrideConfig=").append(Configuration.resourceQualifierString(
+ mOverrideConfiguration));
+ builder.append(" mCompatInfo=").append(mCompatInfo);
+ builder.append("}");
+ return builder.toString();
}
}
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 0065cd9..e7a9b7d 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -119,8 +119,6 @@
public static final String EXTRA_PROMPT = "android.provider.extra.PROMPT";
/** {@hide} */
- public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT";
- /** {@hide} */
public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
/** {@hide} */
@@ -383,18 +381,6 @@
*/
public static final int FLAG_ARCHIVE = 1 << 15;
- /**
- * Flag indicating that document titles should be hidden when viewing
- * this directory in a larger format grid. For example, a directory
- * containing only images may want the image thumbnails to speak for
- * themselves. Only valid when {@link #COLUMN_MIME_TYPE} is
- * {@link #MIME_TYPE_DIR}.
- *
- * @see #COLUMN_FLAGS
- * @see #FLAG_DIR_PREFERS_GRID
- * @hide
- */
- public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 16;
}
/**
diff --git a/core/java/android/transition/ArcMotion.java b/core/java/android/transition/ArcMotion.java
index fa4c8d2..70443ba 100644
--- a/core/java/android/transition/ArcMotion.java
+++ b/core/java/android/transition/ArcMotion.java
@@ -15,13 +15,13 @@
*/
package android.transition;
-import com.android.internal.R;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Path;
import android.util.AttributeSet;
+import com.android.internal.R;
+
/**
* A PathMotion that generates a curved path along an arc on an imaginary circle containing
* the two points. If the horizontal distance between the points is less than the vertical
@@ -207,7 +207,7 @@
ey = (startY + endY) / 2;
} else {
float deltaX = endX - startX;
- float deltaY = startY - endY; // Y is inverted compared to diagram above.
+ float deltaY = endY - startY;
// hypotenuse squared.
float h2 = deltaX * deltaX + deltaY * deltaY;
@@ -219,24 +219,35 @@
float midDist2 = h2 * 0.25f;
float minimumArcDist2 = 0;
+ boolean isQuadrant1Or3 = (deltaX * deltaY) > 0;
- if (Math.abs(deltaX) < Math.abs(deltaY)) {
+ if ((Math.abs(deltaX) < Math.abs(deltaY))) {
// Similar triangles bfa and bde mean that (ab/fb = eb/bd)
// Therefore, eb = ab * bd / fb
// ab = hypotenuse
// bd = hypotenuse/2
// fb = deltaY
float eDistY = h2 / (2 * deltaY);
- ey = endY + eDistY;
- ex = endX;
+ if (isQuadrant1Or3) {
+ ey = startY + eDistY;
+ ex = startX;
+ } else {
+ ey = endY - eDistY;
+ ex = endX;
+ }
minimumArcDist2 = midDist2 * mMinimumVerticalTangent
* mMinimumVerticalTangent;
} else {
// Same as above, but flip X & Y
float eDistX = h2 / (2 * deltaX);
- ex = endX + eDistX;
- ey = endY;
+ if (isQuadrant1Or3) {
+ ex = endX - eDistX;
+ ey = endY;
+ } else {
+ ex = startX + eDistX;
+ ey = startY;
+ }
minimumArcDist2 = midDist2 * mMinimumHorizontalTangent
* mMinimumHorizontalTangent;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ac3eaf7..4c461ad 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3071,7 +3071,14 @@
* @attr ref android.R.styleable#TextView_elegantTextHeight
*/
public void setElegantTextHeight(boolean elegant) {
- mTextPaint.setElegantTextHeight(elegant);
+ if (elegant != mTextPaint.isElegantTextHeight()) {
+ mTextPaint.setElegantTextHeight(elegant);
+ if (mLayout != null) {
+ nullLayouts();
+ requestLayout();
+ invalidate();
+ }
+ }
}
/**
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index a84a061..ee73b90 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -27,6 +27,7 @@
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -477,4 +478,48 @@
}
return !diff;
}
+
+ /**
+ * Removes elements that match the predicate in an efficient way that alters the order of
+ * elements in the collection. This should only be used if order is not important.
+ * @param collection The ArrayList from which to remove elements.
+ * @param predicate The predicate that each element is tested against.
+ * @return the number of elements removed.
+ */
+ public static <T> int unstableRemoveIf(@Nullable ArrayList<T> collection,
+ @NonNull java.util.function.Predicate<T> predicate) {
+ if (collection == null) {
+ return 0;
+ }
+
+ final int size = collection.size();
+ int leftIdx = 0;
+ int rightIdx = size - 1;
+ while (leftIdx <= rightIdx) {
+ // Find the next element to remove moving left to right.
+ while (leftIdx < size && !predicate.test(collection.get(leftIdx))) {
+ leftIdx++;
+ }
+
+ // Find the next element to keep moving right to left.
+ while (rightIdx > leftIdx && predicate.test(collection.get(rightIdx))) {
+ rightIdx--;
+ }
+
+ if (leftIdx >= rightIdx) {
+ // Done.
+ break;
+ }
+
+ Collections.swap(collection, leftIdx, rightIdx);
+ leftIdx++;
+ rightIdx--;
+ }
+
+ // leftIdx is now at the end.
+ for (int i = size - 1; i >= leftIdx; i--) {
+ collection.remove(i);
+ }
+ return size - leftIdx;
+ }
}
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index c6baf1c..d80d8f2 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
#define LOG_TAG "DngCreator_JNI"
#include <inttypes.h>
#include <string.h>
@@ -1792,6 +1792,8 @@
{
// Set up orientation tags.
+ // Note: There's only one orientation field for the whole file, in IFD0
+ // The main image and any thumbnails therefore have the same orientation.
uint16_t orientation = nativeContext->getOrientation();
BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0),
env, TAG_ORIENTATION, writer);
@@ -1873,7 +1875,6 @@
}
Vector<uint16_t> tagsToMove;
- tagsToMove.add(TAG_ORIENTATION);
tagsToMove.add(TAG_NEWSUBFILETYPE);
tagsToMove.add(TAG_ACTIVEAREA);
tagsToMove.add(TAG_BITSPERSAMPLE);
@@ -1904,12 +1905,6 @@
return nullptr;
}
- // Make sure both IFDs get the same orientation tag
- sp<TiffEntry> orientEntry = writer->getEntry(TAG_ORIENTATION, TIFF_IFD_SUB1);
- if (orientEntry.get() != nullptr) {
- writer->addEntry(orientEntry, TIFF_IFD_0);
- }
-
// Setup thumbnail tags
{
diff --git a/core/tests/coretests/src/android/app/DownloadManagerFunctionalTest.java b/core/tests/coretests/src/android/app/DownloadManagerFunctionalTest.java
index 78a3aa6..4a53852 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerFunctionalTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerFunctionalTest.java
@@ -23,8 +23,6 @@
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
import com.google.mockwebserver.MockResponse;
import java.io.File;
@@ -96,7 +94,7 @@
/**
* Test a basic download of a binary file 500k in size.
*/
- @MediumTest
+ @LargeTest
public void testBinaryDownloadToSystemCache() throws Exception {
int fileSize = 1024;
byte[] blobData = generateData(fileSize, DataType.BINARY);
@@ -109,7 +107,7 @@
/**
* Tests the basic downloading of a text file 300000 bytes in size.
*/
- @MediumTest
+ @LargeTest
public void testTextDownloadToSystemCache() throws Exception {
int fileSize = 1024;
byte[] blobData = generateData(fileSize, DataType.TEXT);
@@ -149,7 +147,7 @@
/**
* Tests trying to download to SD card when the file with same name already exists.
*/
- @MediumTest
+ @LargeTest
public void testDownloadToExternal_fileExists() throws Exception {
File existentFile = createFileOnSD(null, 1, DataType.TEXT, null);
byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
@@ -182,7 +180,7 @@
/**
* Tests trying to download a file to SD card.
*/
- @MediumTest
+ @LargeTest
public void testDownloadToExternal() throws Exception {
String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
File downloadedFile = new File(localDownloadDirectory, DEFAULT_FILENAME);
@@ -217,7 +215,7 @@
/**
* Tests trying to download a file to the system partition.
*/
- @MediumTest
+ @LargeTest
public void testDownloadToProhibitedDirectory() throws Exception {
File downloadedFile = new File(PROHIBITED_DIRECTORY, DEFAULT_FILENAME);
try {
@@ -247,7 +245,7 @@
/**
* Tests that we get the correct download ID from the download notification.
*/
- @MediumTest
+ @LargeTest
public void testGetDownloadIdOnNotification() throws Exception {
byte[] blobData = generateData(3000, DataType.TEXT); // file size = 3000 bytes
@@ -265,7 +263,7 @@
/**
* Tests the download failure error after too many redirects (>5).
*/
- @MediumTest
+ @LargeTest
public void testErrorTooManyRedirects() throws Exception {
Uri uri = getServerUri(DEFAULT_FILENAME);
@@ -305,7 +303,7 @@
/**
* Tests that we can remove a download from the download manager.
*/
- @MediumTest
+ @LargeTest
public void testRemoveDownload() throws Exception {
int fileSize = 1024;
byte[] blobData = generateData(fileSize, DataType.BINARY);
@@ -325,7 +323,7 @@
/**
* Tests that we can set the title of a download.
*/
- @MediumTest
+ @LargeTest
public void testSetTitle() throws Exception {
int fileSize = 1024;
byte[] blobData = generateData(fileSize, DataType.BINARY);
diff --git a/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java
index 5d46489..47554a6 100644
--- a/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java
+++ b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java
@@ -17,20 +17,18 @@
package android.content.res;
import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.TypedValue;
import com.android.frameworks.coretests.R;
import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
public class ConfigurationBoundResourceCacheTest
extends ActivityInstrumentationTestCase2<ResourceCacheActivity> {
ConfigurationBoundResourceCache<Float> mCache;
- Method mCalcConfigChanges;
-
public ConfigurationBoundResourceCacheTest() {
super(ResourceCacheActivity.class);
}
@@ -41,33 +39,42 @@
mCache = new ConfigurationBoundResourceCache<>();
}
+ @SmallTest
public void testGetEmpty() {
- assertNull(mCache.get(-1, null));
+ final Resources res = getActivity().getResources();
+ assertNull(mCache.getInstance(-1, res, null));
}
+ @SmallTest
public void testSetGet() {
mCache.put(1, null, new DummyFloatConstantState(5f));
- assertEquals(5f, mCache.get(1, null));
- assertNotSame(5f, mCache.get(1, null));
- assertEquals(null, mCache.get(1, getActivity().getTheme()));
+ final Resources res = getActivity().getResources();
+ assertEquals(5f, mCache.getInstance(1, res, null));
+ assertNotSame(5f, mCache.getInstance(1, res, null));
+ assertEquals(null, mCache.getInstance(1, res, getActivity().getTheme()));
}
+ @SmallTest
public void testSetGetThemed() {
mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f));
- assertEquals(null, mCache.get(1, null));
- assertEquals(5f, mCache.get(1, getActivity().getTheme()));
- assertNotSame(5f, mCache.get(1, getActivity().getTheme()));
+ final Resources res = getActivity().getResources();
+ assertEquals(null, mCache.getInstance(1, res, null));
+ assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()));
+ assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()));
}
+ @SmallTest
public void testMultiThreadPutGet() {
mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f));
mCache.put(1, null, new DummyFloatConstantState(10f));
- assertEquals(10f, mCache.get(1, null));
- assertNotSame(10f, mCache.get(1, null));
- assertEquals(5f, mCache.get(1, getActivity().getTheme()));
- assertNotSame(5f, mCache.get(1, getActivity().getTheme()));
+ final Resources res = getActivity().getResources();
+ assertEquals(10f, mCache.getInstance(1, res, null));
+ assertNotSame(10f, mCache.getInstance(1, res, null));
+ assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()));
+ assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()));
}
+ @SmallTest
public void testVoidConfigChange()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
TypedValue staticValue = new TypedValue();
@@ -83,11 +90,12 @@
Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf);
- assertEquals(staticDim, mCache.get(key, getActivity().getTheme()));
+ assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()));
mCache.onConfigurationChange(changes);
- assertEquals(staticDim, mCache.get(key, getActivity().getTheme()));
+ assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()));
}
+ @SmallTest
public void testEffectiveConfigChange()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
TypedValue changingValue = new TypedValue();
@@ -105,11 +113,12 @@
Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf);
- assertEquals(changingDim, mCache.get(key, getActivity().getTheme()));
+ assertEquals(changingDim, mCache.getInstance(key, res, getActivity().getTheme()));
mCache.onConfigurationChange(changes);
assertNull(mCache.get(key, getActivity().getTheme()));
}
+ @SmallTest
public void testConfigChangeMultipleResources()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
TypedValue staticValue = new TypedValue();
@@ -130,17 +139,19 @@
Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf);
- assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic,
+ assertEquals(staticDim, mCache.getInstance(R.dimen.resource_cache_test_generic, res,
getActivity().getTheme()));
- assertEquals(changingDim, mCache.get(R.dimen.resource_cache_test_orientation_dependent,
- getActivity().getTheme()));
+ assertEquals(changingDim,
+ mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
+ getActivity().getTheme()));
mCache.onConfigurationChange(changes);
- assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic,
+ assertEquals(staticDim, mCache.getInstance(R.dimen.resource_cache_test_generic, res,
getActivity().getTheme()));
- assertNull(mCache.get(R.dimen.resource_cache_test_orientation_dependent,
+ assertNull(mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
getActivity().getTheme()));
}
+ @SmallTest
public void testConfigChangeMultipleThemes()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
TypedValue[] staticValues = new TypedValue[]{new TypedValue(), new TypedValue()};
@@ -172,31 +183,27 @@
int changes = calcConfigChanges(res, newCnf);
for (int i = 0; i < 2; i++) {
final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
- assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, theme));
+ assertEquals(staticDim,
+ mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme));
assertEquals(changingDim,
- mCache.get(R.dimen.resource_cache_test_orientation_dependent, theme));
+ mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
+ theme));
}
mCache.onConfigurationChange(changes);
for (int i = 0; i < 2; i++) {
final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
- assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, theme));
- assertNull(mCache.get(R.dimen.resource_cache_test_orientation_dependent, theme));
+ assertEquals(staticDim,
+ mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme));
+ assertNull(mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
+ theme));
}
}
- private int calcConfigChanges(Resources resources, Configuration configuration)
- throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- if (mCalcConfigChanges == null) {
- mCalcConfigChanges = Resources.class.getDeclaredMethod("calcConfigChanges",
- Configuration.class);
- mCalcConfigChanges.setAccessible(true);
- }
- return (Integer) mCalcConfigChanges.invoke(resources, configuration);
-
+ private static int calcConfigChanges(Resources resources, Configuration configuration) {
+ return resources.calcConfigChanges(configuration);
}
- static class DummyFloatConstantState extends
- ConstantState<Float> {
+ static class DummyFloatConstantState extends ConstantState<Float> {
final Float mObj;
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
new file mode 100644
index 0000000..3cadbf6
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.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.content.res;
+
+import android.annotation.NonNull;
+import android.app.ResourcesManager;
+import android.os.Binder;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.DisplayMetrics;
+import android.util.LocaleList;
+import android.util.TypedValue;
+import android.view.Display;
+import junit.framework.TestCase;
+
+public class ResourcesManagerTest extends TestCase {
+ private static final String APP_ONE_RES_DIR = "app_one.apk";
+ private static final String APP_ONE_RES_SPLIT_DIR = "app_one_split.apk";
+ private static final String APP_TWO_RES_DIR = "app_two.apk";
+ private static final String LIB_RES_DIR = "lib.apk";
+
+ private ResourcesManager mResourcesManager;
+ private DisplayMetrics mDisplayMetrics;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mDisplayMetrics = new DisplayMetrics();
+ mDisplayMetrics.setToDefaults();
+
+ // Override defaults (which take device specific properties).
+ mDisplayMetrics.density = 1.0f;
+ mDisplayMetrics.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
+ mDisplayMetrics.xdpi = DisplayMetrics.DENSITY_DEFAULT;
+ mDisplayMetrics.ydpi = DisplayMetrics.DENSITY_DEFAULT;
+ mDisplayMetrics.noncompatDensity = mDisplayMetrics.density;
+ mDisplayMetrics.noncompatDensityDpi = mDisplayMetrics.densityDpi;
+ mDisplayMetrics.noncompatXdpi = DisplayMetrics.DENSITY_DEFAULT;
+ mDisplayMetrics.noncompatYdpi = DisplayMetrics.DENSITY_DEFAULT;
+
+ mResourcesManager = new ResourcesManager() {
+ @Override
+ protected AssetManager createAssetManager(@NonNull ResourcesKey key) {
+ return new AssetManager();
+ }
+
+ @Override
+ protected DisplayMetrics getDisplayMetricsLocked(int displayId) {
+ return mDisplayMetrics;
+ }
+ };
+ }
+
+ @SmallTest
+ public void testMultipleCallsWithIdenticalParametersCacheReference() {
+ Resources resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources);
+
+ Resources newResources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(newResources);
+ assertSame(resources, newResources);
+ }
+
+ @SmallTest
+ public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() {
+ Resources resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources);
+
+ Configuration overrideConfig = new Configuration();
+ overrideConfig.smallestScreenWidthDp = 200;
+ Resources newResources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, overrideConfig,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(newResources);
+ assertNotSame(resources, newResources);
+ }
+
+ @SmallTest
+ public void testAddingASplitCreatesANewImpl() {
+ Resources resources1 = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources1);
+
+ Resources resources2 = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, new String[] { APP_ONE_RES_SPLIT_DIR }, null, null,
+ Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources2);
+
+ assertNotSame(resources1, resources2);
+ assertNotSame(resources1.getImpl(), resources2.getImpl());
+ }
+
+ @SmallTest
+ public void testUpdateConfigurationUpdatesAllAssetManagers() {
+ Resources resources1 = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources1);
+
+ Resources resources2 = mResourcesManager.getResources(
+ null, APP_TWO_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources2);
+
+ Binder activity = new Binder();
+ final Configuration overrideConfig = new Configuration();
+ overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ Resources resources3 = mResourcesManager.getResources(
+ activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY,
+ overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources3);
+
+ // No Resources object should be the same.
+ assertNotSame(resources1, resources2);
+ assertNotSame(resources1, resources3);
+ assertNotSame(resources2, resources3);
+
+ // Each ResourcesImpl should be different.
+ assertNotSame(resources1.getImpl(), resources2.getImpl());
+ assertNotSame(resources1.getImpl(), resources3.getImpl());
+ assertNotSame(resources2.getImpl(), resources3.getImpl());
+
+ Configuration newConfig = new Configuration();
+ newConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null);
+
+ final Configuration expectedConfig = new Configuration();
+ expectedConfig.setLocales(LocaleList.getAdjustedDefault());
+ expectedConfig.densityDpi = mDisplayMetrics.densityDpi;
+ expectedConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
+
+ assertEquals(expectedConfig, resources1.getConfiguration());
+ assertEquals(expectedConfig, resources2.getConfiguration());
+ assertEquals(expectedConfig, resources3.getConfiguration());
+ }
+
+ @SmallTest
+ public void testTwoActivitiesWithIdenticalParametersShareImpl() {
+ Binder activity1 = new Binder();
+ Resources resources1 = mResourcesManager.getResources(
+ activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources1);
+
+ Binder activity2 = new Binder();
+ Resources resources2 = mResourcesManager.getResources(
+ activity2, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources1);
+
+ // The references themselves should be unique.
+ assertNotSame(resources1, resources2);
+
+ // The implementations should be the same.
+ assertSame(resources1.getImpl(), resources2.getImpl());
+
+ final Configuration overrideConfig = new Configuration();
+ overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ Resources resources3 = mResourcesManager.getResources(
+ activity2, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY,
+ overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+
+ // Since we requested new resources for activity2, the resource should be the same
+ // as the one returned before for activity2.
+ assertSame(resources2, resources3);
+
+ // But the implementation has changed.
+ assertNotSame(resources1.getImpl(), resources2.getImpl());
+ }
+
+ @SmallTest
+ public void testThemesGetUpdatedWithNewImpl() {
+ Binder activity1 = new Binder();
+ Resources resources1 = mResourcesManager.getResources(
+ activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources1);
+
+ Resources.Theme theme = resources1.newTheme();
+ assertSame(resources1, theme.getResources());
+ theme.applyStyle(android.R.style.Theme_NoTitleBar, false);
+
+ TypedValue value = new TypedValue();
+ assertTrue(theme.resolveAttribute(android.R.attr.windowNoTitle, value, true));
+ assertEquals(TypedValue.TYPE_INT_BOOLEAN, value.type);
+ assertTrue(value.data != 0);
+
+ final Configuration overrideConfig = new Configuration();
+ overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ Resources resources2 = mResourcesManager.getResources(
+ activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY,
+ overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources2);
+ assertSame(resources1, resources2);
+ assertSame(resources2, theme.getResources());
+
+ // Make sure we can still access the data.
+ assertTrue(theme.resolveAttribute(android.R.attr.windowNoTitle, value, true));
+ assertEquals(TypedValue.TYPE_INT_BOOLEAN, value.type);
+ assertTrue(value.data != 0);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java
new file mode 100644
index 0000000..b3897ce
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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 com.android.internal.util;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+public class ArrayUtilsTest extends TestCase {
+
+ @SmallTest
+ public void testUnstableRemoveIf() throws Exception {
+ java.util.function.Predicate<Object> isNull = new java.util.function.Predicate<Object>() {
+ @Override
+ public boolean test(Object o) {
+ return o == null;
+ }
+ };
+
+ final Object a = new Object();
+ final Object b = new Object();
+ final Object c = new Object();
+
+ ArrayList<Object> collection = null;
+ assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
+
+ collection = new ArrayList<>();
+ assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
+
+ collection = new ArrayList<>(Collections.singletonList(a));
+ assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Collections.singletonList(null));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(0, collection.size());
+
+ collection = new ArrayList<>(Arrays.asList(a, b));
+ assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(2, collection.size());
+ assertTrue(collection.contains(a));
+ assertTrue(collection.contains(b));
+
+ collection = new ArrayList<>(Arrays.asList(a, null));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Arrays.asList(null, a));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Arrays.asList(null, null));
+ assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(0, collection.size());
+
+ collection = new ArrayList<>(Arrays.asList(a, b, c));
+ assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(3, collection.size());
+ assertTrue(collection.contains(a));
+ assertTrue(collection.contains(b));
+ assertTrue(collection.contains(c));
+
+ collection = new ArrayList<>(Arrays.asList(a, b, null));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(2, collection.size());
+ assertTrue(collection.contains(a));
+ assertTrue(collection.contains(b));
+
+ collection = new ArrayList<>(Arrays.asList(a, null, b));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(2, collection.size());
+ assertTrue(collection.contains(a));
+ assertTrue(collection.contains(b));
+
+ collection = new ArrayList<>(Arrays.asList(null, a, b));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(2, collection.size());
+ assertTrue(collection.contains(a));
+ assertTrue(collection.contains(b));
+
+ collection = new ArrayList<>(Arrays.asList(a, null, null));
+ assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Arrays.asList(null, null, a));
+ assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Arrays.asList(null, a, null));
+ assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Arrays.asList(null, null, null));
+ assertEquals(3, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(0, collection.size());
+ }
+}
diff --git a/docs/docs-documentation-redirect.html b/docs/docs-documentation-redirect.html
index 98a265e..dbdf8b4 100644
--- a/docs/docs-documentation-redirect.html
+++ b/docs/docs-documentation-redirect.html
@@ -1,9 +1,9 @@
<html>
<head>
-<meta http-equiv="refresh" content="0;url=documentation.html">
+<meta http-equiv="refresh" content="0;url=reference/packages.html">
</head>
<body>
-<a href="documentation.html">click here if you are not redirected</a>
+<a href="reference/packages.html">click here if you are not redirected</a>
</body>
</html>
diff --git a/docs/docs-preview-index.html b/docs/docs-preview-index.html
new file mode 100644
index 0000000..e26b57c
--- /dev/null
+++ b/docs/docs-preview-index.html
@@ -0,0 +1,103 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
+<meta content="IE=edge" http-equiv="X-UA-Compatible">
+<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
+
+<title>Android N Developer Preview</title>
+
+<!-- STYLESHEETS -->
+<link rel="stylesheet"
+href="http://fonts.googleapis.com/css?family=Roboto+Condensed">
+<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Roboto:light,regular,medium,thin,italic,mediumitalic,bold"
+ title="roboto">
+<link href="assets/css/default.css?v=17" rel="stylesheet" type="text/css">
+<!-- JAVASCRIPT -->
+<script src="https://www.google.com/jsapi" type="text/javascript"></script>
+<script src="assets/js/android_3p-bundle.js" type="text/javascript"></script>
+<script type="text/javascript">
+ var toRoot = "../";
+ var metaTags = ["develop, getstarted, sdk, appquality, landing"];
+ var devsite = false;
+</script>
+<script src="assets/js/docs.js?v=3" type="text/javascript"></script>
+</head>
+
+<body>
+<div id="header-wrapper">
+ <div class="dac-header" id="header">
+ <div class="dac-header-inner">
+ <a class="dac-nav-toggle" data-dac-toggle-nav="" href="javascript:;"
+ title="Open navigation">
+ <span class="dac-nav-hamburger">
+ <span class="dac-nav-hamburger-top"></span>
+ <span class="dac-nav-hamburger-mid"></span>
+ <span class="dac-nav-hamburger-bot"></span>
+ </span>
+ </a>
+ <a class="dac-header-logo" href="index.html">
+ <img class="dac-header-logo-image" src="assets/images/android_logo.png"
+ srcset="assets/images/android_logo@2x.png 2x" width="32" height="36"
+ alt="Android"> Developers
+ </a>
+ </div>
+ </div>
+</div>
+<nav class="dac-nav">
+ <div class="dac-nav-dimmer" data-dac-toggle-nav=""></div>
+ <ul class="dac-nav-list" data-dac-nav="">
+ <li class="dac-nav-item dac-nav-head">
+ <a class="dac-nav-link dac-nav-logo" data-dac-toggle-nav=""
+ href="javascript:;" title="Close navigation">
+ <img class="dac-logo-image" src="assets/images/android_logo.png"
+ srcset="assets/images/android_logo@2x.png 2x" width="32" height="36"
+ alt="Android"> Developers
+ </a>
+ </li>
+ <li class="dac-nav-item develop">
+ <a class="dac-nav-link" href="reference/packages.html"
+ >API Reference</a>
+ </li>
+ </ul>
+</nav>
+
+<section class="dac-expand" style="padding-top:40px;background-color:#eee">
+ <div class="wrap" style="max-width:1100px;margin-top:0;height:100%">
+ <div class="cols dac-hero-content" style="padding-bottom:1em;">
+ <div class="col-11of16">
+
+
+<h1>Android N Developer Preview</h1>
+<p>
+ Get ready for Android N!
+ <strong>Test your apps</strong> on Nexus devices. Support new system
+ behaviors to <strong>save power and memory</strong>.
+ Extend your apps with <strong>multi-window UI</strong>,
+ <strong>direct reply notifications</strong> and more.
+</p>
+
+<h2>Get Started</h2>
+<ul>
+ <li>View the <a href="reference/packages.html">API Reference</a></li>
+ <li>Read Diff Reports:</a>
+ <ul>
+ <li><a href="sdk/api_diff/n-preview-1/changes.html"
+ >API 23 --> Preview 1</a></li>
+ </ul>
+ </li>
+ <li>Downloads and additional documentation are available at the
+ <a href="http://developer.android.com/preview/index.html">
+ Android N Developer Preview site</a></li>
+ <li>For information about Developer Preview 1, visit the
+ <a href="http://developer.android.com/preview/support.html">Support</a>
+ page.</li>
+</ul>
+
+
+ </div>
+ </div>
+ </div>
+</section>
+</body>
+</html>
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index ddf0528..f0c79d7 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -24,8 +24,8 @@
namespace android {
namespace uirenderer {
-
-static Rect sUnreasonablyLargeBounds(-10000, -10000, 10000, 10000);
+#define MIL_PIX 1000000
+static Rect sUnreasonablyLargeBounds(-MIL_PIX, -MIL_PIX, MIL_PIX, MIL_PIX);
static const Rect& getConservativeOpBounds(const ClipBase* clip) {
// if op is clipped, that rect can be used, but otherwise just use a conservatively large rect
@@ -564,15 +564,17 @@
getRecordedClip(),
renderNode);
int opIndex = addOp(op);
- int childIndex = mDisplayList->addChild(op);
+ if (CC_LIKELY(opIndex >= 0)) {
+ int childIndex = mDisplayList->addChild(op);
- // update the chunk's child indices
- DisplayList::Chunk& chunk = mDisplayList->chunks.back();
- chunk.endChildIndex = childIndex + 1;
+ // update the chunk's child indices
+ DisplayList::Chunk& chunk = mDisplayList->chunks.back();
+ chunk.endChildIndex = childIndex + 1;
- if (renderNode->stagingProperties().isProjectionReceiver()) {
- // use staging property, since recording on UI thread
- mDisplayList->projectionReceiveIndex = opIndex;
+ if (renderNode->stagingProperties().isProjectionReceiver()) {
+ // use staging property, since recording on UI thread
+ mDisplayList->projectionReceiveIndex = opIndex;
+ }
}
}
@@ -595,7 +597,7 @@
mDisplayList->functors.push_back(functor);
auto clip = getRecordedClip();
addOp(alloc().create_trivial<FunctorOp>(
- getConservativeOpBounds(clip), // TODO: explicitly define bounds
+ getConservativeOpBounds(clip),
*(mState.currentSnapshot()->transform),
clip,
functor));
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 4c56a22..f147fd4 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -349,6 +349,29 @@
EXPECT_EQ(1, renderer.getIndex());
}
+TEST(FrameBuilder, functor_reject) {
+ class FunctorTestRenderer : public TestRendererBase {
+ public:
+ void onFunctorOp(const FunctorOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ }
+ };
+ Functor noopFunctor;
+
+ // 1 million pixel tall view, scrolled down 80%
+ auto scrolledFunctorView = TestUtils::createNode(0, 0, 400, 1000000,
+ [&noopFunctor](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.translate(0, -800000);
+ canvas.callDrawGLFunction(&noopFunctor);
+ });
+
+ FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ TestUtils::createSyncedNodeList(scrolledFunctorView), sLightGeometry, nullptr);
+ FunctorTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex()) << "Functor should not be rejected";
+}
+
TEST(FrameBuilder, renderNode) {
class RenderNodeTestRenderer : public TestRendererBase {
public:
@@ -391,6 +414,7 @@
TestUtils::createSyncedNodeList(parent), sLightGeometry, nullptr);
RenderNodeTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex());
}
TEST(FrameBuilder, clipped) {
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index d35b1f9..c3165bb 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -451,6 +451,21 @@
EXPECT_EQ(3, count);
}
+TEST(RecordingCanvas, drawRenderNode_rejection) {
+ auto child = TestUtils::createNode(50, 50, 150, 150,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&child](RecordingCanvas& canvas) {
+ canvas.clipRect(0, 0, 0, 0, SkRegion::kIntersect_Op); // empty clip, reject node
+ canvas.drawRenderNode(child.get()); // shouldn't crash when rejecting node...
+ });
+ ASSERT_TRUE(dl->isEmpty());
+}
+
TEST(RecordingCanvas, drawRenderNode_projection) {
sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150,
[](RenderProperties& props, RecordingCanvas& canvas) {
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index a0e2481..26061e4 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -16,6 +16,10 @@
package android.media;
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
@@ -606,6 +610,16 @@
/** BT.2020 color chromacity coordinates with KR = 0.2627, KB = 0.0593. */
public static final int COLOR_STANDARD_BT2020 = 6;
+ /** @hide */
+ @IntDef({
+ COLOR_STANDARD_BT709,
+ COLOR_STANDARD_BT601_PAL,
+ COLOR_STANDARD_BT601_NTSC,
+ COLOR_STANDARD_BT2020,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ColorStandard {}
+
/**
* An optional key describing the opto-electronic transfer function used
* for the video content.
@@ -628,6 +642,16 @@
/** ARIB STD-B67 hybrid-log-gamma transfer function. This is used by some HDR video content. */
public static final int COLOR_TRANSFER_HLG = 7;
+ /** @hide */
+ @IntDef({
+ COLOR_TRANSFER_LINEAR,
+ COLOR_TRANSFER_SDR_VIDEO,
+ COLOR_TRANSFER_ST2084,
+ COLOR_TRANSFER_HLG,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ColorTransfer {}
+
/**
* An optional key describing the range of the component values of the video content.
*
@@ -644,6 +668,26 @@
/** Full range. Y, Cr and Cb component values range from 0 to 255 for 8-bit content. */
public static final int COLOR_RANGE_FULL = 1;
+ /** @hide */
+ @IntDef({
+ COLOR_RANGE_LIMITED,
+ COLOR_RANGE_FULL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ColorRange {}
+
+ /**
+ * An optional key describing the static metadata of HDR (high-dynamic-range) video content.
+ *
+ * The associated value is a ByteBuffer. This buffer contains the raw contents of the
+ * Static Metadata Descriptor (including the descriptor ID) of an HDMI Dynamic Range and
+ * Mastering InfoFrame as defined by CTA-861.3. This key must be provided to video decoders
+ * for HDR video content unless this information is contained in the bitstream and the video
+ * decoder supports an HDR-capable profile. This key must be provided to video encoders for
+ * HDR video content.
+ */
+ public static final String KEY_HDR_STATIC_INFO = "hdr-static-info";
+
/**
* A key describing a unique ID for the content of a media track.
*
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 637e06e..6f38e25 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -40,18 +40,6 @@
</activity>
<activity
- android:name=".DownloadsActivity"
- android:theme="@style/DocumentsTheme"
- android:label="@string/downloads_label"
- android:icon="@drawable/ic_doc_text">
- <intent-filter>
- <action android:name="android.provider.action.MANAGE_ROOT" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.document/root" />
- </intent-filter>
- </activity>
-
- <activity
android:name=".LauncherActivity"
android:theme="@android:style/Theme.NoDisplay"
android:icon="@drawable/ic_files_app"
@@ -72,6 +60,10 @@
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<intent-filter>
+ <action android:name="android.intent.action.VIEW_DOWNLOADS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
<action android:name="android.provider.action.BROWSE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.document/root" />
diff --git a/packages/DocumentsUI/res/values-af/strings.xml b/packages/DocumentsUI/res/values-af/strings.xml
index f01c81d..6fff804 100644
--- a/packages/DocumentsUI/res/values-af/strings.xml
+++ b/packages/DocumentsUI/res/values-af/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Vee <xliff:g id="COUNT_1">%1$d</xliff:g> lêers uit?</item>
<item quantity="one">Vee <xliff:g id="COUNT_0">%1$d</xliff:g> lêer uit?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> gekies</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> gekies</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-am/strings.xml b/packages/DocumentsUI/res/values-am/strings.xml
index cb8833b..c61db57 100644
--- a/packages/DocumentsUI/res/values-am/strings.xml
+++ b/packages/DocumentsUI/res/values-am/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> ፋይሎች ይሰረዙ?</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ፋይሎች ይሰረዙ?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> ተመርጠዋል</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ተመርጠዋል</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ar/strings.xml b/packages/DocumentsUI/res/values-ar/strings.xml
index 817f226..dcfef2a 100644
--- a/packages/DocumentsUI/res/values-ar/strings.xml
+++ b/packages/DocumentsUI/res/values-ar/strings.xml
@@ -148,5 +148,12 @@
<item quantity="other">هل تريد حذف <xliff:g id="COUNT_1">%1$d</xliff:g> ملف؟</item>
<item quantity="one">هل تريد حذف <xliff:g id="COUNT_0">%1$d</xliff:g> ملف؟</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="zero">تم تحديد <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="two">تم تحديد <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="few">تم تحديد <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="many">تم تحديد <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="other">تم تحديد <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="one">تم تحديد <xliff:g id="COUNT_0">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-az-rAZ/strings.xml b/packages/DocumentsUI/res/values-az-rAZ/strings.xml
index 2e6f04c..e1505f8 100644
--- a/packages/DocumentsUI/res/values-az-rAZ/strings.xml
+++ b/packages/DocumentsUI/res/values-az-rAZ/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"> <xliff:g id="COUNT_1">%1$d</xliff:g> fayl silinsin?</item>
<item quantity="one"> <xliff:g id="COUNT_0">%1$d</xliff:g> fayl silinsin?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> seçilib</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> seçilib</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-b+sr+Latn/strings.xml b/packages/DocumentsUI/res/values-b+sr+Latn/strings.xml
index 36e24834..b884a19 100644
--- a/packages/DocumentsUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/DocumentsUI/res/values-b+sr+Latn/strings.xml
@@ -124,5 +124,9 @@
<item quantity="few">Želite li da izbrišete <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke?</item>
<item quantity="other">Želite li da izbrišete <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one">Izabrana je <xliff:g id="COUNT_1">%1$d</xliff:g> stavka</item>
+ <item quantity="few">Izabrane su <xliff:g id="COUNT_1">%1$d</xliff:g> stavke</item>
+ <item quantity="other">Izabrano je <xliff:g id="COUNT_1">%1$d</xliff:g> stavki</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-bg/strings.xml b/packages/DocumentsUI/res/values-bg/strings.xml
index 74d18f8..94c34bd 100644
--- a/packages/DocumentsUI/res/values-bg/strings.xml
+++ b/packages/DocumentsUI/res/values-bg/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Искате ли да изтриете <xliff:g id="COUNT_1">%1$d</xliff:g> файла?</item>
<item quantity="one">Искате ли да изтриете <xliff:g id="COUNT_0">%1$d</xliff:g> файл?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other">Избрахте <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="one">Избрахте <xliff:g id="COUNT_0">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-bn-rBD/strings.xml b/packages/DocumentsUI/res/values-bn-rBD/strings.xml
index 0e3c552..2159044 100644
--- a/packages/DocumentsUI/res/values-bn-rBD/strings.xml
+++ b/packages/DocumentsUI/res/values-bn-rBD/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g>টি ফাইল মুছবেন?</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g>টি ফাইল মুছবেন?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g>টি নির্বাচন করা হয়েছে</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g>টি নির্বাচন করা হয়েছে</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-bs-rBA/strings.xml b/packages/DocumentsUI/res/values-bs-rBA/strings.xml
index ea8214e..f366ce8 100644
--- a/packages/DocumentsUI/res/values-bs-rBA/strings.xml
+++ b/packages/DocumentsUI/res/values-bs-rBA/strings.xml
@@ -124,5 +124,9 @@
<item quantity="few">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> fajla.</item>
<item quantity="other">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> fajlova.</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> stavka je odabrana</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%1$d</xliff:g> stavke su odabrane</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> stavki je odabrano</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ca/strings.xml b/packages/DocumentsUI/res/values-ca/strings.xml
index 8a35368..5af86b1 100644
--- a/packages/DocumentsUI/res/values-ca/strings.xml
+++ b/packages/DocumentsUI/res/values-ca/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Vols suprimir <xliff:g id="COUNT_1">%1$d</xliff:g> fitxers?</item>
<item quantity="one">Vols suprimir <xliff:g id="COUNT_0">%1$d</xliff:g> fitxer?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> elements seleccionats</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> element seleccionat</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-cs/strings.xml b/packages/DocumentsUI/res/values-cs/strings.xml
index 001a84b..4e8b440 100644
--- a/packages/DocumentsUI/res/values-cs/strings.xml
+++ b/packages/DocumentsUI/res/values-cs/strings.xml
@@ -132,5 +132,10 @@
<item quantity="other">Smazat <xliff:g id="COUNT_1">%1$d</xliff:g> souborů?</item>
<item quantity="one">Smazat <xliff:g id="COUNT_0">%1$d</xliff:g> soubor?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="few">Vybrány <xliff:g id="COUNT_1">%1$d</xliff:g> položky</item>
+ <item quantity="many">Vybráno <xliff:g id="COUNT_1">%1$d</xliff:g> položky</item>
+ <item quantity="other">Vybráno <xliff:g id="COUNT_1">%1$d</xliff:g> položek</item>
+ <item quantity="one">Vybrána <xliff:g id="COUNT_0">%1$d</xliff:g> položka</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-da/strings.xml b/packages/DocumentsUI/res/values-da/strings.xml
index a72cf02..ee5ba49 100644
--- a/packages/DocumentsUI/res/values-da/strings.xml
+++ b/packages/DocumentsUI/res/values-da/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">Vil du slette <xliff:g id="COUNT_1">%1$d</xliff:g> filer?</item>
<item quantity="other">Vil du slette <xliff:g id="COUNT_1">%1$d</xliff:g> filer?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one">Der er valgt <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="other">Der er valgt <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-de/strings.xml b/packages/DocumentsUI/res/values-de/strings.xml
index ed589e0..a42e955 100644
--- a/packages/DocumentsUI/res/values-de/strings.xml
+++ b/packages/DocumentsUI/res/values-de/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Möchtest du <xliff:g id="COUNT_1">%1$d</xliff:g> Dateien löschen?</item>
<item quantity="one">Möchtest du <xliff:g id="COUNT_0">%1$d</xliff:g> Datei löschen?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ausgewählt</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> ausgewählt</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-el/strings.xml b/packages/DocumentsUI/res/values-el/strings.xml
index 7a9d597..e6839de 100644
--- a/packages/DocumentsUI/res/values-el/strings.xml
+++ b/packages/DocumentsUI/res/values-el/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Να διαγραφούν <xliff:g id="COUNT_1">%1$d</xliff:g> αρχεία;</item>
<item quantity="one">Να διαγραφεί <xliff:g id="COUNT_0">%1$d</xliff:g> αρχείο;</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> επιλεγμένα</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> επιλεγμένο</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-en-rAU/strings.xml b/packages/DocumentsUI/res/values-en-rAU/strings.xml
index 4736789..8b46660 100644
--- a/packages/DocumentsUI/res/values-en-rAU/strings.xml
+++ b/packages/DocumentsUI/res/values-en-rAU/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Delete <xliff:g id="COUNT_1">%1$d</xliff:g> files?</item>
<item quantity="one">Delete <xliff:g id="COUNT_0">%1$d</xliff:g> file?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> selected</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> selected</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-en-rGB/strings.xml b/packages/DocumentsUI/res/values-en-rGB/strings.xml
index 4736789..8b46660 100644
--- a/packages/DocumentsUI/res/values-en-rGB/strings.xml
+++ b/packages/DocumentsUI/res/values-en-rGB/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Delete <xliff:g id="COUNT_1">%1$d</xliff:g> files?</item>
<item quantity="one">Delete <xliff:g id="COUNT_0">%1$d</xliff:g> file?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> selected</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> selected</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-en-rIN/strings.xml b/packages/DocumentsUI/res/values-en-rIN/strings.xml
index 4736789..8b46660 100644
--- a/packages/DocumentsUI/res/values-en-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-en-rIN/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Delete <xliff:g id="COUNT_1">%1$d</xliff:g> files?</item>
<item quantity="one">Delete <xliff:g id="COUNT_0">%1$d</xliff:g> file?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> selected</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> selected</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-es-rUS/strings.xml b/packages/DocumentsUI/res/values-es-rUS/strings.xml
index 9d525af..8fd8381 100644
--- a/packages/DocumentsUI/res/values-es-rUS/strings.xml
+++ b/packages/DocumentsUI/res/values-es-rUS/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">¿Deseas borrar <xliff:g id="COUNT_1">%1$d</xliff:g> archivos?</item>
<item quantity="one">¿Deseas borrar <xliff:g id="COUNT_0">%1$d</xliff:g> archivo?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> elementos seleccionados</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> elemento seleccionado</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-es/strings.xml b/packages/DocumentsUI/res/values-es/strings.xml
index b5d6618..81fc59abb 100644
--- a/packages/DocumentsUI/res/values-es/strings.xml
+++ b/packages/DocumentsUI/res/values-es/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">¿Eliminar <xliff:g id="COUNT_1">%1$d</xliff:g> archivos?</item>
<item quantity="one">¿Eliminar <xliff:g id="COUNT_0">%1$d</xliff:g> archivo?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> seleccionados</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> seleccionado</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-et-rEE/strings.xml b/packages/DocumentsUI/res/values-et-rEE/strings.xml
index 5f06046..7cf134e 100644
--- a/packages/DocumentsUI/res/values-et-rEE/strings.xml
+++ b/packages/DocumentsUI/res/values-et-rEE/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Kas kustutada <xliff:g id="COUNT_1">%1$d</xliff:g> faili?</item>
<item quantity="one">Kas kustutada <xliff:g id="COUNT_0">%1$d</xliff:g> fail?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> on valitud</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> on valitud</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-eu-rES/strings.xml b/packages/DocumentsUI/res/values-eu-rES/strings.xml
index 2576709..23e4079 100644
--- a/packages/DocumentsUI/res/values-eu-rES/strings.xml
+++ b/packages/DocumentsUI/res/values-eu-rES/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> fitxategi ezabatu nahi dituzu?</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> fitxategi ezabatu nahi duzu?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> hautatuta</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> hautatuta</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-fa/strings.xml b/packages/DocumentsUI/res/values-fa/strings.xml
index 312f207..6aa5626 100644
--- a/packages/DocumentsUI/res/values-fa/strings.xml
+++ b/packages/DocumentsUI/res/values-fa/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> فایل حذف شود؟</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> فایل حذف شود؟</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> مورد انتخاب شد</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> مورد انتخاب شد</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-fi/strings.xml b/packages/DocumentsUI/res/values-fi/strings.xml
index 0cb4de2..8be6d61 100644
--- a/packages/DocumentsUI/res/values-fi/strings.xml
+++ b/packages/DocumentsUI/res/values-fi/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Poistetaanko <xliff:g id="COUNT_1">%1$d</xliff:g> tiedostoa?</item>
<item quantity="one">Poistetaanko <xliff:g id="COUNT_0">%1$d</xliff:g> tiedosto?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> valittu</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> valittu</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-fr-rCA/strings.xml b/packages/DocumentsUI/res/values-fr-rCA/strings.xml
index 73f1bda..0a33570 100644
--- a/packages/DocumentsUI/res/values-fr-rCA/strings.xml
+++ b/packages/DocumentsUI/res/values-fr-rCA/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">Supprimer <xliff:g id="COUNT_1">%1$d</xliff:g> fichier?</item>
<item quantity="other">Supprimer <xliff:g id="COUNT_1">%1$d</xliff:g> fichiers?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> élément sélectionné</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> éléments sélectionnés</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-fr/strings.xml b/packages/DocumentsUI/res/values-fr/strings.xml
index b32696f..478ea46 100644
--- a/packages/DocumentsUI/res/values-fr/strings.xml
+++ b/packages/DocumentsUI/res/values-fr/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">Supprimer <xliff:g id="COUNT_1">%1$d</xliff:g> fichier ?</item>
<item quantity="other">Supprimer <xliff:g id="COUNT_1">%1$d</xliff:g> fichiers ?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> élément sélectionné</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> éléments sélectionnés</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-gl-rES/strings.xml b/packages/DocumentsUI/res/values-gl-rES/strings.xml
index d9bf7e3..c933faa 100644
--- a/packages/DocumentsUI/res/values-gl-rES/strings.xml
+++ b/packages/DocumentsUI/res/values-gl-rES/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Queres eliminar <xliff:g id="COUNT_1">%1$d</xliff:g> ficheiros?</item>
<item quantity="one">Queres eliminar <xliff:g id="COUNT_0">%1$d</xliff:g> ficheiro?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other">Seleccionáronse <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="one">Seleccionouse <xliff:g id="COUNT_0">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-gu-rIN/strings.xml b/packages/DocumentsUI/res/values-gu-rIN/strings.xml
index a1aa3d4..e9fda19 100644
--- a/packages/DocumentsUI/res/values-gu-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-gu-rIN/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> ફાઇલ કાઢી નાખીએ?</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ફાઇલ કાઢી નાખીએ?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> પસંદ કરી</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> પસંદ કરી</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-hi/strings.xml b/packages/DocumentsUI/res/values-hi/strings.xml
index 1edb19b..489bb98 100644
--- a/packages/DocumentsUI/res/values-hi/strings.xml
+++ b/packages/DocumentsUI/res/values-hi/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> फ़ाइलें हटाएं?</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> फ़ाइलें हटाएं?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> चयनित</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> चयनित</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-hr/strings.xml b/packages/DocumentsUI/res/values-hr/strings.xml
index 42ef1b0..51b8673 100644
--- a/packages/DocumentsUI/res/values-hr/strings.xml
+++ b/packages/DocumentsUI/res/values-hr/strings.xml
@@ -124,5 +124,9 @@
<item quantity="few">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke?</item>
<item quantity="other">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one">Odabrano: <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="few">Odabrano: <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="other">Odabrano: <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-hu/strings.xml b/packages/DocumentsUI/res/values-hu/strings.xml
index b9cb645..b7e74e0 100644
--- a/packages/DocumentsUI/res/values-hu/strings.xml
+++ b/packages/DocumentsUI/res/values-hu/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Töröl <xliff:g id="COUNT_1">%1$d</xliff:g> fájlt?</item>
<item quantity="one">Töröl <xliff:g id="COUNT_0">%1$d</xliff:g> fájlt?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> kiválasztva</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> kiválasztva</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-hy-rAM/strings.xml b/packages/DocumentsUI/res/values-hy-rAM/strings.xml
index c7a14c1..4dbae9d 100644
--- a/packages/DocumentsUI/res/values-hy-rAM/strings.xml
+++ b/packages/DocumentsUI/res/values-hy-rAM/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">Ջնջե՞լ <xliff:g id="COUNT_1">%1$d</xliff:g> ֆայլ:</item>
<item quantity="other">Ջնջե՞լ <xliff:g id="COUNT_1">%1$d</xliff:g> ֆայլ:</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one">Ընտրված է՝ <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="other">Ընտրված է՝ <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-in/strings.xml b/packages/DocumentsUI/res/values-in/strings.xml
index c130de1..73ed8bc 100644
--- a/packages/DocumentsUI/res/values-in/strings.xml
+++ b/packages/DocumentsUI/res/values-in/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Hapus <xliff:g id="COUNT_1">%1$d</xliff:g> file?</item>
<item quantity="one">Hapus <xliff:g id="COUNT_0">%1$d</xliff:g> file?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> dipilih</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> dipilih</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-is-rIS/strings.xml b/packages/DocumentsUI/res/values-is-rIS/strings.xml
index 1b9ff9a..75831ed 100644
--- a/packages/DocumentsUI/res/values-is-rIS/strings.xml
+++ b/packages/DocumentsUI/res/values-is-rIS/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">Eyða <xliff:g id="COUNT_1">%1$d</xliff:g> skrá?</item>
<item quantity="other">Eyða <xliff:g id="COUNT_1">%1$d</xliff:g> skrám?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> valið</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> valin</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-it/strings.xml b/packages/DocumentsUI/res/values-it/strings.xml
index c9329ad..cfd5611 100644
--- a/packages/DocumentsUI/res/values-it/strings.xml
+++ b/packages/DocumentsUI/res/values-it/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Eliminare <xliff:g id="COUNT_1">%1$d</xliff:g> file?</item>
<item quantity="one">Eliminare <xliff:g id="COUNT_0">%1$d</xliff:g> file?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> elementi selezionati</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> elemento selezionato</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-iw/strings.xml b/packages/DocumentsUI/res/values-iw/strings.xml
index 2ac5bac..7d4cb9e 100644
--- a/packages/DocumentsUI/res/values-iw/strings.xml
+++ b/packages/DocumentsUI/res/values-iw/strings.xml
@@ -132,5 +132,10 @@
<item quantity="other">האם למחוק <xliff:g id="COUNT_1">%1$d</xliff:g> קבצים?</item>
<item quantity="one">האם למחוק קובץ <xliff:g id="COUNT_0">%1$d</xliff:g>?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="two"><xliff:g id="COUNT_1">%1$d</xliff:g> נבחרו</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%1$d</xliff:g> נבחרו</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> נבחרו</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> נבחר</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ja/strings.xml b/packages/DocumentsUI/res/values-ja/strings.xml
index a79ecc5..9618d36 100644
--- a/packages/DocumentsUI/res/values-ja/strings.xml
+++ b/packages/DocumentsUI/res/values-ja/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> 個のファイルを削除しますか?</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> 個のファイルを削除しますか?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> 個を選択中</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> 個を選択中</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ka-rGE/strings.xml b/packages/DocumentsUI/res/values-ka-rGE/strings.xml
index 7824910..ac8d267 100644
--- a/packages/DocumentsUI/res/values-ka-rGE/strings.xml
+++ b/packages/DocumentsUI/res/values-ka-rGE/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">გსურთ <xliff:g id="COUNT_1">%1$d</xliff:g> ფაილის წაშლა?</item>
<item quantity="one">გსურთ <xliff:g id="COUNT_0">%1$d</xliff:g> ფაილის წაშლა?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other">არჩეულია <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="one">არჩეულია <xliff:g id="COUNT_0">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-kk-rKZ/strings.xml b/packages/DocumentsUI/res/values-kk-rKZ/strings.xml
index 5537af7..759506b 100644
--- a/packages/DocumentsUI/res/values-kk-rKZ/strings.xml
+++ b/packages/DocumentsUI/res/values-kk-rKZ/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> файлды жою керек пе?</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> файлды жою керек пе?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> таңдалды</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> таңдалды</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-km-rKH/strings.xml b/packages/DocumentsUI/res/values-km-rKH/strings.xml
index 1e1b3f8..d83c1d3 100644
--- a/packages/DocumentsUI/res/values-km-rKH/strings.xml
+++ b/packages/DocumentsUI/res/values-km-rKH/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">លុបឯកសារ <xliff:g id="COUNT_1">%1$d</xliff:g> ច្បាប់ឬ?</item>
<item quantity="one">លុបឯកសារ <xliff:g id="COUNT_0">%1$d</xliff:g> ច្បាប់ឬ?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other">បានជ្រើស <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="one">បានជ្រើស <xliff:g id="COUNT_0">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-kn-rIN/strings.xml b/packages/DocumentsUI/res/values-kn-rIN/strings.xml
index 83bed30..57ddd0b 100644
--- a/packages/DocumentsUI/res/values-kn-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-kn-rIN/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one"> <xliff:g id="COUNT_1">%1$d</xliff:g> ಫೈಲ್ಗಳನ್ನು ಅಳಿಸುವುದೇ?</item>
<item quantity="other"> <xliff:g id="COUNT_1">%1$d</xliff:g> ಫೈಲ್ಗಳನ್ನು ಅಳಿಸುವುದೇ?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ko/strings.xml b/packages/DocumentsUI/res/values-ko/strings.xml
index e313925..907802d 100644
--- a/packages/DocumentsUI/res/values-ko/strings.xml
+++ b/packages/DocumentsUI/res/values-ko/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">파일 <xliff:g id="COUNT_1">%1$d</xliff:g>개를 삭제하시겠습니까?</item>
<item quantity="one">파일 <xliff:g id="COUNT_0">%1$d</xliff:g>개를 삭제하시겠습니까?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g>개 선택됨</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g>개 선택됨</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ky-rKG/strings.xml b/packages/DocumentsUI/res/values-ky-rKG/strings.xml
index 6b8395a..699e76a 100644
--- a/packages/DocumentsUI/res/values-ky-rKG/strings.xml
+++ b/packages/DocumentsUI/res/values-ky-rKG/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> файл жок кылынсынбы?</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> файл жок кылынсынбы?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> тандалды</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> тандалды</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-lo-rLA/strings.xml b/packages/DocumentsUI/res/values-lo-rLA/strings.xml
index d9ab810..468853b 100644
--- a/packages/DocumentsUI/res/values-lo-rLA/strings.xml
+++ b/packages/DocumentsUI/res/values-lo-rLA/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">ລຶບ <xliff:g id="COUNT_1">%1$d</xliff:g> ໄຟລ໌ອອກບໍ?</item>
<item quantity="one">ລຶບ <xliff:g id="COUNT_0">%1$d</xliff:g> ໄຟລ໌ອອກບໍ?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other">ເລືອກ <xliff:g id="COUNT_1">%1$d</xliff:g> ແລ້ວ</item>
+ <item quantity="one">ເລືອກ <xliff:g id="COUNT_0">%1$d</xliff:g> ແລ້ວ</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-lt/strings.xml b/packages/DocumentsUI/res/values-lt/strings.xml
index 91f5948..a6297be 100644
--- a/packages/DocumentsUI/res/values-lt/strings.xml
+++ b/packages/DocumentsUI/res/values-lt/strings.xml
@@ -132,5 +132,10 @@
<item quantity="many">Ištrinti <xliff:g id="COUNT_1">%1$d</xliff:g> failo?</item>
<item quantity="other">Ištrinti <xliff:g id="COUNT_1">%1$d</xliff:g> failų?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one">Pasirinktas <xliff:g id="COUNT_1">%1$d</xliff:g> elementas</item>
+ <item quantity="few">Pasirinkti <xliff:g id="COUNT_1">%1$d</xliff:g> elementai</item>
+ <item quantity="many">Pasirinkta <xliff:g id="COUNT_1">%1$d</xliff:g> elemento</item>
+ <item quantity="other">Pasirinkta <xliff:g id="COUNT_1">%1$d</xliff:g> elementų</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-lv/strings.xml b/packages/DocumentsUI/res/values-lv/strings.xml
index d3154d1..1a9b77c0 100644
--- a/packages/DocumentsUI/res/values-lv/strings.xml
+++ b/packages/DocumentsUI/res/values-lv/strings.xml
@@ -124,5 +124,9 @@
<item quantity="one">Vai izdzēst <xliff:g id="COUNT_1">%1$d</xliff:g> failu?</item>
<item quantity="other">Vai izdzēst <xliff:g id="COUNT_1">%1$d</xliff:g> failus?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="zero"><xliff:g id="COUNT_1">%1$d</xliff:g> atlasīti</item>
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> atlasīts</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> atlasīti</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-mk-rMK/strings.xml b/packages/DocumentsUI/res/values-mk-rMK/strings.xml
index f7486f1..5bbf8c5 100644
--- a/packages/DocumentsUI/res/values-mk-rMK/strings.xml
+++ b/packages/DocumentsUI/res/values-mk-rMK/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">Да се избрише <xliff:g id="COUNT_1">%1$d</xliff:g> датотека?</item>
<item quantity="other">Да се избришат <xliff:g id="COUNT_1">%1$d</xliff:g> датотеки?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> е избрана</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> се избрани</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ml-rIN/strings.xml b/packages/DocumentsUI/res/values-ml-rIN/strings.xml
index e164ad2..264d196 100644
--- a/packages/DocumentsUI/res/values-ml-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-ml-rIN/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ഫയലുകൾ ഇല്ലാതാക്കണോ?</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> ഫയൽ ഇല്ലാതാക്കണോ?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> തിരഞ്ഞെടുത്തു</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> തിരഞ്ഞെടുത്തു</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-mn-rMN/strings.xml b/packages/DocumentsUI/res/values-mn-rMN/strings.xml
index fae1f04..02c818b 100644
--- a/packages/DocumentsUI/res/values-mn-rMN/strings.xml
+++ b/packages/DocumentsUI/res/values-mn-rMN/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"> <xliff:g id="COUNT_1">%1$d</xliff:g> файлыг устгах уу?</item>
<item quantity="one"> <xliff:g id="COUNT_0">%1$d</xliff:g> файлыг устгах уу?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> сонгосон</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> сонгосон</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-mr-rIN/strings.xml b/packages/DocumentsUI/res/values-mr-rIN/strings.xml
index 0def32f..6fae578 100644
--- a/packages/DocumentsUI/res/values-mr-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-mr-rIN/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one"> <xliff:g id="COUNT_1">%1$d</xliff:g> फाईल हटवायची?</item>
<item quantity="other"> <xliff:g id="COUNT_1">%1$d</xliff:g> फायली हटवायच्या?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> निवडला</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> निवडले</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ms-rMY/strings.xml b/packages/DocumentsUI/res/values-ms-rMY/strings.xml
index ef62ec4..6f7c525 100644
--- a/packages/DocumentsUI/res/values-ms-rMY/strings.xml
+++ b/packages/DocumentsUI/res/values-ms-rMY/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Padamkan <xliff:g id="COUNT_1">%1$d</xliff:g> fail?</item>
<item quantity="one">Padamkan <xliff:g id="COUNT_0">%1$d</xliff:g> fail?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> dipilih</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> dipilih</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-my-rMM/strings.xml b/packages/DocumentsUI/res/values-my-rMM/strings.xml
index 08bdfc1..9fb7f84 100644
--- a/packages/DocumentsUI/res/values-my-rMM/strings.xml
+++ b/packages/DocumentsUI/res/values-my-rMM/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">ဖိုင် <xliff:g id="COUNT_1">%1$d</xliff:g> ခုကိုဖျက်မလား။</item>
<item quantity="one">ဖိုင် <xliff:g id="COUNT_0">%1$d</xliff:g> ခုကိုဖျက်မလား။</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ခုရွေးချယ်ထားသည်</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> ခုရွေးချယ်ထားသည်</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-nb/strings.xml b/packages/DocumentsUI/res/values-nb/strings.xml
index 3326a55..0e48f0f 100644
--- a/packages/DocumentsUI/res/values-nb/strings.xml
+++ b/packages/DocumentsUI/res/values-nb/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Vil du slette <xliff:g id="COUNT_1">%1$d</xliff:g> filer?</item>
<item quantity="one">Vil du slette <xliff:g id="COUNT_0">%1$d</xliff:g> fil?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> er valgt</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> er valgt</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ne-rNP/strings.xml b/packages/DocumentsUI/res/values-ne-rNP/strings.xml
index 191574c..cc70c91 100644
--- a/packages/DocumentsUI/res/values-ne-rNP/strings.xml
+++ b/packages/DocumentsUI/res/values-ne-rNP/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> फाइलहरूलाई मेट्ने हो?</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> फाइललाई मेट्ने हो?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> लाई चयन गरियो</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> लाई चयन गरियो</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-nl/strings.xml b/packages/DocumentsUI/res/values-nl/strings.xml
index 9a7ad50..ebddf54 100644
--- a/packages/DocumentsUI/res/values-nl/strings.xml
+++ b/packages/DocumentsUI/res/values-nl/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> bestanden verwijderen?</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> bestand verwijderen?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> geselecteerd</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> geselecteerd</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-pa-rIN/strings.xml b/packages/DocumentsUI/res/values-pa-rIN/strings.xml
index 523085d..a8c65e7 100644
--- a/packages/DocumentsUI/res/values-pa-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-pa-rIN/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">ਕੀ <xliff:g id="COUNT_1">%1$d</xliff:g> ਫ਼ਾਈਲਾਂ ਨੂੰ ਮਿਟਾਉਣਾ ਹੈ?</item>
<item quantity="other">ਕੀ <xliff:g id="COUNT_1">%1$d</xliff:g> ਫ਼ਾਈਲਾਂ ਨੂੰ ਮਿਟਾਉਣਾ ਹੈ?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> ਚੁਣੀ ਗਈ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ਚੁਣੀਆਂ ਗਈਆਂ</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-pl/strings.xml b/packages/DocumentsUI/res/values-pl/strings.xml
index 60dbda8..e888fd7 100644
--- a/packages/DocumentsUI/res/values-pl/strings.xml
+++ b/packages/DocumentsUI/res/values-pl/strings.xml
@@ -132,5 +132,10 @@
<item quantity="other">Usunąć <xliff:g id="COUNT_1">%1$d</xliff:g> pliku?</item>
<item quantity="one">Usunąć <xliff:g id="COUNT_0">%1$d</xliff:g> plik?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="few">Wybrano <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="many">Wybrano <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="other">Wybrano <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="one">Wybrano <xliff:g id="COUNT_0">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-pt-rBR/strings.xml b/packages/DocumentsUI/res/values-pt-rBR/strings.xml
index 92939ba1..213e76a 100644
--- a/packages/DocumentsUI/res/values-pt-rBR/strings.xml
+++ b/packages/DocumentsUI/res/values-pt-rBR/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">Excluir <xliff:g id="COUNT_1">%1$d</xliff:g> arquivos?</item>
<item quantity="other">Excluir <xliff:g id="COUNT_1">%1$d</xliff:g> arquivos?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> selecionados</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> selecionados</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-pt-rPT/strings.xml b/packages/DocumentsUI/res/values-pt-rPT/strings.xml
index 6b3d23c..2c74e67 100644
--- a/packages/DocumentsUI/res/values-pt-rPT/strings.xml
+++ b/packages/DocumentsUI/res/values-pt-rPT/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Pretende eliminar <xliff:g id="COUNT_1">%1$d</xliff:g> ficheiros?</item>
<item quantity="one">Pretende eliminar <xliff:g id="COUNT_0">%1$d</xliff:g> ficheiro?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> selecionados</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> selecionado</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-pt/strings.xml b/packages/DocumentsUI/res/values-pt/strings.xml
index 92939ba1..213e76a 100644
--- a/packages/DocumentsUI/res/values-pt/strings.xml
+++ b/packages/DocumentsUI/res/values-pt/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">Excluir <xliff:g id="COUNT_1">%1$d</xliff:g> arquivos?</item>
<item quantity="other">Excluir <xliff:g id="COUNT_1">%1$d</xliff:g> arquivos?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> selecionados</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> selecionados</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ro/strings.xml b/packages/DocumentsUI/res/values-ro/strings.xml
index eda2eac..e57d1ab 100644
--- a/packages/DocumentsUI/res/values-ro/strings.xml
+++ b/packages/DocumentsUI/res/values-ro/strings.xml
@@ -124,5 +124,9 @@
<item quantity="other">Ștergeți <xliff:g id="COUNT_1">%1$d</xliff:g> de fișiere?</item>
<item quantity="one">Ștergeți <xliff:g id="COUNT_0">%1$d</xliff:g> fișier?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="few"><xliff:g id="COUNT_1">%1$d</xliff:g> selectate</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> selectate</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> selectat</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ru/strings.xml b/packages/DocumentsUI/res/values-ru/strings.xml
index 5719fe8..9c0f031 100644
--- a/packages/DocumentsUI/res/values-ru/strings.xml
+++ b/packages/DocumentsUI/res/values-ru/strings.xml
@@ -132,5 +132,10 @@
<item quantity="many">Удалить <xliff:g id="COUNT_1">%1$d</xliff:g> файлов?</item>
<item quantity="other">Удалить <xliff:g id="COUNT_1">%1$d</xliff:g> файла?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one">Выбрано: <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="few">Выбрано: <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="many">Выбрано: <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="other">Выбрано: <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-si-rLK/strings.xml b/packages/DocumentsUI/res/values-si-rLK/strings.xml
index 7c2e3a2..a34aa88 100644
--- a/packages/DocumentsUI/res/values-si-rLK/strings.xml
+++ b/packages/DocumentsUI/res/values-si-rLK/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">ගොනු <xliff:g id="COUNT_1">%1$d</xliff:g>ක් මකන්නද?</item>
<item quantity="other">ගොනු <xliff:g id="COUNT_1">%1$d</xliff:g>ක් මකන්නද?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g>ක් තෝරන ලදී</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g>ක් තෝරන ලදී</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-sk/strings.xml b/packages/DocumentsUI/res/values-sk/strings.xml
index 818c276..1133815 100644
--- a/packages/DocumentsUI/res/values-sk/strings.xml
+++ b/packages/DocumentsUI/res/values-sk/strings.xml
@@ -132,5 +132,10 @@
<item quantity="other">Odstrániť <xliff:g id="COUNT_1">%1$d</xliff:g> súborov?</item>
<item quantity="one">Odstrániť <xliff:g id="COUNT_0">%1$d</xliff:g> súbor?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="few">Vybraté: <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="many">Vybraté: <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="other">Vybraté: <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="one">Vybraté: <xliff:g id="COUNT_0">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-sl/strings.xml b/packages/DocumentsUI/res/values-sl/strings.xml
index 1994f07..f2c691d 100644
--- a/packages/DocumentsUI/res/values-sl/strings.xml
+++ b/packages/DocumentsUI/res/values-sl/strings.xml
@@ -132,5 +132,10 @@
<item quantity="few">Želite izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke?</item>
<item quantity="other">Želite izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> datotek?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> izbran</item>
+ <item quantity="two"><xliff:g id="COUNT_1">%1$d</xliff:g> izbrana</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%1$d</xliff:g> izbrani</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> izbranih</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-sq-rAL/strings.xml b/packages/DocumentsUI/res/values-sq-rAL/strings.xml
index 671b5ea..80ae000 100644
--- a/packages/DocumentsUI/res/values-sq-rAL/strings.xml
+++ b/packages/DocumentsUI/res/values-sq-rAL/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Të fshihen <xliff:g id="COUNT_1">%1$d</xliff:g> skedarë?</item>
<item quantity="one">Të fshihet <xliff:g id="COUNT_0">%1$d</xliff:g> skedar?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> të zgjedhur</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> i zgjedhur</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-sr/strings.xml b/packages/DocumentsUI/res/values-sr/strings.xml
index de45e61..be96db6 100644
--- a/packages/DocumentsUI/res/values-sr/strings.xml
+++ b/packages/DocumentsUI/res/values-sr/strings.xml
@@ -124,5 +124,9 @@
<item quantity="few">Желите ли да избришете <xliff:g id="COUNT_1">%1$d</xliff:g> датотеке?</item>
<item quantity="other">Желите ли да избришете <xliff:g id="COUNT_1">%1$d</xliff:g> датотека?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one">Изабрана је <xliff:g id="COUNT_1">%1$d</xliff:g> ставка</item>
+ <item quantity="few">Изабране су <xliff:g id="COUNT_1">%1$d</xliff:g> ставке</item>
+ <item quantity="other">Изабрано је <xliff:g id="COUNT_1">%1$d</xliff:g> ставки</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-sv/strings.xml b/packages/DocumentsUI/res/values-sv/strings.xml
index 42bc405..7fd4be0 100644
--- a/packages/DocumentsUI/res/values-sv/strings.xml
+++ b/packages/DocumentsUI/res/values-sv/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Radera <xliff:g id="COUNT_1">%1$d</xliff:g> filer?</item>
<item quantity="one">Radera <xliff:g id="COUNT_0">%1$d</xliff:g> fil?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> har valts</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> har valts</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-sw/strings.xml b/packages/DocumentsUI/res/values-sw/strings.xml
index ce042f0..2730ce9 100644
--- a/packages/DocumentsUI/res/values-sw/strings.xml
+++ b/packages/DocumentsUI/res/values-sw/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Ungependa kufuta faili <xliff:g id="COUNT_1">%1$d</xliff:g>?</item>
<item quantity="one">Ungependa kufuta faili <xliff:g id="COUNT_0">%1$d</xliff:g>?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other">Imechagua <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="one">Imechagua <xliff:g id="COUNT_0">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ta-rIN/strings.xml b/packages/DocumentsUI/res/values-ta-rIN/strings.xml
index 5bd71ae..881e05a 100644
--- a/packages/DocumentsUI/res/values-ta-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-ta-rIN/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> கோப்புகளை நீக்கவா?</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> கோப்பை நீக்கவா?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> தேர்ந்தெடுக்கப்பட்டன</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> தேர்ந்தெடுக்கப்பட்டது</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-te-rIN/strings.xml b/packages/DocumentsUI/res/values-te-rIN/strings.xml
index 0b0f04b..0043ddc 100644
--- a/packages/DocumentsUI/res/values-te-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-te-rIN/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ఫైల్లను తొలగించాలా?</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> ఫైల్ను తొలగించాలా?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ఎంచుకోబడ్డాయి</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> ఎంచుకోబడింది</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-th/strings.xml b/packages/DocumentsUI/res/values-th/strings.xml
index fbc170e..8b24210 100644
--- a/packages/DocumentsUI/res/values-th/strings.xml
+++ b/packages/DocumentsUI/res/values-th/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">ลบ <xliff:g id="COUNT_1">%1$d</xliff:g> ไฟล์ใช่ไหม</item>
<item quantity="one">ลบ <xliff:g id="COUNT_0">%1$d</xliff:g> ไฟล์ใช่ไหม</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other">เลือกไว้ <xliff:g id="COUNT_1">%1$d</xliff:g> รายการ</item>
+ <item quantity="one">เลือกไว้ <xliff:g id="COUNT_0">%1$d</xliff:g> รายการ</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-tl/strings.xml b/packages/DocumentsUI/res/values-tl/strings.xml
index 8a4f148..9849b85 100644
--- a/packages/DocumentsUI/res/values-tl/strings.xml
+++ b/packages/DocumentsUI/res/values-tl/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">Gusto mo bang i-delete ang <xliff:g id="COUNT_1">%1$d</xliff:g> file?</item>
<item quantity="other">Gusto mo bang i-delete ang <xliff:g id="COUNT_1">%1$d</xliff:g> na file?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> ang napili</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ang napili</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-tr/strings.xml b/packages/DocumentsUI/res/values-tr/strings.xml
index ceb5c48..8df52d2 100644
--- a/packages/DocumentsUI/res/values-tr/strings.xml
+++ b/packages/DocumentsUI/res/values-tr/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> dosya silinsin mi?</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> dosya silinsin mi?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> öğe seçildi</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> öğe seçildi</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-uk/strings.xml b/packages/DocumentsUI/res/values-uk/strings.xml
index 8003c16..d5a1fcd 100644
--- a/packages/DocumentsUI/res/values-uk/strings.xml
+++ b/packages/DocumentsUI/res/values-uk/strings.xml
@@ -132,5 +132,10 @@
<item quantity="many">Видалити <xliff:g id="COUNT_1">%1$d</xliff:g> файлів?</item>
<item quantity="other">Видалити <xliff:g id="COUNT_1">%1$d</xliff:g> файлу?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one">Вибрано <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="few">Вибрано <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="many">Вибрано <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="other">Вибрано <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-ur-rPK/strings.xml b/packages/DocumentsUI/res/values-ur-rPK/strings.xml
index b96a96f..846555a 100644
--- a/packages/DocumentsUI/res/values-ur-rPK/strings.xml
+++ b/packages/DocumentsUI/res/values-ur-rPK/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> فائلیں حذف کریں؟</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> فائل حذف کریں؟</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> منتخب کردہ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> منتخب کردہ</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-uz-rUZ/strings.xml b/packages/DocumentsUI/res/values-uz-rUZ/strings.xml
index 72cc7d9..0e1ac5a 100644
--- a/packages/DocumentsUI/res/values-uz-rUZ/strings.xml
+++ b/packages/DocumentsUI/res/values-uz-rUZ/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ta fayl o‘chirilsinmi?</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> ta fayl o‘chirilsinmi?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> ta tanlandi</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%1$d</xliff:g> ta tanlandi</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-vi/strings.xml b/packages/DocumentsUI/res/values-vi/strings.xml
index adb3ffe..c3c16f4 100644
--- a/packages/DocumentsUI/res/values-vi/strings.xml
+++ b/packages/DocumentsUI/res/values-vi/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">Xóa <xliff:g id="COUNT_1">%1$d</xliff:g> tệp?</item>
<item quantity="one">Xóa <xliff:g id="COUNT_0">%1$d</xliff:g> tệp?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other">Đã chọn <xliff:g id="COUNT_1">%1$d</xliff:g></item>
+ <item quantity="one">Đã chọn <xliff:g id="COUNT_0">%1$d</xliff:g></item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-zh-rCN/strings.xml b/packages/DocumentsUI/res/values-zh-rCN/strings.xml
index ccf5d42..7f479ff 100644
--- a/packages/DocumentsUI/res/values-zh-rCN/strings.xml
+++ b/packages/DocumentsUI/res/values-zh-rCN/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">删除 <xliff:g id="COUNT_1">%1$d</xliff:g> 个文件?</item>
<item quantity="one">删除 <xliff:g id="COUNT_0">%1$d</xliff:g> 个文件?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other">已选择 <xliff:g id="COUNT_1">%1$d</xliff:g> 项</item>
+ <item quantity="one">已选择 <xliff:g id="COUNT_0">%1$d</xliff:g> 项</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-zh-rHK/strings.xml b/packages/DocumentsUI/res/values-zh-rHK/strings.xml
index ecd0071..f22e27e 100644
--- a/packages/DocumentsUI/res/values-zh-rHK/strings.xml
+++ b/packages/DocumentsUI/res/values-zh-rHK/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">要刪除 <xliff:g id="COUNT_1">%1$d</xliff:g> 個檔案嗎?</item>
<item quantity="one">要刪除 <xliff:g id="COUNT_0">%1$d</xliff:g> 個檔案嗎?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other">已選取 <xliff:g id="COUNT_1">%1$d</xliff:g> 個項目</item>
+ <item quantity="one">已選取 <xliff:g id="COUNT_0">%1$d</xliff:g> 個項目</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-zh-rTW/strings.xml b/packages/DocumentsUI/res/values-zh-rTW/strings.xml
index ea83ab2..a5ede47 100644
--- a/packages/DocumentsUI/res/values-zh-rTW/strings.xml
+++ b/packages/DocumentsUI/res/values-zh-rTW/strings.xml
@@ -116,5 +116,8 @@
<item quantity="other">要刪除 <xliff:g id="COUNT_1">%1$d</xliff:g> 個檔案嗎?</item>
<item quantity="one">要刪除 <xliff:g id="COUNT_0">%1$d</xliff:g> 個檔案嗎?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="other">已選取 <xliff:g id="COUNT_1">%1$d</xliff:g> 個項目</item>
+ <item quantity="one">已選取 <xliff:g id="COUNT_0">%1$d</xliff:g> 個項目</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/res/values-zu/strings.xml b/packages/DocumentsUI/res/values-zu/strings.xml
index 4588b12..b99ab8c 100644
--- a/packages/DocumentsUI/res/values-zu/strings.xml
+++ b/packages/DocumentsUI/res/values-zu/strings.xml
@@ -116,5 +116,8 @@
<item quantity="one">Sula amafayela angu-<xliff:g id="COUNT_1">%1$d</xliff:g>?</item>
<item quantity="other">Sula amafayela angu-<xliff:g id="COUNT_1">%1$d</xliff:g>?</item>
</plurals>
- <!-- no translation found for elements_selected (1376955402452875047) -->
+ <plurals name="elements_selected" formatted="false" msgid="1376955402452875047">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> okukhethiwe</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> okukhethiwe</item>
+ </plurals>
</resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 12a4186..f8b32a0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -95,7 +95,7 @@
}
if (mState.restored) {
- refreshCurrentRootAndDirectory(ANIM_NONE);
+ if (DEBUG) Log.d(TAG, "Stack already resolved");
} else {
// We set the activity title in AsyncTask.onPostExecute().
// To prevent talkback from reading aloud the default title, we clear it here.
@@ -108,9 +108,7 @@
// we restore the stack as last used from that app.
if (mState.action == ACTION_PICK_COPY_DESTINATION) {
if (DEBUG) Log.d(TAG, "Launching directly into Home directory.");
- Uri homeUri = DocumentsContract.buildHomeUri();
- new LoadRootTask(this, homeUri).executeOnExecutor(
- ProviderExecutor.forAuthority(homeUri.getAuthority()));
+ loadRoot(DocumentsContract.buildHomeUri());
} else {
if (DEBUG) Log.d(TAG, "Attempting to load last used stack for calling package.");
new LoadLastUsedStackTask(this).execute();
@@ -156,30 +154,6 @@
}
}
- private void onStackRestored(boolean restored, boolean external) {
- // Show drawer when no stack restored, but only when requesting
- // non-visual content. However, if we last used an external app,
- // drawer is always shown.
-
- boolean showDrawer = false;
- if (!restored) {
- showDrawer = true;
- }
- if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
- showDrawer = false;
- }
- if (external && mState.action == ACTION_GET_CONTENT) {
- showDrawer = true;
- }
- if (mState.action == ACTION_PICK_COPY_DESTINATION) {
- showDrawer = true;
- }
-
- if (showDrawer) {
- mNavigator.revealRootsDrawer(true);
- }
- }
-
public void onAppPicked(ResolveInfo info) {
final Intent intent = new Intent(getIntent());
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
@@ -517,8 +491,8 @@
@Override
protected void finish(Void result) {
mState.restored = true;
+ mState.external = mExternal;
mOwner.refreshCurrentRootAndDirectory(ANIM_NONE);
- mOwner.onStackRestored(mRestoredStack, mExternal);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
deleted file mode 100644
index 2f784cb..0000000
--- a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.documentsui;
-
-import static com.android.documentsui.State.ACTION_MANAGE;
-import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.content.ActivityNotFoundException;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.DocumentsContract;
-import android.support.design.widget.Snackbar;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.widget.Toolbar;
-
-import com.android.documentsui.dirlist.DirectoryFragment;
-import com.android.documentsui.dirlist.Model;
-import com.android.documentsui.model.DocumentInfo;
-import com.android.documentsui.model.RootInfo;
-
-import java.util.Arrays;
-import java.util.List;
-
-// Let's face it. MANAGE_ROOT is used almost exclusively
-// for downloads, and is specialized for this purpose.
-// So it is now thusly christened.
-public class DownloadsActivity extends BaseActivity {
- private static final String TAG = "DownloadsActivity";
-
- public DownloadsActivity() {
- super(R.layout.downloads_activity, TAG);
- }
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- final Context context = this;
-
- Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
- toolbar.setTitleTextAppearance(context,
- android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
-
- if (!mState.restored) {
- // In this case, we set the activity title in AsyncTask.onPostExecute(). To prevent
- // talkback from reading aloud the default title, we clear it here.
- setTitle("");
- final Uri rootUri = getIntent().getData();
- new LoadRootTask(this, rootUri).executeOnExecutor(getExecutorForCurrentDirectory());
- } else {
- refreshCurrentRootAndDirectory(ANIM_NONE);
- }
- }
-
- @Override
- void includeState(State state) {
- state.action = ACTION_MANAGE;
- state.acceptMimes = new String[] { "*/*" };
- state.allowMultiple = true;
- state.showSize = true;
- state.excludedAuthorities = getExcludedAuthorities();
- }
-
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- mNavigator.update();
- }
-
- @Override
- public String getDrawerTitle() {
- return null; // being and nothingness
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
-
- final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
- final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
- final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
-
- createDir.setVisible(false);
- pasteFromCb.setEnabled(false);
- fileSize.setVisible(false);
-
- Menus.disableHiddenItems(menu);
- return true;
- }
-
- @Override
- void refreshDirectory(int anim) {
- final FragmentManager fm = getFragmentManager();
- final RootInfo root = getCurrentRoot();
- final DocumentInfo cwd = getCurrentDirectory();
-
- assert(!mSearchManager.isSearching());
-
- // If started in manage roots mode, there has to be a cwd (i.e. the root dir of the managed
- // root).
- assert(cwd != null);
-
- // Normal boring directory
- DirectoryFragment.showDirectory(fm, root, cwd, anim);
- }
-
- @Override
- public void onDocumentPicked(DocumentInfo doc, Model model) {
- assert(!doc.isDirectory());
-
- // First try managing the document; we expect manager to filter
- // based on authority, so we don't grant.
- final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
- manage.setData(doc.derivedUri);
-
- try {
- startActivity(manage);
- } catch (ActivityNotFoundException ex) {
- // Fall back to viewing.
- final Intent view = new Intent(Intent.ACTION_VIEW);
- view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- view.setData(doc.derivedUri);
-
- try {
- startActivity(view);
- } catch (ActivityNotFoundException ex2) {
- Snackbars.makeSnackbar(this, R.string.toast_no_application, Snackbar.LENGTH_SHORT)
- .show();
- }
- }
- }
-
- @Override
- public void onDocumentsPicked(List<DocumentInfo> docs) {}
-
- @Override
- void onTaskFinished(Uri... uris) {
- Log.d(TAG, "onFinished() " + Arrays.toString(uris));
-
- final Intent intent = new Intent();
- if (uris.length == 1) {
- intent.setData(uris[0]);
- } else if (uris.length > 1) {
- final ClipData clipData = new ClipData(
- null, mState.acceptMimes, new ClipData.Item(uris[0]));
- for (int i = 1; i < uris.length; i++) {
- clipData.addItem(new ClipData.Item(uris[i]));
- }
- intent.setClipData(clipData);
- }
-
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
-
- setResult(Activity.RESULT_OK, intent);
- finish();
- }
-
- public static DownloadsActivity get(Fragment fragment) {
- return (DownloadsActivity) fragment.getActivity();
- }
-}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index c56a12f..573e4f3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -108,10 +108,10 @@
// authority. That way a misbehaving provider won't result in an ANR.
loadRoot(uri);
} else {
- if (DEBUG) Log.d(TAG, "Launching into Home directory.");
- // If all else fails, try to load "Home" directory.
- final Uri homeUri = DocumentsContract.buildHomeUri();
- loadRoot(homeUri);
+ if (DEBUG) Log.d(TAG, "All other means skipped. Launching into default directory.");
+ Uri defaultUri = DocumentsContract.buildRootUri(
+ "com.android.providers.downloads.documents", "downloads");
+ loadRoot(defaultUri);
}
final @DialogType int dialogType = intent.getIntExtra(
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
index dcaea15..afd308c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
@@ -18,6 +18,7 @@
import static android.os.Environment.STANDARD_DIRECTORIES;
import static com.android.documentsui.Shared.DEBUG;
+
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.StringDef;
@@ -58,7 +59,7 @@
private static final String COUNT_CREATE_MIME = "docsui_create_mime";
private static final String COUNT_GET_CONTENT_MIME = "docsui_get_content_mime";
private static final String COUNT_BROWSE_ROOT = "docsui_browse_root";
- private static final String COUNT_MANAGE_ROOT = "docsui_manage_root";
+ @Deprecated private static final String COUNT_MANAGE_ROOT = "docsui_manage_root";
private static final String COUNT_MULTI_WINDOW = "docsui_multi_window";
private static final String COUNT_FILEOP_SYSTEM = "docsui_fileop_system";
private static final String COUNT_FILEOP_EXTERNAL = "docsui_fileop_external";
@@ -194,7 +195,7 @@
private static final int ACTION_CREATE = 3;
private static final int ACTION_GET_CONTENT = 4;
private static final int ACTION_OPEN_TREE = 5;
- private static final int ACTION_MANAGE = 6;
+ @Deprecated private static final int ACTION_MANAGE = 6;
private static final int ACTION_BROWSE = 7;
private static final int ACTION_PICK_COPY_DESTINATION = 8;
@@ -246,9 +247,6 @@
case State.ACTION_GET_CONTENT:
logHistogram(context, COUNT_GET_CONTENT_MIME, sanitizeMime(intent.getType()));
break;
- case State.ACTION_MANAGE:
- logHistogram(context, COUNT_MANAGE_ROOT, sanitizeRoot(uri));
- break;
case State.ACTION_BROWSE:
logHistogram(context, COUNT_BROWSE_ROOT, sanitizeRoot(uri));
break;
@@ -641,8 +639,6 @@
return ACTION_GET_CONTENT;
case State.ACTION_OPEN_TREE:
return ACTION_OPEN_TREE;
- case State.ACTION_MANAGE:
- return ACTION_MANAGE;
case State.ACTION_BROWSE:
return ACTION_BROWSE;
case State.ACTION_PICK_COPY_DESTINATION:
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 4f460b4..16b7660 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -43,13 +43,15 @@
private static final String TAG = "State";
- public static final int ACTION_OPEN = 1;
- public static final int ACTION_CREATE = 2;
- public static final int ACTION_GET_CONTENT = 3;
- public static final int ACTION_OPEN_TREE = 4;
- public static final int ACTION_MANAGE = 5;
- public static final int ACTION_BROWSE = 6;
- public static final int ACTION_PICK_COPY_DESTINATION = 8;
+ // File manager and related private picking activity.
+ public static final int ACTION_BROWSE = 1;
+ public static final int ACTION_PICK_COPY_DESTINATION = 2;
+
+ // All public picking activities
+ public static final int ACTION_OPEN = 3;
+ public static final int ACTION_CREATE = 4;
+ public static final int ACTION_GET_CONTENT = 5;
+ public static final int ACTION_OPEN_TREE = 6;
@IntDef(flag = true, value = {
MODE_UNKNOWN,
@@ -83,6 +85,10 @@
public boolean showSize;
public boolean localOnly;
public boolean restored;
+ /*
+ * Indicates handler was an external app, like photos.
+ */
+ public boolean external;
// Indicates that a copy operation (or move) includes a directory.
// Why? Directory creation isn't supported by some roots (like Downloads).
@@ -180,6 +186,7 @@
out.writeInt(showSize ? 1 : 0);
out.writeInt(localOnly ? 1 : 0);
out.writeInt(restored ? 1 : 0);
+ out.writeInt(external ? 1 : 0);
DurableUtils.writeToParcel(out, stack);
out.writeMap(dirState);
out.writeParcelable(selectedDocuments, 0);
@@ -208,6 +215,7 @@
state.showSize = in.readInt() != 0;
state.localOnly = in.readInt() != 0;
state.restored = in.readInt() != 0;
+ state.external = in.readInt() != 0;
DurableUtils.readFromParcel(in, state.stack);
in.readMap(state.dirState, loader);
state.selectedDocuments = in.readParcelable(loader);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index ca1b444..461bade 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -281,16 +281,6 @@
mTuner = FragmentTuner.pick(getContext(), state);
mClipper = new DocumentClipper(context);
- boolean hideGridTitles;
- if (mType == TYPE_RECENT_OPEN) {
- // Hide titles when showing recents for picking images/videos
- hideGridTitles = MimePredicate.mimeMatches(
- MimePredicate.VISUAL_MIMES, state.acceptMimes);
- } else {
- hideGridTitles = (mDocument != null) && mDocument.isGridTitlesHidden();
- }
- GridDocumentHolder.setHideTitles(hideGridTitles);
-
final ActivityManager am = (ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE);
boolean svelte = am.isLowRamDevice() && (mType == TYPE_RECENT_OPEN);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index adaa850..0ee7623 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -19,9 +19,9 @@
import static com.android.documentsui.State.ACTION_BROWSE;
import static com.android.documentsui.State.ACTION_CREATE;
import static com.android.documentsui.State.ACTION_GET_CONTENT;
-import static com.android.documentsui.State.ACTION_MANAGE;
import static com.android.documentsui.State.ACTION_OPEN;
import static com.android.documentsui.State.ACTION_OPEN_TREE;
+import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION;
import android.content.Context;
import android.provider.DocumentsContract.Document;
@@ -53,8 +53,6 @@
switch (state.action) {
case ACTION_BROWSE:
return new FilesTuner(context, state);
- case ACTION_MANAGE:
- return new DownloadsTuner(context, state);
default:
return new DocumentsTuner(context, state);
}
@@ -157,8 +155,27 @@
@Override
void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {
+ boolean showDrawer = false;
+
+ if (mState.restored) {
+ showDrawer = true;
+ }
+ if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
+ showDrawer = false;
+ }
+ if (mState.external && mState.action == ACTION_GET_CONTENT) {
+ showDrawer = true;
+ }
+ if (mState.action == ACTION_PICK_COPY_DESTINATION) {
+ showDrawer = true;
+ }
+
// When launched into empty root, open drawer.
- if (model.isEmpty() && !mState.hasInitialLocationChanged() && !isSearch) {
+ if (model.isEmpty()) {
+ showDrawer = true;
+ }
+
+ if (showDrawer && !mState.hasInitialLocationChanged() && !isSearch) {
// This noops on layouts without drawer, so no need to guard.
((BaseActivity) mContext).setRootsDrawerOpen(true);
}
@@ -171,47 +188,6 @@
}
/**
- * Provides support for Platform specific specializations of DirectoryFragment.
- */
- private static final class DownloadsTuner extends FragmentTuner {
-
- public DownloadsTuner(Context context, State state) {
- super(context, state);
- assert(state.action == ACTION_MANAGE);
- }
-
- @Override
- public void updateActionMenu(
- Menu menu, @ResultType int resultType, boolean canDelete, boolean canRename) {
- assert(resultType != DirectoryFragment.TYPE_RECENT_OPEN);
-
- MenuItem open = menu.findItem(R.id.menu_open);
- MenuItem delete = menu.findItem(R.id.menu_delete);
- MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
- MenuItem moveTo = menu.findItem(R.id.menu_move_to);
- MenuItem rename = menu.findItem(R.id.menu_rename);
- MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
-
- open.setVisible(false);
- delete.setVisible(canDelete);
- copy.setEnabled(true); // to clipboard
- copyTo.setVisible(true);
- copyTo.setEnabled(true);
- moveTo.setVisible(true);
- moveTo.setEnabled(true);
- rename.setVisible(false);
- }
-
- @Override
- void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {}
-
- @Override
- public boolean enableManagedMode() {
- return mState.stack.root != null && mState.stack.root.isDownloads();
- }
- }
-
- /**
* Provides support for Files activity specific specializations of DirectoryFragment.
*/
private static final class FilesTuner extends FragmentTuner {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java
index e7fa28b..c8641a8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java
@@ -150,12 +150,4 @@
mSize.setText(Formatter.formatFileSize(mContext, docSize));
}
}
-
- /**
- * Sets whether to hide titles on subsequently created GridDocumentHolder items.
- * @param hideTitles
- */
- public static void setHideTitles(boolean hideTitles) {
- mHideTitles = hideTitles;
- }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index d74121e..d5327f9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -245,10 +245,6 @@
return (flags & Document.FLAG_SUPPORTS_RENAME) != 0;
}
- public boolean isGridTitlesHidden() {
- return (flags & Document.FLAG_DIR_HIDE_GRID_TITLES) != 0;
- }
-
public boolean isArchive() {
return (flags & Document.FLAG_ARCHIVE) != 0;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
index ad48a70..9ed2abf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
@@ -299,18 +299,15 @@
if ((src.flags & Document.FLAG_SUPPORTS_COPY) != 0) {
try {
if (DocumentsContract.copyDocument(getClient(src), src.derivedUri,
- dstDirInfo.derivedUri) == null) {
- throw new ResourceException("Provider side copy failed for document %s.",
- src.derivedUri);
+ dstDirInfo.derivedUri) != null) {
+ return;
}
- } catch (ResourceException e) {
- throw e;
} catch (RemoteException | RuntimeException e) {
- throw new ResourceException(
- "Provider side copy failed for document %s due to an exception.",
- src.derivedUri, e);
+ Log.e(TAG, "Provider side copy failed for: " + src.derivedUri
+ + " due to an exception: " + e);
}
- return;
+ // If optimized copy fails, then fallback to byte-by-byte copy.
+ if (DEBUG) Log.d(TAG, "Fallback to byte-by-byte copy for: " + src.derivedUri);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
index f1b4681..aaa7596 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
@@ -16,6 +16,7 @@
package com.android.documentsui.services;
+import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
import android.app.Notification;
@@ -24,6 +25,7 @@
import android.os.RemoteException;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
+import android.util.Log;
import com.android.documentsui.R;
import com.android.documentsui.model.DocumentInfo;
@@ -34,6 +36,8 @@
// TODO: Stop extending CopyJob.
final class MoveJob extends CopyJob {
+ private static final String TAG = "MoveJob";
+
final DocumentInfo mSrcParent;
/**
@@ -89,16 +93,15 @@
try {
if (DocumentsContract.moveDocument(getClient(src), src.derivedUri,
srcParent != null ? srcParent.derivedUri : mSrcParent.derivedUri,
- dest.derivedUri) == null) {
- throw new ResourceException("Provider side move failed for document %s.",
- src.derivedUri);
+ dest.derivedUri) != null) {
+ return;
}
- } catch (RuntimeException | RemoteException e) {
- throw new ResourceException(
- "Provider side move failed for document %s due to an exception.",
- src.derivedUri, e);
+ } catch (RemoteException | RuntimeException e) {
+ Log.e(TAG, "Provider side move failed for: " + src.derivedUri
+ + " due to an exception: " + e);
}
- return;
+ // If optimized move fails, then fallback to byte-by-byte copy.
+ if (DEBUG) Log.d(TAG, "Fallback to byte-by-byte move for: " + src.derivedUri);
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
index af478ea..16ed2d9 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
@@ -106,13 +106,15 @@
return createDocument(root.documentId, mimeType, name);
}
- public Uri createDocumentWithFlags(String documentId, String mimeType, String name, int flags)
+ public Uri createDocumentWithFlags(String documentId, String mimeType, String name, int flags,
+ String... streamTypes)
throws RemoteException {
Bundle in = new Bundle();
in.putInt(StubProvider.EXTRA_FLAGS, flags);
in.putString(StubProvider.EXTRA_PARENT_ID, documentId);
in.putString(Document.COLUMN_MIME_TYPE, mimeType);
in.putString(Document.COLUMN_DISPLAY_NAME, name);
+ in.putStringArrayList(StubProvider.EXTRA_STREAM_TYPES, Lists.newArrayList(streamTypes));
Bundle out = mClient.call("createDocumentWithFlags", null, in);
Uri uri = out.getParcelable(DocumentsContract.EXTRA_URI);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
deleted file mode 100644
index 79d7887..0000000
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
+++ /dev/null
@@ -1,102 +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 com.android.documentsui;
-
-import static com.android.documentsui.StubProvider.ROOT_0_ID;
-
-import android.content.Intent;
-import android.os.RemoteException;
-import android.provider.DocumentsContract;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.Until;
-import android.test.suitebuilder.annotation.LargeTest;
-
-@LargeTest
-public class DownloadsActivityUiTest extends ActivityTest<DownloadsActivity> {
-
- public DownloadsActivityUiTest() {
- super(DownloadsActivity.class);
- }
-
- @Override
- void launchActivity() {
- final Intent intent = new Intent(DocumentsContract.ACTION_MANAGE_ROOT);
- intent.setDataAndType(rootDir0.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- setActivityIntent(intent);
- getActivity(); // Launch the activity.
- }
-
- @Override
- void initTestFiles() throws RemoteException {
- mDocsHelper.createDocument(rootDir0, "text/plain", "file0.log");
- mDocsHelper.createDocument(rootDir0, "image/png", "file1.png");
- mDocsHelper.createDocument(rootDir0, "text/csv", "file2.csv");
- }
-
- public void testWindowTitle() throws Exception {
- initTestFiles();
-
- bots.main.assertWindowTitle(ROOT_0_ID);
- }
-
- public void testFilesListed() throws Exception {
- initTestFiles();
-
- bots.directory.assertDocumentsPresent("file0.log", "file1.png", "file2.csv");
- }
-
- public void testFilesList_LiveUpdate() throws Exception {
- initTestFiles();
-
- mDocsHelper.createDocument(rootDir0, "yummers/sandwich", "Ham & Cheese.sandwich");
-
- bots.directory.waitForDocument("Ham & Cheese.sandwich");
- bots.directory.assertDocumentsPresent(
- "file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
- }
-
- public void testDeleteDocument() throws Exception {
- initTestFiles();
-
- bots.directory.clickDocument("file1.png");
- device.waitForIdle();
- bots.main.menuDelete().click();
-
- bots.directory.waitForDeleteSnackbar();
- bots.directory.assertDocumentsAbsent("file1.png");
-
- bots.directory.waitForDeleteSnackbarGone();
- bots.directory.assertDocumentsAbsent("file1.png");
- }
-
- public void testSupportsShare() throws Exception {
- initTestFiles();
-
- bots.directory.clickDocument("file1.png");
- device.waitForIdle();
- assertNotNull(bots.main.menuShare());
- }
-
- public void testClosesOnBack() throws Exception {
- DownloadsActivity activity = getActivity();
- device.pressBack();
- device.wait(Until.gone(By.text(ROOT_0_ID)), TIMEOUT); // wait for the window to go away
- assertTrue(activity.isDestroyed());
- }
-}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 2486209..056e6ed 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -72,11 +72,11 @@
bots.directory.assertDocumentsPresent("file0.log", "file1.png", "file2.csv");
}
- public void testLoadsHomeDirectoryByDefault() throws Exception {
+ public void testLoadsDownloadsDirectoryByDefault() throws Exception {
initTestFiles();
device.waitForIdle();
- bots.main.assertWindowTitle("Documents");
+ bots.main.assertWindowTitle("Downloads");
}
public void testRootClickSetsWindowTitle() throws Exception {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 2527650..f71ce5d 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -518,28 +518,29 @@
String rootId = extras.getString(EXTRA_PARENT_ID);
String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
String name = extras.getString(Document.COLUMN_DISPLAY_NAME);
+ List<String> streamTypes = extras.getStringArrayList(EXTRA_STREAM_TYPES);
int flags = extras.getInt(EXTRA_FLAGS);
Bundle out = new Bundle();
String documentId = null;
try {
- documentId = createDocument(rootId, mimeType, name, flags);
+ documentId = createDocument(rootId, mimeType, name, flags, streamTypes);
Uri uri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
out.putParcelable(DocumentsContract.EXTRA_URI, uri);
} catch (FileNotFoundException e) {
- Log.d(TAG, "Cretaing document with flags failed" + name);
+ Log.d(TAG, "Creating document with flags failed" + name);
}
return out;
}
- public String createDocument(String parentId, String mimeType, String displayName, int flags)
- throws FileNotFoundException {
+ public String createDocument(String parentId, String mimeType, String displayName, int flags,
+ List<String> streamTypes) throws FileNotFoundException {
StubDocument parent = mStorage.get(parentId);
File file = createFile(parent, mimeType, displayName);
final StubDocument document = StubDocument.createDocumentWithFlags(file, mimeType, parent,
- flags);
+ flags, streamTypes);
mStorage.put(document.documentId, document);
Log.d(TAG, "Created document " + document.documentId);
notifyParentChanged(document.parentId);
@@ -787,8 +788,9 @@
}
public static StubDocument createDocumentWithFlags(
- File file, String mimeType, StubDocument parent, int flags) {
- return new StubDocument(file, mimeType, new ArrayList<String>(), flags, parent);
+ File file, String mimeType, StubDocument parent, int flags,
+ List<String> streamTypes) {
+ return new StubDocument(file, mimeType, streamTypes, flags, parent);
}
public static StubDocument createVirtualDocument(
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
index 543396e..bb7c01a 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
@@ -16,6 +16,10 @@
package com.android.documentsui.services;
+import static com.google.common.collect.Lists.newArrayList;
+
+import android.net.Uri;
+import android.provider.DocumentsContract.Document;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.documentsui.model.DocumentInfo;
@@ -38,6 +42,21 @@
runCopyVirtualNonTypedFileTest();
}
+ public void testCopy_BackendSideVirtualTypedFile_Fallback() throws Exception {
+ mDocs.assertChildCount(mDestRoot, 0);
+
+ Uri testFile = mDocs.createDocumentWithFlags(
+ mSrcRoot.documentId, "virtual/mime-type", "tokyo.sth",
+ Document.FLAG_VIRTUAL_DOCUMENT | Document.FLAG_SUPPORTS_COPY
+ | Document.FLAG_SUPPORTS_MOVE, "application/pdf");
+
+ createJob(newArrayList(testFile)).run();
+
+ mJobListener.waitForFinished();
+ mDocs.assertChildCount(mDestRoot, 1);
+ mDocs.assertHasFile(mDestRoot, "tokyo.sth.pdf"); // Copy should convert file to PDF.
+ }
+
public void testCopyEmptyDir() throws Exception {
runCopyEmptyDirTest();
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
index 749264a..24181d6 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
@@ -59,6 +59,21 @@
mDocs.assertChildCount(mSrcRoot, 1);
}
+ public void testMove_BackendSideVirtualTypedFile_Fallback() throws Exception {
+ Uri testFile = mDocs.createDocumentWithFlags(
+ mSrcRoot.documentId, "virtual/mime-type", "tokyo.sth",
+ Document.FLAG_VIRTUAL_DOCUMENT | Document.FLAG_SUPPORTS_COPY
+ | Document.FLAG_SUPPORTS_MOVE, "application/pdf");
+
+ createJob(newArrayList(testFile)).run();
+ mJobListener.waitForFinished();
+
+ // Should have failed, source not deleted. Moving by bytes for virtual files
+ // is not supported.
+ mDocs.assertChildCount(mDestRoot, 0);
+ mDocs.assertChildCount(mSrcRoot, 1);
+ }
+
public void testMoveEmptyDir() throws Exception {
runCopyEmptyDirTest();
diff --git a/packages/PrintSpooler/res/values-zh-rCN/strings.xml b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
index 4a27839..fb30e44 100644
--- a/packages/PrintSpooler/res/values-zh-rCN/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
@@ -61,25 +61,17 @@
</plurals>
<string name="printer_extended_description_template" msgid="1366699227703381874">"<xliff:g id="PRINT_SERVICE_LABEL">%1$s</xliff:g> - <xliff:g id="PRINTER_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="printer_info_desc" msgid="7181988788991581654">"关于此打印机的更多信息"</string>
- <!-- no translation found for print_services_disabled_toast (9089060734685174685) -->
- <skip />
+ <string name="print_services_disabled_toast" msgid="9089060734685174685">"部分打印服务已停用"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"正在搜索打印机"</string>
<string name="print_no_print_services" msgid="8561247706423327966">"未启用任何打印服务"</string>
<string name="print_no_printers" msgid="4869403323900054866">"找不到打印机"</string>
- <!-- no translation found for cannot_add_printer (7840348733668023106) -->
- <skip />
- <!-- no translation found for select_to_add_printers (3800709038689830974) -->
- <skip />
- <!-- no translation found for enable_print_service (3482815747043533842) -->
- <skip />
- <!-- no translation found for enabled_services_title (7036986099096582296) -->
- <skip />
- <!-- no translation found for recommended_services_title (3799434882937956924) -->
- <skip />
- <!-- no translation found for disabled_services_title (7313253167968363211) -->
- <skip />
- <!-- no translation found for all_services_title (5578662754874906455) -->
- <skip />
+ <string name="cannot_add_printer" msgid="7840348733668023106">"无法添加打印机"</string>
+ <string name="select_to_add_printers" msgid="3800709038689830974">"选择即可添加打印机"</string>
+ <string name="enable_print_service" msgid="3482815747043533842">"选择即可启用"</string>
+ <string name="enabled_services_title" msgid="7036986099096582296">"已启用的服务"</string>
+ <string name="recommended_services_title" msgid="3799434882937956924">"推荐的服务"</string>
+ <string name="disabled_services_title" msgid="7313253167968363211">"已停用的服务"</string>
+ <string name="all_services_title" msgid="5578662754874906455">"所有服务"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"正在打印“<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>”"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"正在取消打印“<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>”"</string>
<string name="failed_notification_title_template" msgid="2256217208186530973">"打印机在打印“<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>”时出错"</string>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index b611ba3..f601f90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -198,6 +198,7 @@
}
mNonfirstPageAnimator = new TouchAnimator.Builder()
.addFloat(mQuickQsPanel, "alpha", 1, 0)
+ .setListener(mNonFirstPageListener)
.setEndDelay(.5f)
.build();
}
@@ -267,7 +268,7 @@
private void clearAnimationState() {
final int N = mAllViews.size();
mQuickQsPanel.setAlpha(0);
- mQuickQsPanel.setVisibility(View.VISIBLE);
+ mQuickQsPanel.setVisibility(View.INVISIBLE);
for (int i = 0; i < N; i++) {
View v = mAllViews.get(i);
v.setAlpha(1);
@@ -293,6 +294,14 @@
mQsPanel.post(mUpdateAnimators);
}
+ private final TouchAnimator.Listener mNonFirstPageListener =
+ new TouchAnimator.ListenerAdapter() {
+ @Override
+ public void onAnimationStarted() {
+ mQuickQsPanel.setVisibility(View.VISIBLE);
+ }
+ };
+
private Runnable mUpdateAnimators = new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 079d7b9..84590f2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -16,6 +16,7 @@
package com.android.systemui.recents.views;
+import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Point;
import android.view.MotionEvent;
@@ -149,7 +150,8 @@
mTaskView.setTranslationY(y);
mVisibleDockStates.clear();
- if (!ssp.hasDockedTask() && mRv.getTaskStack().getTaskCount() > 1) {
+ if (ActivityManager.supportsMultiWindow() &&
+ !ssp.hasDockedTask() && mRv.getTaskStack().getTaskCount() > 1) {
if (!event.task.isDockable) {
Toast.makeText(mRv.getContext(), R.string.recents_drag_non_dockable_task_message,
Toast.LENGTH_SHORT).show();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index c2e1f7d..51553be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -1349,7 +1349,7 @@
float y = event.getY();
NotificationHeaderView header = getVisibleNotificationHeader();
if (header != null) {
- return header.isInTouchRect(x, y);
+ return header.isInTouchRect(x - getTranslation(), y);
}
return super.disallowSingleClick(event);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index c0e4340..91418ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -43,6 +43,7 @@
private boolean mWillBeGone;
private int mMinClipTopAmount = 0;
private boolean mClipToActualHeight = true;
+ private boolean mChangingPosition = false;
public ExpandableView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -407,6 +408,14 @@
return 0;
}
+ public void setChangingPosition(boolean changingPosition) {
+ mChangingPosition = changingPosition;
+ }
+
+ public boolean isChangingPosition() {
+ return mChangingPosition;
+ }
+
/**
* A listener notifying when {@link #getActualHeight} changes.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 5b4a3f0..65e7973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -105,7 +105,6 @@
if (mSelectedUser != null && mSelectedUser.getIdentifier() != mCurrentUserId) {
// When selected user is different from the current user, show the selected
// user's static wallpaper.
- mWallpaperManager.forgetLoadedWallpaper();
mCache = mWallpaperManager.getBitmapAsUser(mSelectedUser.getIdentifier());
} else {
// When there is no selected user, or it's same as the current user, show the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 553d7f8..e5e3caf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -277,7 +277,8 @@
public void updateResources() {
int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mQsContainer.getLayoutParams();
+ FrameLayout.LayoutParams lp =
+ (FrameLayout.LayoutParams) mQsDensityContainer.getLayoutParams();
if (lp.width != panelWidth) {
lp.width = panelWidth;
lp.gravity = panelGravity;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 03cdcb2..8cfda3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1012,7 +1012,7 @@
}
}
- private void clearAllNotifications() {
+ public void clearAllNotifications() {
// animate-swipe all dismissable notifications, then animate the shade closed
int numChildren = mStackScroller.getChildCount();
@@ -1149,9 +1149,10 @@
@Override
public boolean onLongClick(View v) {
- if (mRecents == null) {
+ if (mRecents == null || !ActivityManager.supportsMultiWindow()) {
return false;
}
+
boolean initiallyDocked = WindowManagerProxy.getInstance().getDockSide()
== WindowManager.DOCKED_INVALID;
boolean dockedAtEnd = toggleSplitScreenMode();
@@ -1946,6 +1947,7 @@
// We are unlocking directly - no animation!
mBackdrop.setVisibility(View.GONE);
+ mBackdropBack.setImageDrawable(null);
} else {
mBackdrop.animate()
// Never let the alpha become zero - otherwise the RenderNode
@@ -1961,7 +1963,7 @@
public void run() {
mBackdrop.setVisibility(View.GONE);
mBackdropFront.animate().cancel();
- mBackdropBack.animate().cancel();
+ mBackdropBack.setImageDrawable(null);
mHandler.post(mHideBackdropFront);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index fcaf050..77ece93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -173,7 +173,7 @@
private void applyInputFeatures(State state) {
if (state.isKeyguardShowingAndNotOccluded()
&& state.statusBarState == StatusBarState.KEYGUARD
- && !state.qsExpanded) {
+ && !state.qsExpanded && !state.forceUserActivity) {
mLpChanged.inputFeatures |=
WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
} else {
@@ -265,6 +265,11 @@
apply(mCurrentState);
}
+ public void setForceUserActivity(boolean forceUserActivity) {
+ mCurrentState.forceUserActivity = forceUserActivity;
+ apply(mCurrentState);
+ }
+
public void setHeadsUpShowing(boolean showing) {
mCurrentState.headsUpShowing = showing;
apply(mCurrentState);
@@ -332,6 +337,7 @@
boolean forceStatusBarVisible;
boolean forceCollapsed;
boolean forceDozeBrightness;
+ boolean forceUserActivity;
/**
* The {@link BaseStatusBar} state from the status bar.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index d9e8bd9..29b0f4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -155,8 +155,21 @@
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mEntry.row.isChangingPosition()) {
+ if (getVisibility() == VISIBLE && mEditText.isFocusable()) {
+ mEditText.requestFocus();
+ }
+ }
+ }
+
+ @Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ if (mEntry.row.isChangingPosition()) {
+ return;
+ }
mController.removeRemoteInput(mEntry);
}
@@ -229,6 +242,9 @@
}
private void defocusIfNeeded() {
+ if (mDefocusListener.mEntry.row.isChangingPosition()) {
+ return;
+ }
if (isFocusable() && isEnabled()) {
setInnerFocusable(false);
if (mDefocusListener != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 2ea9507..9dfe369 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -2252,8 +2252,10 @@
int currentIndex = indexOfChild(child);
if (child != null && child.getParent() == this && currentIndex != newIndex) {
mChangePositionInProgress = true;
+ ((ExpandableView)child).setChangingPosition(true);
removeView(child);
addView(child, newIndex);
+ ((ExpandableView)child).setChangingPosition(false);
mChangePositionInProgress = false;
if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
mChildrenChangingPositions.add(child);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
index 284c309..3ac81f6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
@@ -37,7 +37,8 @@
/**
* This class handles gesture detection for the Touch Explorer. It collects
- * touch events, and sends events to mListener as gestures are recognized.
+ * touch events and determines when they match a gesture, as well as when they
+ * won't match a gesture. These state changes are then surfaced to mListener.
*/
class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListener {
@@ -46,12 +47,66 @@
// Tag for logging received events.
private static final String LOG_TAG = "AccessibilityGestureDetector";
+ /**
+ * Listener functions are called as a result of onMoveEvent(). The current
+ * MotionEvent in the context of these functions is the event passed into
+ * onMotionEvent.
+ */
public interface Listener {
- public void onDoubleTapAndHold(MotionEvent event, int policyFlags);
- public boolean onDoubleTap(MotionEvent event, int policyFlags);
- public boolean onGestureCompleted(int gestureId);
- public void onGestureStarted();
- public void onGestureCancelled(MotionEvent event, int policyFlags);
+ /**
+ * Called when the user has performed a double tap and then held down
+ * the second tap.
+ *
+ * @param event The most recent MotionEvent received.
+ * @param policyFlags The policy flags of the most recent event.
+ */
+ void onDoubleTapAndHold(MotionEvent event, int policyFlags);
+
+ /**
+ * Called when the user touches the screen on the second tap of a double
+ * tap.
+ *
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTapStarted();
+
+ /**
+ * Called when the user lifts their finger on the second tap of a double
+ * tap.
+ *
+ * @param event The most recent MotionEvent received.
+ * @param policyFlags The policy flags of the most recent event.
+ *
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTap(MotionEvent event, int policyFlags);
+
+ /**
+ * Called when the system has decided the event stream is a gesture.
+ *
+ * @return true if the event is consumed, else false
+ */
+ boolean onGestureStarted();
+
+ /**
+ * Called when an event stream is recognized as a gesture.
+ *
+ * @param gestureId ID of the gesture that was recognized.
+ *
+ * @return true if the event is consumed, else false
+ */
+ boolean onGestureCompleted(int gestureId);
+
+ /**
+ * Called when the system has decided an event stream doesn't match any
+ * known gesture.
+ *
+ * @param event The most recent MotionEvent received.
+ * @param policyFlags The policy flags of the most recent event.
+ *
+ * @return true if the event is consumed, else false
+ */
+ public boolean onGestureCancelled(MotionEvent event, int policyFlags);
}
private final Listener mListener;
@@ -145,6 +200,18 @@
context.getResources().getDisplayMetrics()) * GESTURE_CONFIRM_MM;
}
+ /**
+ * Handle a motion event. If an action is completed, the appropriate
+ * callback on mListener is called, and the return value of the callback is
+ * passed to the caller.
+ *
+ * @param event The raw motion event. It's important that this be the raw
+ * event, before any transformations have been applied, so that measurements
+ * can be made in physical units.
+ * @param policyFlags Policy flags for the event.
+ *
+ * @return true if the event is consumed, else false
+ */
public boolean onMotionEvent(MotionEvent event, int policyFlags) {
final float x = event.getX();
final float y = event.getY();
@@ -183,7 +250,7 @@
// the event.
if (!mGestureStarted) {
mGestureStarted = true;
- mListener.onGestureStarted();
+ return mListener.onGestureStarted();
}
} else {
final long timeDelta = time - mBaseTime;
@@ -195,8 +262,7 @@
// timeout, cancel gesture detection.
if (timeDelta > threshold) {
cancelGesture();
- mListener.onGestureCancelled(event, policyFlags);
- return false;
+ return mListener.onGestureCancelled(event, policyFlags);
}
}
@@ -211,16 +277,13 @@
break;
case MotionEvent.ACTION_UP:
- if (maybeFinishDoubleTap(event, policyFlags)) {
- return true;
+ if (mDoubleTapDetected) {
+ return finishDoubleTap(event, policyFlags);
}
if (mGestureStarted) {
mStrokeBuffer.add(new GesturePoint(x, y, time));
- if (!recognizeGesture()) {
- mListener.onGestureCancelled(event, policyFlags);
- }
- return true;
+ return recognizeGesture(event, policyFlags);
}
break;
@@ -244,8 +307,8 @@
case MotionEvent.ACTION_POINTER_UP:
// If we're detecting taps on the second finger, see if we
// should finish the double tap.
- if (mSecondFingerDoubleTap && maybeFinishDoubleTap(event, policyFlags)) {
- return true;
+ if (mSecondFingerDoubleTap && mDoubleTapDetected) {
+ return finishDoubleTap(event, policyFlags);
}
break;
@@ -308,7 +371,7 @@
// The processing of the double tap is deferred until the finger is
// lifted, so that we can detect a long press on the second tap.
mDoubleTapDetected = true;
- return true;
+ return mListener.onDoubleTapStarted();
}
private void maybeSendLongPress(MotionEvent event, int policyFlags) {
@@ -321,11 +384,7 @@
mListener.onDoubleTapAndHold(event, policyFlags);
}
- private boolean maybeFinishDoubleTap(MotionEvent event, int policyFlags) {
- if (!mDoubleTapDetected) {
- return false;
- }
-
+ private boolean finishDoubleTap(MotionEvent event, int policyFlags) {
clear();
return mListener.onDoubleTap(event, policyFlags);
@@ -337,7 +396,7 @@
mStrokeBuffer.clear();
}
- private boolean recognizeGesture() {
+ private boolean recognizeGesture(MotionEvent event, int policyFlags) {
Gesture gesture = new Gesture();
gesture.addStroke(new GestureStroke(mStrokeBuffer));
@@ -351,16 +410,14 @@
}
try {
final int gestureId = Integer.parseInt(bestPrediction.name);
- if (mListener.onGestureCompleted(gestureId)) {
- return true;
- }
+ return mListener.onGestureCompleted(gestureId);
} catch (NumberFormatException nfe) {
Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
}
}
}
- return false;
+ return mListener.onGestureCancelled(event, policyFlags);
}
private MotionEvent mapSecondPointerToFirstPointer(MotionEvent event) {
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 3ecff40..cd8b792 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -390,6 +390,11 @@
}
@Override
+ public boolean onDoubleTapStarted() {
+ return true;
+ }
+
+ @Override
public boolean onDoubleTap(MotionEvent event, int policyFlags) {
// Ignore the event if we aren't touch exploring.
if (mCurrentState != STATE_TOUCH_EXPLORING) {
@@ -437,6 +442,20 @@
}
@Override
+ public boolean onGestureStarted() {
+ // We have to perform gesture detection, so
+ // clear the current state and try to detect.
+ mCurrentState = STATE_GESTURE_DETECTING;
+ mSendHoverEnterAndMoveDelayed.cancel();
+ mSendHoverExitDelayed.cancel();
+ mExitGestureDetectionModeDelayed.post();
+ // Send accessibility event to announce the start
+ // of gesture recognition.
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
+ return false;
+ }
+
+ @Override
public boolean onGestureCompleted(int gestureId) {
if (mCurrentState != STATE_GESTURE_DETECTING) {
return false;
@@ -450,36 +469,26 @@
}
@Override
- public void onGestureStarted() {
- // We have to perform gesture detection, so
- // clear the current state and try to detect.
- mCurrentState = STATE_GESTURE_DETECTING;
- mSendHoverEnterAndMoveDelayed.cancel();
- mSendHoverExitDelayed.cancel();
- mExitGestureDetectionModeDelayed.post();
- // Send accessibility event to announce the start
- // of gesture recognition.
- sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
- }
+ public boolean onGestureCancelled(MotionEvent event, int policyFlags) {
+ if (mCurrentState == STATE_GESTURE_DETECTING) {
+ endGestureDetection();
+ return true;
+ } else if (mCurrentState == STATE_TOUCH_EXPLORING) {
+ // If the finger is still moving, pass the event on.
+ if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
+ final int pointerIdBits = (1 << pointerId);
- @Override
- public void onGestureCancelled(MotionEvent event, int policyFlags) {
- if (mCurrentState == STATE_GESTURE_DETECTING) {
- endGestureDetection();
- } else if (mCurrentState == STATE_TOUCH_EXPLORING) {
- // If the finger is still moving, pass the event on.
- if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
- final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
- final int pointerIdBits = (1 << pointerId);
-
- // We have just decided that the user is touch,
- // exploring so start sending events.
- mSendHoverEnterAndMoveDelayed.addEvent(event);
- mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
- mSendHoverExitDelayed.cancel();
- sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
- }
- }
+ // We have just decided that the user is touch,
+ // exploring so start sending events.
+ mSendHoverEnterAndMoveDelayed.addEvent(event);
+ mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
+ mSendHoverExitDelayed.cancel();
+ sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+ return true;
+ }
+ }
+ return false;
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index d11b436..5561456 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -20,6 +20,7 @@
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
@@ -1404,22 +1405,37 @@
* needed. A stack is considered translucent if it don't contain a visible or
* starting (about to be visible) activity that is fullscreen (opaque).
* @param starting The currently starting activity or null if there is none.
+ * @param stackBehindId The id of the stack directly behind this one.
*/
- private boolean isStackTranslucent(ActivityRecord starting) {
+ private boolean isStackTranslucent(ActivityRecord starting, int stackBehindId) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
final ArrayList<ActivityRecord> activities = task.mActivities;
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
- // Conditions for an activity to obscure the stack we're
- // examining:
- // 1. Not Finishing AND (Visible or the Starting activity) AND:
- // 2. Either:
- // - Full Screen Activity OR
- // - On top of Home and our stack is NOT home
- if (!r.finishing && (r.visible || r == starting) && (r.fullscreen ||
- (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()))) {
+ if (r.finishing) {
+ // We don't factor in finishing activities when determining translucency since
+ // they will be gone soon.
+ continue;
+ }
+
+ if (!r.visible && r != starting) {
+ // Also ignore invisible activities that are not the currently starting
+ // activity (about to be visible).
+ continue;
+ }
+
+ if (r.fullscreen) {
+ // Stack isn't translucent if it has at least one fullscreen activity
+ // that is visible.
+ return false;
+ }
+
+ if (!isHomeStack() && r.frontOfTask
+ && task.isOverHomeStack() && stackBehindId != HOME_STACK_ID) {
+ // Stack isn't translucent if it's top activity should have the home stack
+ // behind it and the stack currently behind it isn't the home stack.
return false;
}
}
@@ -1475,30 +1491,33 @@
: STACK_INVISIBLE;
}
- // Find the first stack below focused stack that actually got something visible.
- int belowFocusedIndex = mStacks.indexOf(focusedStack) - 1;
- while (belowFocusedIndex >= 0 &&
- mStacks.get(belowFocusedIndex).topRunningActivityLocked() == null) {
- belowFocusedIndex--;
+ // Find the first stack behind focused stack that actually got something visible.
+ int stackBehindFocusedIndex = mStacks.indexOf(focusedStack) - 1;
+ while (stackBehindFocusedIndex >= 0 &&
+ mStacks.get(stackBehindFocusedIndex).topRunningActivityLocked() == null) {
+ stackBehindFocusedIndex--;
}
if ((focusedStackId == DOCKED_STACK_ID || focusedStackId == PINNED_STACK_ID)
- && stackIndex == belowFocusedIndex) {
+ && stackIndex == stackBehindFocusedIndex) {
// Stacks directly behind the docked or pinned stack are always visible.
return STACK_VISIBLE;
}
+ final int stackBehindFocusedId = (stackBehindFocusedIndex >= 0)
+ ? mStacks.get(stackBehindFocusedIndex).mStackId : INVALID_STACK_ID;
+
if (focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID
- && focusedStack.isStackTranslucent(starting)) {
+ && focusedStack.isStackTranslucent(starting, stackBehindFocusedId)) {
// Stacks behind the fullscreen stack with a translucent activity are always
// visible so they can act as a backdrop to the translucent activity.
// For example, dialog activities
- if (stackIndex == belowFocusedIndex) {
+ if (stackIndex == stackBehindFocusedIndex) {
return STACK_VISIBLE;
}
- if (belowFocusedIndex >= 0) {
- final ActivityStack stack = mStacks.get(belowFocusedIndex);
- if ((stack.mStackId == DOCKED_STACK_ID || stack.mStackId == PINNED_STACK_ID)
- && stackIndex == (belowFocusedIndex - 1)) {
+ if (stackBehindFocusedIndex >= 0) {
+ if ((stackBehindFocusedId == DOCKED_STACK_ID
+ || stackBehindFocusedId == PINNED_STACK_ID)
+ && stackIndex == (stackBehindFocusedIndex - 1)) {
// The stack behind the docked or pinned stack is also visible so we can have a
// complete backdrop to the translucent activity when the docked stack is up.
return STACK_VISIBLE;
@@ -1523,7 +1542,7 @@
return STACK_INVISIBLE;
}
- if (!stack.isStackTranslucent(starting)) {
+ if (!stack.isStackTranslucent(starting, INVALID_STACK_ID)) {
return STACK_INVISIBLE;
}
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index ea85fa1..004be34 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1334,8 +1334,13 @@
if (bounds == null) {
return;
}
- final int minimalSize = mMinimalSize == -1
- ? mService.mStackSupervisor.mDefaultMinimalSizeOfResizeableTask : mMinimalSize;
+ int minimalSize = mMinimalSize;
+ // If the task has no requested minimal size, we'd like to enforce a minimal size
+ // so that the user can not render the task too small to manipulate. We don't need
+ // to do this for the pinned stack as the bounds are controlled by the system.
+ if (minimalSize == -1 && stack.mStackId != PINNED_STACK_ID) {
+ minimalSize = mService.mStackSupervisor.mDefaultMinimalSizeOfResizeableTask;
+ }
final boolean adjustWidth = minimalSize > bounds.width();
final boolean adjustHeight = minimalSize > bounds.height();
if (!(adjustWidth || adjustHeight)) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 9d8def6..1cd0592 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -111,8 +111,8 @@
public class ShortcutService extends IShortcutService.Stub {
static final String TAG = "ShortcutService";
- private static final boolean DEBUG = true; // STOPSHIP if true
- private static final boolean DEBUG_LOAD = true; // STOPSHIP if true
+ static final boolean DEBUG = false; // STOPSHIP if true
+ static final boolean DEBUG_LOAD = false; // STOPSHIP if true
@VisibleForTesting
static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
@@ -148,27 +148,27 @@
static final String DIRECTORY_BITMAPS = "bitmaps";
- private static final String TAG_ROOT = "root";
- private static final String TAG_PACKAGE = "package";
- private static final String TAG_LAST_RESET_TIME = "last_reset_time";
- private static final String TAG_INTENT_EXTRAS = "intent-extras";
- private static final String TAG_EXTRAS = "extras";
- private static final String TAG_SHORTCUT = "shortcut";
+ static final String TAG_ROOT = "root";
+ static final String TAG_PACKAGE = "package";
+ static final String TAG_LAST_RESET_TIME = "last_reset_time";
+ static final String TAG_INTENT_EXTRAS = "intent-extras";
+ static final String TAG_EXTRAS = "extras";
+ static final String TAG_SHORTCUT = "shortcut";
- private static final String ATTR_VALUE = "value";
- private static final String ATTR_NAME = "name";
- private static final String ATTR_DYNAMIC_COUNT = "dynamic-count";
- private static final String ATTR_CALL_COUNT = "call-count";
- private static final String ATTR_LAST_RESET = "last-reset";
- private static final String ATTR_ID = "id";
- private static final String ATTR_ACTIVITY = "activity";
- private static final String ATTR_TITLE = "title";
- private static final String ATTR_INTENT = "intent";
- private static final String ATTR_WEIGHT = "weight";
- private static final String ATTR_TIMESTAMP = "timestamp";
- private static final String ATTR_FLAGS = "flags";
- private static final String ATTR_ICON_RES = "icon-res";
- private static final String ATTR_BITMAP_PATH = "bitmap-path";
+ static final String ATTR_VALUE = "value";
+ static final String ATTR_NAME = "name";
+ static final String ATTR_DYNAMIC_COUNT = "dynamic-count";
+ static final String ATTR_CALL_COUNT = "call-count";
+ static final String ATTR_LAST_RESET = "last-reset";
+ static final String ATTR_ID = "id";
+ static final String ATTR_ACTIVITY = "activity";
+ static final String ATTR_TITLE = "title";
+ static final String ATTR_INTENT = "intent";
+ static final String ATTR_WEIGHT = "weight";
+ static final String ATTR_TIMESTAMP = "timestamp";
+ static final String ATTR_FLAGS = "flags";
+ static final String ATTR_ICON_RES = "icon-res";
+ static final String ATTR_BITMAP_PATH = "bitmap-path";
@VisibleForTesting
interface ConfigConstants {
@@ -198,7 +198,7 @@
String KEY_MAX_SHORTCUTS = "max_shortcuts";
/**
- * Key name for icom compression quality, 0-100.
+ * Key name for icon compression quality, 0-100.
*/
String KEY_ICON_QUALITY = "icon_quality";
@@ -208,7 +208,7 @@
String KEY_ICON_FORMAT = "icon_format";
}
- private final Context mContext;
+ final Context mContext;
private final Object mLock = new Object();
@@ -221,225 +221,6 @@
private long mRawLastResetTime;
/**
- * All the information relevant to shortcuts from a single package (per-user).
- *
- * TODO Move the persisting code to this class.
- *
- * Only save/load/dump should look/touch inside this class.
- */
- private static class PackageShortcuts {
- @UserIdInt
- private final int mUserId;
-
- @NonNull
- private final String mPackageName;
-
- /**
- * All the shortcuts from the package, keyed on IDs.
- */
- final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
-
- /**
- * # of dynamic shortcuts.
- */
- private int mDynamicShortcutCount = 0;
-
- /**
- * # of times the package has called rate-limited APIs.
- */
- private int mApiCallCount;
-
- /**
- * When {@link #mApiCallCount} was reset last time.
- */
- private long mLastResetTime;
-
- private PackageShortcuts(int userId, String packageName) {
- mUserId = userId;
- mPackageName = packageName;
- }
-
- @GuardedBy("mLock")
- @Nullable
- public ShortcutInfo findShortcutById(String id) {
- return mShortcuts.get(id);
- }
-
- private ShortcutInfo deleteShortcut(@NonNull ShortcutService s,
- @NonNull String id) {
- final ShortcutInfo shortcut = mShortcuts.remove(id);
- if (shortcut != null) {
- s.removeIcon(mUserId, shortcut);
- shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
- }
- return shortcut;
- }
-
- void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) {
- deleteShortcut(s, newShortcut.getId());
- s.saveIconAndFixUpShortcut(mUserId, newShortcut);
- mShortcuts.put(newShortcut.getId(), newShortcut);
- }
-
- /**
- * Add a shortcut, or update one with the same ID, with taking over existing flags.
- *
- * It checks the max number of dynamic shortcuts.
- */
- @GuardedBy("mLock")
- public void updateShortcutWithCapping(@NonNull ShortcutService s,
- @NonNull ShortcutInfo newShortcut) {
- final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
-
- int oldFlags = 0;
- int newDynamicCount = mDynamicShortcutCount;
-
- if (oldShortcut != null) {
- oldFlags = oldShortcut.getFlags();
- if (oldShortcut.isDynamic()) {
- newDynamicCount--;
- }
- }
- if (newShortcut.isDynamic()) {
- newDynamicCount++;
- }
- // Make sure there's still room.
- s.enforceMaxDynamicShortcuts(newDynamicCount);
-
- // Okay, make it dynamic and add.
- newShortcut.addFlags(oldFlags);
-
- addShortcut(s, newShortcut);
- mDynamicShortcutCount = newDynamicCount;
- }
-
- /**
- * Remove all shortcuts that aren't pinned nor dynamic.
- */
- private void removeOrphans(@NonNull ShortcutService s) {
- ArrayList<String> removeList = null; // Lazily initialize.
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
- if (si.isPinned() || si.isDynamic()) continue;
-
- if (removeList == null) {
- removeList = new ArrayList<>();
- }
- removeList.add(si.getId());
- }
- if (removeList != null) {
- for (int i = removeList.size() - 1 ; i >= 0; i--) {
- deleteShortcut(s, removeList.get(i));
- }
- }
- }
-
- @GuardedBy("mLock")
- public void deleteAllDynamicShortcuts(@NonNull ShortcutService s) {
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC);
- }
- removeOrphans(s);
- mDynamicShortcutCount = 0;
- }
-
- @GuardedBy("mLock")
- public void deleteDynamicWithId(@NonNull ShortcutService s, @NonNull String shortcutId) {
- final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
-
- if (oldShortcut == null) {
- return;
- }
- if (oldShortcut.isDynamic()) {
- mDynamicShortcutCount--;
- }
- if (oldShortcut.isPinned()) {
- oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
- } else {
- deleteShortcut(s, shortcutId);
- }
- }
-
- @GuardedBy("mLock")
- public void replacePinned(@NonNull ShortcutService s, String launcherPackage,
- List<String> shortcutIds) {
-
- // TODO Should be per launcherPackage.
-
- // First, un-pin all shortcuts
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED);
- }
-
- // Then pin ALL
- for (int i = shortcutIds.size() - 1; i >= 0; i--) {
- final ShortcutInfo shortcut = mShortcuts.get(shortcutIds.get(i));
- if (shortcut != null) {
- shortcut.addFlags(ShortcutInfo.FLAG_PINNED);
- }
- }
-
- removeOrphans(s);
- }
-
- /**
- * Number of calls that the caller has made, since the last reset.
- */
- @GuardedBy("mLock")
- public int getApiCallCount(@NonNull ShortcutService s) {
- final long last = s.getLastResetTimeLocked();
-
- final long now = s.injectCurrentTimeMillis();
- if (mLastResetTime > now) {
- // Clock rewound. // TODO Test it
- mLastResetTime = now;
- }
-
- // If not reset yet, then reset.
- if (mLastResetTime < last) {
- mApiCallCount = 0;
- mLastResetTime = last;
- }
- return mApiCallCount;
- }
-
- /**
- * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount}
- * and return true. Otherwise just return false.
- */
- @GuardedBy("mLock")
- public boolean tryApiCall(@NonNull ShortcutService s) {
- if (getApiCallCount(s) >= s.mMaxDailyUpdates) {
- return false;
- }
- mApiCallCount++;
- return true;
- }
-
- @GuardedBy("mLock")
- public void resetRateLimitingForCommandLine() {
- mApiCallCount = 0;
- mLastResetTime = 0;
- }
-
- /**
- * Find all shortcuts that match {@code query}.
- */
- @GuardedBy("mLock")
- public void findAll(@NonNull List<ShortcutInfo> result,
- @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
- for (int i = 0; i < mShortcuts.size(); i++) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
- if (query == null || query.test(si)) {
- result.add(si.clone(cloneFlag));
- }
- }
- }
- }
-
- /**
* User ID -> package name -> list of ShortcutInfos.
*/
@GuardedBy("mLock")
@@ -454,7 +235,7 @@
/**
* Max number of updating API calls that each application can make a day.
*/
- private int mMaxDailyUpdates;
+ int mMaxDailyUpdates;
/**
* Actual throttling-reset interval. By default it's a day.
@@ -504,7 +285,7 @@
}
@Override
- public void onStartUser(int userId) {
+ public void onUnlockUser(int userId) {
synchronized (mService.mLock) {
mService.onStartUserLocked(userId);
}
@@ -622,11 +403,15 @@
// === Persisting ===
@Nullable
- private String parseStringAttribute(XmlPullParser parser, String attribute) {
+ static String parseStringAttribute(XmlPullParser parser, String attribute) {
return parser.getAttributeValue(null, attribute);
}
- private long parseLongAttribute(XmlPullParser parser, String attribute) {
+ static int parseIntAttribute(XmlPullParser parser, String attribute) {
+ return (int) parseLongAttribute(parser, attribute);
+ }
+
+ static long parseLongAttribute(XmlPullParser parser, String attribute) {
final String value = parseStringAttribute(parser, attribute);
if (TextUtils.isEmpty(value)) {
return 0;
@@ -640,7 +425,7 @@
}
@Nullable
- private ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
+ static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
final String value = parseStringAttribute(parser, attribute);
if (TextUtils.isEmpty(value)) {
return null;
@@ -649,7 +434,7 @@
}
@Nullable
- private Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
+ static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
final String value = parseStringAttribute(parser, attribute);
if (TextUtils.isEmpty(value)) {
return null;
@@ -662,7 +447,7 @@
}
}
- private void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
+ static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
if (TextUtils.isEmpty(value)) return;
out.startTag(null, tag);
@@ -670,11 +455,11 @@
out.endTag(null, tag);
}
- private void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
+ static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
writeTagValue(out, tag, Long.toString(value));
}
- private void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
+ static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
throws IOException, XmlPullParserException {
if (bundle == null) return;
@@ -683,22 +468,22 @@
out.endTag(null, tag);
}
- private void writeAttr(XmlSerializer out, String name, String value) throws IOException {
+ static void writeAttr(XmlSerializer out, String name, String value) throws IOException {
if (TextUtils.isEmpty(value)) return;
out.attribute(null, name, value);
}
- private void writeAttr(XmlSerializer out, String name, long value) throws IOException {
+ static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
writeAttr(out, name, String.valueOf(value));
}
- private void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
+ static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
if (comp == null) return;
writeAttr(out, name, comp.flattenToString());
}
- private void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
+ static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
if (intent == null) return;
writeAttr(out, name, intent.toUri(/* flags =*/ 0));
@@ -807,22 +592,7 @@
final String packageName = packages.keyAt(i);
final PackageShortcuts packageShortcuts = packages.valueAt(i);
- // TODO Move this to PackageShortcuts.
-
- out.startTag(null, TAG_PACKAGE);
-
- writeAttr(out, ATTR_NAME, packageName);
- writeAttr(out, ATTR_DYNAMIC_COUNT, packageShortcuts.mDynamicShortcutCount);
- writeAttr(out, ATTR_CALL_COUNT, packageShortcuts.mApiCallCount);
- writeAttr(out, ATTR_LAST_RESET, packageShortcuts.mLastResetTime);
-
- final ArrayMap<String, ShortcutInfo> shortcuts = packageShortcuts.mShortcuts;
- final int size = shortcuts.size();
- for (int j = 0; j < size; j++) {
- saveShortcut(out, shortcuts.valueAt(j));
- }
-
- out.endTag(null, TAG_PACKAGE);
+ packageShortcuts.saveToXml(out);
}
// Epilogue.
@@ -837,28 +607,7 @@
}
}
- private void saveShortcut(XmlSerializer out, ShortcutInfo si)
- throws IOException, XmlPullParserException {
- out.startTag(null, TAG_SHORTCUT);
- writeAttr(out, ATTR_ID, si.getId());
- // writeAttr(out, "package", si.getPackageName()); // not needed
- writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent());
- // writeAttr(out, "icon", si.getIcon()); // We don't save it.
- writeAttr(out, ATTR_TITLE, si.getTitle());
- writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
- writeAttr(out, ATTR_WEIGHT, si.getWeight());
- writeAttr(out, ATTR_TIMESTAMP, si.getLastChangedTimestamp());
- writeAttr(out, ATTR_FLAGS, si.getFlags());
- writeAttr(out, ATTR_ICON_RES, si.getIconResourceId());
- writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
-
- writeTagExtra(out, TAG_INTENT_EXTRAS, si.getIntentPersistableExtras());
- writeTagExtra(out, TAG_EXTRAS, si.getExtras());
-
- out.endTag(null, TAG_SHORTCUT);
- }
-
- private static IOException throwForInvalidTag(int depth, String tag) throws IOException {
+ static IOException throwForInvalidTag(int depth, String tag) throws IOException {
throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
}
@@ -868,7 +617,6 @@
if (DEBUG) {
Slog.i(TAG, "Loading from " + path);
}
- path.mkdirs();
final AtomicFile file = new AtomicFile(path);
final FileInputStream in;
@@ -880,12 +628,11 @@
}
return null;
}
- final ArrayMap<String, PackageShortcuts> ret = new ArrayMap<String, PackageShortcuts>();
+ final ArrayMap<String, PackageShortcuts> ret = new ArrayMap<>();
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, StandardCharsets.UTF_8.name());
- String packageName = null;
PackageShortcuts shortcuts = null;
int type;
@@ -895,8 +642,6 @@
}
final int depth = parser.getDepth();
- // TODO Move some of this to PackageShortcuts.
-
final String tag = parser.getName();
if (DEBUG_LOAD) {
Slog.d(TAG, String.format("depth=%d type=%d name=%s",
@@ -912,27 +657,8 @@
case 2: {
switch (tag) {
case TAG_PACKAGE:
- packageName = parseStringAttribute(parser, ATTR_NAME);
- shortcuts = new PackageShortcuts(userId, packageName);
- ret.put(packageName, shortcuts);
-
- shortcuts.mDynamicShortcutCount =
- (int) parseLongAttribute(parser, ATTR_DYNAMIC_COUNT);
- shortcuts.mApiCallCount =
- (int) parseLongAttribute(parser, ATTR_CALL_COUNT);
- shortcuts.mLastResetTime = parseLongAttribute(parser,
- ATTR_LAST_RESET);
- continue;
- }
- break;
- }
- case 3: {
- switch (tag) {
- case TAG_SHORTCUT:
- final ShortcutInfo si = parseShortcut(parser, packageName);
-
- // Don't use addShortcut(), we don't need to save the icon.
- shortcuts.mShortcuts.put(si.getId(), si);
+ shortcuts = PackageShortcuts.loadFromXml(parser, userId);
+ ret.put(shortcuts.mPackageName, shortcuts);
continue;
}
break;
@@ -949,60 +675,6 @@
}
}
- private ShortcutInfo parseShortcut(XmlPullParser parser, String packgeName)
- throws IOException, XmlPullParserException {
- String id;
- ComponentName activityComponent;
- Icon icon;
- String title;
- Intent intent;
- PersistableBundle intentPersistableExtras = null;
- int weight;
- PersistableBundle extras = null;
- long lastChangedTimestamp;
- int flags;
- int iconRes;
- String bitmapPath;
-
- id = parseStringAttribute(parser, ATTR_ID);
- activityComponent = parseComponentNameAttribute(parser, ATTR_ACTIVITY);
- title = parseStringAttribute(parser, ATTR_TITLE);
- intent = parseIntentAttribute(parser, ATTR_INTENT);
- weight = (int) parseLongAttribute(parser, ATTR_WEIGHT);
- lastChangedTimestamp = (int) parseLongAttribute(parser, ATTR_TIMESTAMP);
- flags = (int) parseLongAttribute(parser, ATTR_FLAGS);
- iconRes = (int) parseLongAttribute(parser, ATTR_ICON_RES);
- bitmapPath = parseStringAttribute(parser, ATTR_BITMAP_PATH);
-
- final int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
- final int depth = parser.getDepth();
- final String tag = parser.getName();
- if (DEBUG_LOAD) {
- Slog.d(TAG, String.format(" depth=%d type=%d name=%s",
- depth, type, tag));
- }
- switch (tag) {
- case TAG_INTENT_EXTRAS:
- intentPersistableExtras = PersistableBundle.restoreFromXml(parser);
- continue;
- case TAG_EXTRAS:
- extras = PersistableBundle.restoreFromXml(parser);
- continue;
- }
- throw throwForInvalidTag(depth, tag);
- }
- return new ShortcutInfo(
- id, packgeName, activityComponent, /* icon =*/ null, title, intent,
- intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
- iconRes, bitmapPath);
- }
-
// TODO Actually make it async.
private void scheduleSaveBaseState() {
synchronized (mLock) {
@@ -1764,16 +1436,20 @@
@Override
public ParcelFileDescriptor getShortcutIconFd(@NonNull String callingPackage,
- @NonNull ShortcutInfo shortcut, int userId) {
- Preconditions.checkNotNull(shortcut, "shortcut");
+ @NonNull ShortcutInfo shortcutIn, int userId) {
+ Preconditions.checkNotNull(shortcutIn, "shortcut");
synchronized (mLock) {
final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
- shortcut.getPackageName(), userId).findShortcutById(shortcut.getId());
+ shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId());
if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
return null;
}
try {
+ if (shortcutInfo.getBitmapPath() == null) {
+ Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
+ return null;
+ }
return ParcelFileDescriptor.open(
new File(shortcutInfo.getBitmapPath()),
ParcelFileDescriptor.MODE_READ_ONLY);
@@ -1865,46 +1541,10 @@
return;
}
- pw.print(" Package: ");
- pw.print(packageName);
- pw.println();
-
- pw.print(" Calls: ");
- pw.print(packageShortcuts.getApiCallCount(this));
- pw.println();
-
- // This should be after getApiCallCount(), which may update it.
- pw.print(" Last reset: [");
- pw.print(packageShortcuts.mLastResetTime);
- pw.print("] ");
- pw.print(formatTime(packageShortcuts.mLastResetTime));
- pw.println();
-
- pw.println(" Shortcuts:");
- long totalBitmapSize = 0;
- final ArrayMap<String, ShortcutInfo> shortcuts = packageShortcuts.mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
- pw.print(" ");
- pw.println(si.toInsecureString());
- if (si.hasIconFile()) {
- final long len = new File(si.getBitmapPath()).length();
- pw.print(" ");
- pw.print("bitmap size=");
- pw.println(len);
-
- totalBitmapSize += len;
- }
- }
- pw.print(" Total bitmap size: ");
- pw.print(totalBitmapSize);
- pw.print(" (");
- pw.print(Formatter.formatFileSize(mContext, totalBitmapSize));
- pw.println(")");
+ packageShortcuts.dump(this, pw, " ");
}
- private static String formatTime(long time) {
+ static String formatTime(long time) {
Time tobj = new Time();
tobj.set(time);
return tobj.format("%Y-%m-%d %H:%M:%S");
@@ -2087,4 +1727,407 @@
int getIconPersistQualityForTest() {
return mIconPersistQuality;
}
+
+ @VisibleForTesting
+ ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
+ synchronized (mLock) {
+ return getPackageShortcutsLocked(packageName, userId).findShortcutById(shortcutId);
+ }
+ }
+}
+
+/**
+ * All the information relevant to shortcuts from a single package (per-user).
+ */
+class PackageShortcuts {
+ private static final String TAG = ShortcutService.TAG;
+
+ @UserIdInt
+ final int mUserId;
+
+ @NonNull
+ final String mPackageName;
+
+ /**
+ * All the shortcuts from the package, keyed on IDs.
+ */
+ final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
+
+ /**
+ * # of dynamic shortcuts.
+ */
+ private int mDynamicShortcutCount = 0;
+
+ /**
+ * # of times the package has called rate-limited APIs.
+ */
+ private int mApiCallCount;
+
+ /**
+ * When {@link #mApiCallCount} was reset last time.
+ */
+ private long mLastResetTime;
+
+ PackageShortcuts(int userId, String packageName) {
+ mUserId = userId;
+ mPackageName = packageName;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ public ShortcutInfo findShortcutById(String id) {
+ return mShortcuts.get(id);
+ }
+
+ private ShortcutInfo deleteShortcut(@NonNull ShortcutService s,
+ @NonNull String id) {
+ final ShortcutInfo shortcut = mShortcuts.remove(id);
+ if (shortcut != null) {
+ s.removeIcon(mUserId, shortcut);
+ shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
+ }
+ return shortcut;
+ }
+
+ void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) {
+ deleteShortcut(s, newShortcut.getId());
+ s.saveIconAndFixUpShortcut(mUserId, newShortcut);
+ mShortcuts.put(newShortcut.getId(), newShortcut);
+ }
+
+ /**
+ * Add a shortcut, or update one with the same ID, with taking over existing flags.
+ *
+ * It checks the max number of dynamic shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void updateShortcutWithCapping(@NonNull ShortcutService s,
+ @NonNull ShortcutInfo newShortcut) {
+ final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
+
+ int oldFlags = 0;
+ int newDynamicCount = mDynamicShortcutCount;
+
+ if (oldShortcut != null) {
+ oldFlags = oldShortcut.getFlags();
+ if (oldShortcut.isDynamic()) {
+ newDynamicCount--;
+ }
+ }
+ if (newShortcut.isDynamic()) {
+ newDynamicCount++;
+ }
+ // Make sure there's still room.
+ s.enforceMaxDynamicShortcuts(newDynamicCount);
+
+ // Okay, make it dynamic and add.
+ newShortcut.addFlags(oldFlags);
+
+ addShortcut(s, newShortcut);
+ mDynamicShortcutCount = newDynamicCount;
+ }
+
+ /**
+ * Remove all shortcuts that aren't pinned nor dynamic.
+ */
+ private void removeOrphans(@NonNull ShortcutService s) {
+ ArrayList<String> removeList = null; // Lazily initialize.
+
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+
+ if (si.isPinned() || si.isDynamic()) continue;
+
+ if (removeList == null) {
+ removeList = new ArrayList<>();
+ }
+ removeList.add(si.getId());
+ }
+ if (removeList != null) {
+ for (int i = removeList.size() - 1 ; i >= 0; i--) {
+ deleteShortcut(s, removeList.get(i));
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ public void deleteAllDynamicShortcuts(@NonNull ShortcutService s) {
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ }
+ removeOrphans(s);
+ mDynamicShortcutCount = 0;
+ }
+
+ @GuardedBy("mLock")
+ public void deleteDynamicWithId(@NonNull ShortcutService s, @NonNull String shortcutId) {
+ final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
+
+ if (oldShortcut == null) {
+ return;
+ }
+ if (oldShortcut.isDynamic()) {
+ mDynamicShortcutCount--;
+ }
+ if (oldShortcut.isPinned()) {
+ oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ } else {
+ deleteShortcut(s, shortcutId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ public void replacePinned(@NonNull ShortcutService s, String launcherPackage,
+ List<String> shortcutIds) {
+
+ // TODO Should be per launcherPackage.
+
+ // First, un-pin all shortcuts
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED);
+ }
+
+ // Then pin ALL
+ for (int i = shortcutIds.size() - 1; i >= 0; i--) {
+ final ShortcutInfo shortcut = mShortcuts.get(shortcutIds.get(i));
+ if (shortcut != null) {
+ shortcut.addFlags(ShortcutInfo.FLAG_PINNED);
+ }
+ }
+
+ removeOrphans(s);
+ }
+
+ /**
+ * Number of calls that the caller has made, since the last reset.
+ */
+ @GuardedBy("mLock")
+ public int getApiCallCount(@NonNull ShortcutService s) {
+ final long last = s.getLastResetTimeLocked();
+
+ final long now = s.injectCurrentTimeMillis();
+ if (mLastResetTime > now) {
+ // Clock rewound. // TODO Test it
+ mLastResetTime = now;
+ }
+
+ // If not reset yet, then reset.
+ if (mLastResetTime < last) {
+ mApiCallCount = 0;
+ mLastResetTime = last;
+ }
+ return mApiCallCount;
+ }
+
+ /**
+ * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount}
+ * and return true. Otherwise just return false.
+ */
+ @GuardedBy("mLock")
+ public boolean tryApiCall(@NonNull ShortcutService s) {
+ if (getApiCallCount(s) >= s.mMaxDailyUpdates) {
+ return false;
+ }
+ mApiCallCount++;
+ return true;
+ }
+
+ @GuardedBy("mLock")
+ public void resetRateLimitingForCommandLine() {
+ mApiCallCount = 0;
+ mLastResetTime = 0;
+ }
+
+ /**
+ * Find all shortcuts that match {@code query}.
+ */
+ @GuardedBy("mLock")
+ public void findAll(@NonNull List<ShortcutInfo> result,
+ @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
+ for (int i = 0; i < mShortcuts.size(); i++) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+ if (query == null || query.test(si)) {
+ result.add(si.clone(cloneFlag));
+ }
+ }
+ }
+
+ public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.print(prefix);
+ pw.print("Package: ");
+ pw.print(mPackageName);
+ pw.println();
+
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print("Calls: ");
+ pw.print(getApiCallCount(s));
+ pw.println();
+
+ // This should be after getApiCallCount(), which may update it.
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print("Last reset: [");
+ pw.print(mLastResetTime);
+ pw.print("] ");
+ pw.print(s.formatTime(mLastResetTime));
+ pw.println();
+
+ pw.println(" Shortcuts:");
+ long totalBitmapSize = 0;
+ final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
+ final int size = shortcuts.size();
+ for (int i = 0; i < size; i++) {
+ final ShortcutInfo si = shortcuts.valueAt(i);
+ pw.print(" ");
+ pw.println(si.toInsecureString());
+ if (si.getBitmapPath() != null) {
+ final long len = new File(si.getBitmapPath()).length();
+ pw.print(" ");
+ pw.print("bitmap size=");
+ pw.println(len);
+
+ totalBitmapSize += len;
+ }
+ }
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print("Total bitmap size: ");
+ pw.print(totalBitmapSize);
+ pw.print(" (");
+ pw.print(Formatter.formatFileSize(s.mContext, totalBitmapSize));
+ pw.println(")");
+ }
+
+ public void saveToXml(@NonNull XmlSerializer out) throws IOException, XmlPullParserException {
+ out.startTag(null, ShortcutService.TAG_PACKAGE);
+
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_NAME, mPackageName);
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_DYNAMIC_COUNT, mDynamicShortcutCount);
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_CALL_COUNT, mApiCallCount);
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_LAST_RESET, mLastResetTime);
+
+ final int size = mShortcuts.size();
+ for (int j = 0; j < size; j++) {
+ saveShortcut(out, mShortcuts.valueAt(j));
+ }
+
+ out.endTag(null, ShortcutService.TAG_PACKAGE);
+ }
+
+ private static void saveShortcut(XmlSerializer out, ShortcutInfo si)
+ throws IOException, XmlPullParserException {
+ out.startTag(null, ShortcutService.TAG_SHORTCUT);
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_ID, si.getId());
+ // writeAttr(out, "package", si.getPackageName()); // not needed
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_ACTIVITY, si.getActivityComponent());
+ // writeAttr(out, "icon", si.getIcon()); // We don't save it.
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_TITLE, si.getTitle());
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_INTENT, si.getIntentNoExtras());
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_WEIGHT, si.getWeight());
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_TIMESTAMP,
+ si.getLastChangedTimestamp());
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_FLAGS, si.getFlags());
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_ICON_RES, si.getIconResourceId());
+ ShortcutService.writeAttr(out, ShortcutService.ATTR_BITMAP_PATH, si.getBitmapPath());
+
+ ShortcutService.writeTagExtra(out, ShortcutService.TAG_INTENT_EXTRAS,
+ si.getIntentPersistableExtras());
+ ShortcutService.writeTagExtra(out, ShortcutService.TAG_EXTRAS, si.getExtras());
+
+ out.endTag(null, ShortcutService.TAG_SHORTCUT);
+ }
+
+ public static PackageShortcuts loadFromXml(XmlPullParser parser, int userId)
+ throws IOException, XmlPullParserException {
+
+ final String packageName = ShortcutService.parseStringAttribute(parser,
+ ShortcutService.ATTR_NAME);
+
+ final PackageShortcuts ret = new PackageShortcuts(userId, packageName);
+
+ ret.mDynamicShortcutCount =
+ ShortcutService.parseIntAttribute(parser, ShortcutService.ATTR_DYNAMIC_COUNT);
+ ret.mApiCallCount =
+ ShortcutService.parseIntAttribute(parser, ShortcutService.ATTR_CALL_COUNT);
+ ret.mLastResetTime =
+ ShortcutService.parseLongAttribute(parser, ShortcutService.ATTR_LAST_RESET);
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+ final String tag = parser.getName();
+ switch (tag) {
+ case ShortcutService.TAG_SHORTCUT:
+ final ShortcutInfo si = parseShortcut(parser, packageName);
+
+ // Don't use addShortcut(), we don't need to save the icon.
+ ret.mShortcuts.put(si.getId(), si);
+ continue;
+ }
+ throw ShortcutService.throwForInvalidTag(depth, tag);
+ }
+ return ret;
+ }
+
+ private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName)
+ throws IOException, XmlPullParserException {
+ String id;
+ ComponentName activityComponent;
+ // Icon icon;
+ String title;
+ Intent intent;
+ PersistableBundle intentPersistableExtras = null;
+ int weight;
+ PersistableBundle extras = null;
+ long lastChangedTimestamp;
+ int flags;
+ int iconRes;
+ String bitmapPath;
+
+ id = ShortcutService.parseStringAttribute(parser, ShortcutService.ATTR_ID);
+ activityComponent = ShortcutService.parseComponentNameAttribute(parser,
+ ShortcutService.ATTR_ACTIVITY);
+ title = ShortcutService.parseStringAttribute(parser, ShortcutService.ATTR_TITLE);
+ intent = ShortcutService.parseIntentAttribute(parser, ShortcutService.ATTR_INTENT);
+ weight = (int) ShortcutService.parseLongAttribute(parser, ShortcutService.ATTR_WEIGHT);
+ lastChangedTimestamp = (int) ShortcutService.parseLongAttribute(parser,
+ ShortcutService.ATTR_TIMESTAMP);
+ flags = (int) ShortcutService.parseLongAttribute(parser, ShortcutService.ATTR_FLAGS);
+ iconRes = (int) ShortcutService.parseLongAttribute(parser, ShortcutService.ATTR_ICON_RES);
+ bitmapPath = ShortcutService.parseStringAttribute(parser, ShortcutService.ATTR_BITMAP_PATH);
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+ final String tag = parser.getName();
+ if (ShortcutService.DEBUG_LOAD) {
+ Slog.d(TAG, String.format(" depth=%d type=%d name=%s",
+ depth, type, tag));
+ }
+ switch (tag) {
+ case ShortcutService.TAG_INTENT_EXTRAS:
+ intentPersistableExtras = PersistableBundle.restoreFromXml(parser);
+ continue;
+ case ShortcutService.TAG_EXTRAS:
+ extras = PersistableBundle.restoreFromXml(parser);
+ continue;
+ }
+ throw ShortcutService.throwForInvalidTag(depth, tag);
+ }
+ return new ShortcutInfo(
+ id, packageName, activityComponent, /* icon =*/ null, title, intent,
+ intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
+ iconRes, bitmapPath);
+ }
}
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 5f67b7f..e2c71a1 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -1087,8 +1087,25 @@
const char *const JavaMethodHelper<bool>::signature_ = "(Z)V";
#define SET(setter, value) object.callSetter("set" # setter, (value))
-#define SET_IF(flag, setter, value) \
- if (flags & (flag)) object.callSetter("set" # setter, (value))
+
+// If you want to check if a flag is not set, use SET_IF_NOT(FLAG, setter,
+// value) to do that. SET_IF(!FLAG, setter, value) won't compile.
+//
+// This macros generates compilation error if the provided 'flag' is not a
+// single token. For example, 'GNSS_CLOCK_HAS_BIAS' can be accepted, but
+// '!GNSS_CLOCK_HAS_DRIFT' will fail to compile.
+#define SET_IF(flag, setter, value) do { \
+ if (flags & flag) { \
+ JavaObject& name_check_##flag = object; \
+ name_check_##flag.callSetter("set" # setter, (value)); \
+ } \
+ } while (false)
+#define SET_IF_NOT(flag, setter, value) do { \
+ if (!(flags & flag)) { \
+ JavaObject& name_check_##flag = object; \
+ name_check_##flag.callSetter("set" # setter, (value)); \
+ } \
+ } while (false)
static jobject translate_gps_clock(JNIEnv* env, GpsClock* clock) {
static uint32_t discontinuity_count_to_handle_old_lock_type = 0;
@@ -1209,9 +1226,9 @@
static_cast<int32_t>(measurement->multipath_indicator));
SET_IF(GNSS_MEASUREMENT_HAS_SNR, SnrInDb, measurement->snr_db);
- SET_IF(!GPS_MEASUREMENT_HAS_UNCORRECTED_PSEUDORANGE_RATE,
- PseudorangeRateCorrected,
- true);
+ SET_IF_NOT(GPS_MEASUREMENT_HAS_UNCORRECTED_PSEUDORANGE_RATE,
+ PseudorangeRateCorrected,
+ true);
return object.get();
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d218afc..ea1a569 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4862,10 +4862,10 @@
* {@link DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE}.
*/
private int getEncryptionStatus() {
- if (!StorageManager.isNonDefaultBlockEncrypted()) {
- return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY;
- } else if (StorageManager.isEncrypted()) {
- return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
+ if (StorageManager.isEncrypted()) {
+ return StorageManager.isNonDefaultBlockEncrypted() ?
+ DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE
+ : DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY;
} else if (StorageManager.isEncryptable()) {
return DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE;
} else {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
index 036cc66..2f4beaa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -85,7 +85,7 @@
* Whether to enable dump or not. Should be only true when debugging to avoid bugs where
* dump affecting the behavior.
*/
- private static final boolean ENABLE_DUMP = true; // DO NOT SUBMIT WITH true
+ private static final boolean ENABLE_DUMP = false; // DO NOT SUBMIT WITH true
/** Context used in the client side */
private final class ClientContext extends MockContext {
@@ -145,7 +145,7 @@
@Override
int injectGetPackageUid(String packageName, int userId) {
Integer uid = mInjectedPackageUidMap.get(packageName);
- return uid != null ? uid : -1;
+ return UserHandle.getUid(getCallingUserId(), (uid != null ? uid : 0));
}
@Override
@@ -224,6 +224,9 @@
private static final String LAUNCHER_2 = "com.android.launcher.2";
private static final int LAUNCHER_UID_2 = 10012;
+ private static final int USER_10 = 10;
+ private static final int USER_11 = 11;
+
private static final long START_TIME = 1234560000000L;
private static final long INTERVAL = 10000;
@@ -282,16 +285,32 @@
}
/** Replace the current calling package */
- private void setCaller(String packageName) {
+ private void setCaller(String packageName, int userId) {
mInjectedClientPackage = packageName;
- mInjectedCallingUid = Preconditions.checkNotNull(mInjectedPackageUidMap.get(packageName),
- "Unknown package");
+ mInjectedCallingUid = UserHandle.getUid(userId,
+ Preconditions.checkNotNull(mInjectedPackageUidMap.get(packageName),
+ "Unknown package"));
+ }
+
+ private void setCaller(String packageName) {
+ setCaller(packageName, UserHandle.USER_SYSTEM);
}
private String getCallingPackage() {
return mInjectedClientPackage;
}
+ private void runWithCaller(String packageName, int userId, Runnable r) {
+ final String previousPackage = mInjectedClientPackage;
+ final int previousUid = mInjectedCallingUid;
+
+ setCaller(packageName, userId);
+
+ r.run();
+
+ setCaller(previousPackage, previousUid);
+ }
+
private int getCallingUserId() {
return UserHandle.getUserId(mInjectedCallingUid);
}
@@ -565,6 +584,15 @@
}
@NonNull
+ private List<ShortcutInfo> assertAllHaveIcon(
+ @NonNull List<ShortcutInfo> actualShortcuts) {
+ for (ShortcutInfo s : actualShortcuts) {
+ assertTrue("ID " + s.getId(), s.hasIconFile() || s.hasIconResource());
+ }
+ return actualShortcuts;
+ }
+
+ @NonNull
private List<ShortcutInfo> assertAllHaveFlags(@NonNull List<ShortcutInfo> actualShortcuts,
int shortcutFlags) {
for (ShortcutInfo s : actualShortcuts) {
@@ -635,6 +663,18 @@
}
}
+ private ShortcutInfo getPackageShortcut(String packageName, String shortcutId, int userId) {
+ return mService.getPackageShortcutForTest(packageName, shortcutId, userId);
+ }
+
+ private ShortcutInfo getPackageShortcut(String packageName, String shortcutId) {
+ return getPackageShortcut(packageName, shortcutId, getCallingUserId());
+ }
+
+ private ShortcutInfo getCallerShortcut(String shortcutId) {
+ return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId());
+ }
+
/**
* Test for the first launch path, no settings file available.
*/
@@ -1083,7 +1123,8 @@
"res64x64",
"none");
- dumpsysOnLogcat();
+ // Re-initialize and load from the files.
+ initService();
// Load from launcher.
Bitmap bmp;
@@ -1568,35 +1609,87 @@
*/
public void testSaveAndLoadUser() {
// First, create some shortcuts and save.
- final Icon icon1 = Icon.createWithResource(mContext, R.drawable.icon1);
- final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- mContext.getResources(), R.drawable.icon2));
+ runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+ final Icon icon1 = Icon.createWithResource(mContext, R.drawable.black_64x16);
+ final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), R.drawable.icon2));
- final ShortcutInfo si1 = makeShortcut(
- "shortcut1",
- "Title 1",
- makeComponent(ShortcutActivity.class),
- icon1,
- makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
- "key1", "val1", "nest", makeBundle("key", 123)),
- /* weight */ 10);
+ final ShortcutInfo si1 = makeShortcut(
+ "s1",
+ "title1-1",
+ makeComponent(ShortcutActivity.class),
+ icon1,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+ "key1", "val1", "nest", makeBundle("key", 123)),
+ /* weight */ 10);
- final ShortcutInfo si2 = makeShortcut(
- "shortcut2",
- "Title 2",
- /* activity */ null,
- icon2,
- makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
- /* weight */ 12);
+ final ShortcutInfo si2 = makeShortcut(
+ "s2",
+ "title1-2",
+ /* activity */ null,
+ icon2,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+ /* weight */ 12);
- assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2)));
+ assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2)));
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
- assertEquals(2, mManager.getRemainingCallCount());
+ assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+ assertEquals(2, mManager.getRemainingCallCount());
+ });
+ runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+ final Icon icon1 = Icon.createWithResource(mContext, R.drawable.black_16x64);
+ final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), R.drawable.icon2));
- Log.i(TAG, "Saved state");
- dumpsysOnLogcat();
- dumpUserFile(0);
+ final ShortcutInfo si1 = makeShortcut(
+ "s1",
+ "title2-1",
+ makeComponent(ShortcutActivity.class),
+ icon1,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+ "key1", "val1", "nest", makeBundle("key", 123)),
+ /* weight */ 10);
+
+ final ShortcutInfo si2 = makeShortcut(
+ "s2",
+ "title2-2",
+ /* activity */ null,
+ icon2,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+ /* weight */ 12);
+
+ assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2)));
+
+ assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+ assertEquals(2, mManager.getRemainingCallCount());
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ final Icon icon1 = Icon.createWithResource(mContext, R.drawable.black_64x64);
+ final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), R.drawable.icon2));
+
+ final ShortcutInfo si1 = makeShortcut(
+ "s1",
+ "title10-1-1",
+ makeComponent(ShortcutActivity.class),
+ icon1,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+ "key1", "val1", "nest", makeBundle("key", 123)),
+ /* weight */ 10);
+
+ final ShortcutInfo si2 = makeShortcut(
+ "s2",
+ "title10-1-2",
+ /* activity */ null,
+ icon2,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+ /* weight */ 12);
+
+ assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2)));
+
+ assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+ assertEquals(2, mManager.getRemainingCallCount());
+ });
// Restore.
initService();
@@ -1610,26 +1703,40 @@
// Now it's loaded.
assertEquals(1, mService.getShortcutsForTest().size());
+ runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+ assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+ mManager.getDynamicShortcuts()))), "s1", "s2");
+ assertEquals(2, mManager.getRemainingCallCount());
+
+ assertEquals("title1-1", getCallerShortcut("s1").getTitle());
+ assertEquals("title1-2", getCallerShortcut("s2").getTitle());
+ });
+ runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+ assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+ mManager.getDynamicShortcuts()))), "s1", "s2");
+ assertEquals(2, mManager.getRemainingCallCount());
+
+ assertEquals("title2-1", getCallerShortcut("s1").getTitle());
+ assertEquals("title2-2", getCallerShortcut("s2").getTitle());
+ });
+
// Start another user
- mService.onStartUserLocked(10);
+ mService.onStartUserLocked(USER_10);
// Now the size is 2.
assertEquals(2, mService.getShortcutsForTest().size());
- Log.i(TAG, "Dumping the new instance");
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+ mManager.getDynamicShortcuts()))), "s1", "s2");
+ assertEquals(2, mManager.getRemainingCallCount());
- List<ShortcutInfo> loaded = mManager.getDynamicShortcuts();
-
- Log.i(TAG, "Loaded state");
- dumpsysOnLogcat();
-
- assertEquals(2, loaded.size());
-
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
- assertEquals(2, mManager.getRemainingCallCount());
+ assertEquals("title10-1-1", getCallerShortcut("s1").getTitle());
+ assertEquals("title10-1-2", getCallerShortcut("s2").getTitle());
+ });
// Try stopping the user
- mService.onCleanupUserInner(UserHandle.USER_SYSTEM);
+ mService.onCleanupUserInner(USER_10);
// Now it's unloaded.
assertEquals(1, mService.getShortcutsForTest().size());
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 605e0d3..8afb455 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1442,7 +1442,7 @@
/**
* Creates the {@link Intent} which can be used with {@link Context#startActivity(Intent)} to
* launch the activity to manage blocked numbers.
- * <p> This method displays the UI to manage blocked numbers only if
+ * <p> The activity will display the UI to manage blocked numbers only if
* {@link android.provider.BlockedNumberContract#canCurrentUserBlockNumbers(Context)} returns
* {@code true} for the current user.
*/
diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs
index caa947d..0a1742e 100644
--- a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs
+++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs
@@ -14,10 +14,14 @@
for (int x = 0; x < HEIGHT; x += REGION_SIZE) {
bool interestingRegion = false;
- int regionColor = (int) rsGetElementAt_uchar4(ideal, x, y);
+ uchar4 regionColor = rsGetElementAt_uchar4(ideal, x, y);
for (int i = 0; i < REGION_SIZE && !interestingRegion; i++) {
for (int j = 0; j < REGION_SIZE && !interestingRegion; j++) {
- interestingRegion |= ((int) rsGetElementAt_uchar4(ideal, x + j, y + i)) != regionColor;
+ uchar4 testVal = rsGetElementAt_uchar4(ideal, x + j, y + i);
+ interestingRegion |= (testVal.r != regionColor.r);
+ interestingRegion |= (testVal.g != regionColor.g);
+ interestingRegion |= (testVal.b != regionColor.b);
+ interestingRegion |= (testVal.a != regionColor.a);
}
}
if (interestingRegion) {