Merge "SoundPool: add lock for findSample access from SoundPoolThread"
diff --git a/Android.mk b/Android.mk
index f5d5a11..d22273c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -261,6 +261,7 @@
core/java/android/view/IApplicationToken.aidl \
core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
core/java/android/view/IAssetAtlas.aidl \
+ core/java/android/view/IDockDividerVisibilityListener.aidl \
core/java/android/view/IGraphicsStats.aidl \
core/java/android/view/IInputFilter.aidl \
core/java/android/view/IInputFilterHost.aidl \
diff --git a/api/current.txt b/api/current.txt
index aea7f67..7ddd5af 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7809,6 +7809,7 @@
method public abstract java.lang.String getPackageResourcePath();
method public abstract android.content.res.Resources getResources();
method public abstract android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public final java.lang.String getString(int);
method public final java.lang.String getString(int, java.lang.Object...);
method public abstract java.lang.Object getSystemService(java.lang.String);
@@ -7991,6 +7992,7 @@
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public java.lang.Object getSystemService(java.lang.String);
method public java.lang.String getSystemServiceName(java.lang.Class<?>);
method public android.content.res.Resources.Theme getTheme();
@@ -8344,6 +8346,7 @@
field public static final java.lang.String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND";
field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
+ field public static final java.lang.String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
@@ -13880,7 +13883,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
field public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
- field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> SENSOR_DYNAMIC_BLACK_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_EXPOSURE_TIME;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_FRAME_DURATION;
@@ -28861,6 +28864,8 @@
method public boolean isFailed();
method public boolean isQueued();
method public boolean isStarted();
+ method public void setProgress(float);
+ method public void setStatus(java.lang.CharSequence);
method public boolean setTag(java.lang.String);
method public boolean start();
}
@@ -31403,7 +31408,10 @@
public static final class Telephony.Sms.Intents {
method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+ field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+ field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+ field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
@@ -36197,6 +36205,7 @@
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public java.lang.Object getSystemService(java.lang.String);
method public java.lang.String getSystemServiceName(java.lang.Class<?>);
method public android.content.res.Resources.Theme getTheme();
diff --git a/api/system-current.txt b/api/system-current.txt
index a8b054a..edc9f18 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8052,6 +8052,7 @@
method public abstract java.lang.String getPackageResourcePath();
method public abstract android.content.res.Resources getResources();
method public abstract android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public final java.lang.String getString(int);
method public final java.lang.String getString(int, java.lang.Object...);
method public abstract java.lang.Object getSystemService(java.lang.String);
@@ -8243,6 +8244,7 @@
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public java.lang.Object getSystemService(java.lang.String);
method public java.lang.String getSystemServiceName(java.lang.Class<?>);
method public android.content.res.Resources.Theme getTheme();
@@ -8602,6 +8604,7 @@
field public static final java.lang.String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND";
field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
+ field public static final java.lang.String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
@@ -14228,7 +14231,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
field public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
- field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> SENSOR_DYNAMIC_BLACK_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_EXPOSURE_TIME;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_FRAME_DURATION;
@@ -30844,6 +30847,8 @@
method public boolean isFailed();
method public boolean isQueued();
method public boolean isStarted();
+ method public void setProgress(float);
+ method public void setStatus(java.lang.CharSequence);
method public boolean setTag(java.lang.String);
method public boolean start();
}
@@ -33519,7 +33524,10 @@
public static final class Telephony.Sms.Intents {
method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+ field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+ field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+ field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
@@ -38512,6 +38520,7 @@
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public java.lang.Object getSystemService(java.lang.String);
method public java.lang.String getSystemServiceName(java.lang.Class<?>);
method public android.content.res.Resources.Theme getTheme();
diff --git a/api/test-current.txt b/api/test-current.txt
index aea7f67..4838702 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -7809,6 +7809,7 @@
method public abstract java.lang.String getPackageResourcePath();
method public abstract android.content.res.Resources getResources();
method public abstract android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public final java.lang.String getString(int);
method public final java.lang.String getString(int, java.lang.Object...);
method public abstract java.lang.Object getSystemService(java.lang.String);
@@ -7991,6 +7992,7 @@
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public java.lang.Object getSystemService(java.lang.String);
method public java.lang.String getSystemServiceName(java.lang.Class<?>);
method public android.content.res.Resources.Theme getTheme();
@@ -8344,6 +8346,7 @@
field public static final java.lang.String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND";
field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
+ field public static final java.lang.String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
@@ -13880,7 +13883,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
field public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
- field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> SENSOR_DYNAMIC_BLACK_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_EXPOSURE_TIME;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_FRAME_DURATION;
@@ -28742,7 +28745,9 @@
method public java.lang.String getLabel();
method public android.print.PageRange[] getPages();
method public android.print.PrinterId getPrinterId();
+ method public float getProgress();
method public int getState();
+ method public java.lang.CharSequence getStatus();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.print.PrintJobInfo> CREATOR;
field public static final int STATE_BLOCKED = 4; // 0x4
@@ -28861,6 +28866,8 @@
method public boolean isFailed();
method public boolean isQueued();
method public boolean isStarted();
+ method public void setProgress(float);
+ method public void setStatus(java.lang.CharSequence);
method public boolean setTag(java.lang.String);
method public boolean start();
}
@@ -31403,7 +31410,10 @@
public static final class Telephony.Sms.Intents {
method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+ field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+ field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+ field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
@@ -36197,6 +36207,7 @@
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public java.lang.Object getSystemService(java.lang.String);
method public java.lang.String getSystemServiceName(java.lang.Class<?>);
method public android.content.res.Resources.Theme getTheme();
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 273483a..468c145 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -178,15 +178,8 @@
* </p>
* <h3>Notification strategy</h3>
* <p>
- * For each feedback type only one accessibility service is notified. Services are notified
- * in the order of registration. Hence, if two services are registered for the same
- * feedback type in the same package the first one wins. It is possible however, to
- * register a service as the default one for a given feedback type. In such a case this
- * service is invoked if no other service was interested in the event. In other words, default
- * services do not compete with other services and are notified last regardless of the
- * registration order. This enables "generic" accessibility services that work reasonably
- * well with most applications to coexist with "polished" ones that are targeted for
- * specific applications.
+ * All accessibility services are notified of all events they have requested, regardless of their
+ * feedback type.
* </p>
* <p class="note">
* <strong>Note:</strong> The event notification timeout is useful to avoid propagating
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f7aee75..f1a7de8 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3084,7 +3084,7 @@
/** {@hide} */
public static final int FLAG_OR_STOPPED = 1 << 0;
/** {@hide} */
- public static final int FLAG_WITH_AMNESIA = 1 << 1;
+ public static final int FLAG_AND_LOCKED = 1 << 1;
/**
* Return whether the given user is actively running. This means that
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 9081ef8..b24bce3 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -798,21 +798,33 @@
}
}
- private static void setFirstOut(SparseArray<Fragment> fragments, Fragment fragment) {
+ private static void setFirstOut(SparseArray<Fragment> firstOutFragments,
+ SparseArray<Fragment> lastInFragments, Fragment fragment) {
if (fragment != null) {
int containerId = fragment.mContainerId;
- if (containerId != 0 && !fragment.isHidden() && fragment.isAdded() &&
- fragment.getView() != null && fragments.get(containerId) == null) {
- fragments.put(containerId, fragment);
+ if (containerId != 0 && !fragment.isHidden()) {
+ if (fragment.isAdded() && fragment.getView() != null
+ && firstOutFragments.get(containerId) == null) {
+ firstOutFragments.put(containerId, fragment);
+ }
+ if (lastInFragments.get(containerId) == fragment) {
+ lastInFragments.remove(containerId);
+ }
}
}
}
- private void setLastIn(SparseArray<Fragment> fragments, Fragment fragment) {
+ private void setLastIn(SparseArray<Fragment> firstOutFragments,
+ SparseArray<Fragment> lastInFragments, Fragment fragment) {
if (fragment != null) {
int containerId = fragment.mContainerId;
if (containerId != 0) {
- fragments.put(containerId, fragment);
+ if (!fragment.isAdded()) {
+ lastInFragments.put(containerId, fragment);
+ }
+ if (firstOutFragments.get(containerId) == fragment) {
+ firstOutFragments.remove(containerId);
+ }
}
}
}
@@ -835,7 +847,7 @@
while (op != null) {
switch (op.cmd) {
case OP_ADD:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_REPLACE: {
Fragment f = op.fragment;
@@ -845,29 +857,30 @@
if (f == null || old.mContainerId == f.mContainerId) {
if (old == f) {
f = null;
+ lastInFragments.remove(old.mContainerId);
} else {
- setFirstOut(firstOutFragments, old);
+ setFirstOut(firstOutFragments, lastInFragments, old);
}
}
}
}
- setLastIn(lastInFragments, f);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
}
case OP_REMOVE:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_HIDE:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_SHOW:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_DETACH:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_ATTACH:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
}
@@ -889,38 +902,38 @@
if (!mManager.mContainer.onHasView()) {
return; // nothing to see, so no transitions
}
- Op op = mHead;
+ Op op = mTail;
while (op != null) {
switch (op.cmd) {
case OP_ADD:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_REPLACE:
if (op.removed != null) {
for (int i = op.removed.size() - 1; i >= 0; i--) {
- setLastIn(lastInFragments, op.removed.get(i));
+ setLastIn(firstOutFragments, lastInFragments, op.removed.get(i));
}
}
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_REMOVE:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_HIDE:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_SHOW:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_DETACH:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_ATTACH:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
}
- op = op.next;
+ op = op.prev;
}
}
@@ -957,6 +970,7 @@
*/
private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments,
SparseArray<Fragment> lastInFragments, boolean isBack) {
+ ensureFragmentsAreInitialized(lastInFragments);
TransitionState state = new TransitionState();
// Adding a non-existent target view makes sure that the transitions don't target
@@ -982,6 +996,20 @@
return state;
}
+ /**
+ * Ensure that fragments that are entering are at least at the CREATED state
+ * so that they may load Transitions using TransitionInflater.
+ */
+ private void ensureFragmentsAreInitialized(SparseArray<Fragment> lastInFragments) {
+ final int count = lastInFragments.size();
+ for (int i = 0; i < count; i++) {
+ final Fragment fragment = lastInFragments.valueAt(i);
+ if (fragment.mState < Fragment.CREATED) {
+ mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
+ }
+ }
+ }
+
private static Transition cloneTransition(Transition transition) {
if (transition != null) {
transition = transition.clone();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index bc7c3d0..c661107 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -129,7 +129,7 @@
/**
* Map from package name, to preference name, to cached preferences.
*/
- private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs;
+ private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefs;
final ActivityThread mMainThread;
final LoadedApk mPackageInfo;
@@ -327,34 +327,39 @@
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
+ // At least one application in the world actually passes in a null
+ // name. This happened to work because when we generated the file name
+ // we would stringify it to "null.xml". Nice.
+ if (mPackageInfo.getApplicationInfo().targetSdkVersion <
+ Build.VERSION_CODES.KITKAT) {
+ if (name == null) {
+ name = "null";
+ }
+ }
+
+ final File file = getSharedPrefsFile(name);
+ return getSharedPreferences(file, mode);
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
if (sSharedPrefs == null) {
- sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
+ sSharedPrefs = new ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>>();
}
final String packageName = getPackageName();
- ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
+ ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
if (packagePrefs == null) {
- packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
+ packagePrefs = new ArrayMap<File, SharedPreferencesImpl>();
sSharedPrefs.put(packageName, packagePrefs);
}
- // At least one application in the world actually passes in a null
- // name. This happened to work because when we generated the file name
- // we would stringify it to "null.xml". Nice.
- if (mPackageInfo.getApplicationInfo().targetSdkVersion <
- Build.VERSION_CODES.KITKAT) {
- if (name == null) {
- name = "null";
- }
- }
-
- sp = packagePrefs.get(name);
+ sp = packagePrefs.get(file);
if (sp == null) {
- File prefsFile = getSharedPrefsFile(name);
- sp = new SharedPreferencesImpl(prefsFile, mode);
- packagePrefs.put(name, sp);
+ sp = new SharedPreferencesImpl(file, mode);
+ packagePrefs.put(file, sp);
return sp;
}
}
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index 2e05edb..1370000 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -129,8 +129,8 @@
String libname = "main";
String funcname = "ANativeActivity_onCreate";
ActivityInfo ai;
-
- mIMM = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ mIMM = getSystemService(InputMethodManager.class);
getWindow().takeSurface(this);
getWindow().takeInputQueue(this);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 74634a9..6c0c3e8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
@@ -45,7 +46,6 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
-import android.util.MathUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
@@ -58,7 +58,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
-import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -2969,7 +2968,6 @@
Bitmap profileBadge = getProfileBadge();
contentView.setViewVisibility(R.id.profile_badge_large_template, View.GONE);
- contentView.setViewVisibility(R.id.profile_badge_line2, View.GONE);
contentView.setViewVisibility(R.id.profile_badge_line3, View.GONE);
if (profileBadge != null) {
@@ -2986,38 +2984,35 @@
return false;
}
- private void shrinkLine3Text(RemoteViews contentView) {
- float subTextSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_subtext_size);
- contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize);
- }
-
- private void unshrinkLine3Text(RemoteViews contentView) {
- float regularTextSize = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_text_size);
- contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, regularTextSize);
- }
-
private void resetStandardTemplate(RemoteViews contentView) {
- removeLargeIconBackground(contentView);
- contentView.setViewPadding(R.id.icon, 0, 0, 0, 0);
- contentView.setImageViewResource(R.id.icon, 0);
- contentView.setInt(R.id.icon, "setBackgroundResource", 0);
+ resetNotificationHeader(contentView);
+ resetContentMargins(contentView);
contentView.setViewVisibility(R.id.right_icon, View.GONE);
- contentView.setInt(R.id.right_icon, "setBackgroundResource", 0);
- contentView.setImageViewResource(R.id.right_icon, 0);
- contentView.setImageViewResource(R.id.icon, 0);
contentView.setTextViewText(R.id.title, null);
contentView.setTextViewText(R.id.text, null);
- unshrinkLine3Text(contentView);
- contentView.setTextViewText(R.id.text2, null);
- contentView.setViewVisibility(R.id.text2, View.GONE);
- contentView.setViewVisibility(R.id.info, View.GONE);
- contentView.setViewVisibility(R.id.time, View.GONE);
contentView.setViewVisibility(R.id.line3, View.GONE);
- contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
+ contentView.setViewVisibility(R.id.text_line_1, View.GONE);
contentView.setViewVisibility(R.id.progress, View.GONE);
+ }
+
+ /**
+ * Resets the notification header to its original state
+ */
+ private void resetNotificationHeader(RemoteViews contentView) {
+ contentView.setImageViewResource(R.id.icon, 0);
+ contentView.setTextViewText(R.id.app_name_text, null);
contentView.setViewVisibility(R.id.chronometer, View.GONE);
+ contentView.setViewVisibility(R.id.header_sub_text, View.GONE);
+ contentView.setViewVisibility(R.id.header_content_info, View.GONE);
+ contentView.setViewVisibility(R.id.number_of_children, View.GONE);
+ contentView.setViewVisibility(R.id.sub_text_divider, View.GONE);
+ contentView.setViewVisibility(R.id.content_info_divider, View.GONE);
+ contentView.setViewVisibility(R.id.time_divider, View.GONE);
+ }
+
+ private void resetContentMargins(RemoteViews contentView) {
+ contentView.setViewLayoutMarginEnd(R.id.line1, 0);
+ contentView.setViewLayoutMarginEnd(R.id.line3, 0);
}
private RemoteViews applyStandardTemplate(int resId) {
@@ -3033,95 +3028,118 @@
resetStandardTemplate(contentView);
boolean showLine3 = false;
- boolean showLine2 = false;
- boolean contentTextInLine2 = false;
final Bundle ex = mN.extras;
- if (mN.mLargeIcon != null) {
- contentView.setImageViewIcon(R.id.icon, mN.mLargeIcon);
- processLargeLegacyIcon(mN.mLargeIcon, contentView);
- contentView.setImageViewIcon(R.id.right_icon, mN.mSmallIcon);
- contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
- processSmallRightIcon(mN.mSmallIcon, contentView);
- } else { // small icon at left
- contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
- contentView.setViewVisibility(R.id.icon, View.VISIBLE);
- processSmallIconAsLarge(mN.mSmallIcon, contentView);
- }
+ bindNotificationHeader(contentView);
+ bindLargeIcon(contentView);
if (ex.getCharSequence(EXTRA_TITLE) != null) {
contentView.setTextViewText(R.id.title,
processLegacyText(ex.getCharSequence(EXTRA_TITLE)));
}
+ boolean showProgress = handleProgressBar(hasProgress, contentView, ex);
if (ex.getCharSequence(EXTRA_TEXT) != null) {
- contentView.setTextViewText(R.id.text,
+ contentView.setTextViewText(showProgress ? R.id.text_line_1 : R.id.text,
processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
+ if (showProgress) {
+ contentView.setViewVisibility(R.id.text_line_1, View.VISIBLE);
+ }
+ showLine3 = !showProgress;
+ }
+ // We want to add badge to first line of text.
+ if (addProfileBadge(contentView, R.id.profile_badge_line3)) {
showLine3 = true;
}
- if (ex.getCharSequence(EXTRA_INFO_TEXT) != null) {
- contentView.setTextViewText(R.id.info,
- processLegacyText(ex.getCharSequence(EXTRA_INFO_TEXT)));
- contentView.setViewVisibility(R.id.info, View.VISIBLE);
- showLine3 = true;
+ // Note getStandardView may hide line 3 again.
+ contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
+
+ return contentView;
+ }
+
+ private boolean handleProgressBar(boolean hasProgress, RemoteViews contentView, Bundle ex) {
+ final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
+ final int progress = ex.getInt(EXTRA_PROGRESS, 0);
+ final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+ if (hasProgress && (max != 0 || ind)) {
+ contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
+ contentView.setProgressBar(
+ R.id.progress, max, progress, ind);
+ contentView.setProgressBackgroundTintList(
+ R.id.progress, ColorStateList.valueOf(mContext.getColor(
+ R.color.notification_progress_background_color)));
+ if (mN.color != COLOR_DEFAULT) {
+ ColorStateList colorStateList = ColorStateList.valueOf(mN.color);
+ contentView.setProgressTintList(R.id.progress, colorStateList);
+ contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
+ }
+ return true;
+ } else {
+ contentView.setViewVisibility(R.id.progress, View.GONE);
+ return false;
+ }
+ }
+
+ private void bindLargeIcon(RemoteViews contentView) {
+ if (mN.mLargeIcon != null) {
+ contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+ contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
+ processLargeLegacyIcon(mN.mLargeIcon, contentView);
+ int endMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_content_picture_margin);
+ contentView.setViewLayoutMarginEnd(R.id.line1, endMargin);
+ contentView.setViewLayoutMarginEnd(R.id.line3, endMargin);
+ contentView.setViewLayoutMarginEnd(R.id.progress, endMargin);
+ }
+ }
+
+ private void bindNotificationHeader(RemoteViews contentView) {
+ bindSmallIcon(contentView);
+ bindChildCountColor(contentView);
+ bindHeaderAppName(contentView);
+ bindHeaderSubText(contentView);
+ bindContentInfo(contentView);
+ bindHeaderChronometerAndTime(contentView);
+ bindExpandButton(contentView);
+ }
+
+ private void bindChildCountColor(RemoteViews contentView) {
+ contentView.setTextColor(R.id.number_of_children, resolveColor());
+ }
+
+ private void bindContentInfo(RemoteViews contentView) {
+ boolean visible = false;
+ if (mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
+ contentView.setTextViewText(R.id.header_content_info,
+ processLegacyText(mN.extras.getCharSequence(EXTRA_INFO_TEXT)));
+ contentView.setViewVisibility(R.id.header_content_info, View.VISIBLE);
+ visible = true;
} else if (mN.number > 0) {
final int tooBig = mContext.getResources().getInteger(
R.integer.status_bar_notification_info_maxnum);
if (mN.number > tooBig) {
- contentView.setTextViewText(R.id.info, processLegacyText(
+ contentView.setTextViewText(R.id.header_content_info, processLegacyText(
mContext.getResources().getString(
R.string.status_bar_notification_info_overflow)));
} else {
- NumberFormat f = NumberFormat.getIntegerInstance();
- contentView.setTextViewText(R.id.info, processLegacyText(f.format(mN.number)));
+ contentView.setTextViewText(R.id.header_content_info,
+ processLegacyText(String.valueOf(mN.number)));
}
- contentView.setViewVisibility(R.id.info, View.VISIBLE);
- showLine3 = true;
- } else {
- contentView.setViewVisibility(R.id.info, View.GONE);
+ contentView.setViewVisibility(R.id.header_content_info, View.VISIBLE);
+ visible = true;
}
-
- // Need to show three lines?
- if (ex.getCharSequence(EXTRA_SUB_TEXT) != null) {
- contentView.setTextViewText(R.id.text,
- processLegacyText(ex.getCharSequence(EXTRA_SUB_TEXT)));
- if (ex.getCharSequence(EXTRA_TEXT) != null) {
- contentView.setTextViewText(R.id.text2,
- processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
- contentView.setViewVisibility(R.id.text2, View.VISIBLE);
- showLine2 = true;
- contentTextInLine2 = true;
- } else {
- contentView.setViewVisibility(R.id.text2, View.GONE);
- }
- } else {
- contentView.setViewVisibility(R.id.text2, View.GONE);
- final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
- final int progress = ex.getInt(EXTRA_PROGRESS, 0);
- final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
- if (hasProgress && (max != 0 || ind)) {
- contentView.setViewVisibility(R.id.progress, View.VISIBLE);
- contentView.setProgressBar(
- R.id.progress, max, progress, ind);
- contentView.setProgressBackgroundTintList(
- R.id.progress, ColorStateList.valueOf(mContext.getColor(
- R.color.notification_progress_background_color)));
- if (mN.color != COLOR_DEFAULT) {
- ColorStateList colorStateList = ColorStateList.valueOf(mN.color);
- contentView.setProgressTintList(R.id.progress, colorStateList);
- contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
- }
- showLine2 = true;
- } else {
- contentView.setViewVisibility(R.id.progress, View.GONE);
- }
+ if (visible) {
+ contentView.setViewVisibility(R.id.content_info_divider, View.VISIBLE);
}
- if (showLine2) {
+ }
- // need to shrink all the type to make sure everything fits
- shrinkLine3Text(contentView);
- }
+ private void bindExpandButton(RemoteViews contentView) {
+ contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveColor(),
+ PorterDuff.Mode.SRC_ATOP, -1);
+ }
+ private void bindHeaderChronometerAndTime(RemoteViews contentView) {
if (showsTimeOrChronometer()) {
- if (ex.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
+ contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
+ if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
contentView.setLong(R.id.chronometer, "setBase",
mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
@@ -3131,26 +3149,42 @@
contentView.setLong(R.id.time, "setTime", mN.when);
}
}
+ }
- // Adjust padding depending on line count and font size.
- contentView.setViewPadding(R.id.line1, 0,
- calculateTopPadding(mContext, hasThreeLines(),
- mContext.getResources().getConfiguration().fontScale),
- 0, 0);
-
- // We want to add badge to first line of text.
- boolean addedBadge = addProfileBadge(contentView,
- contentTextInLine2 ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
- // If we added the badge to line 3 then we should show line 3.
- if (addedBadge && !contentTextInLine2) {
- showLine3 = true;
+ private void bindHeaderSubText(RemoteViews contentView) {
+ CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
+ if (subText == null && mStyle != null && mStyle.mSummaryTextSet
+ && mStyle.hasSummaryInHeader()) {
+ subText = mStyle.mSummaryText;
}
+ if (subText != null) {
+ // TODO: Remove the span entirely to only have the string with propper formating.
+ contentView.setTextViewText(R.id.header_sub_text, processLegacyText(subText));
+ contentView.setViewVisibility(R.id.header_sub_text, View.VISIBLE);
+ contentView.setViewVisibility(R.id.sub_text_divider, View.VISIBLE);
+ }
+ }
- // Note getStandardView may hide line 3 again.
- contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
- contentView.setViewVisibility(R.id.overflow_divider,
- showLine3 ? View.VISIBLE : View.GONE);
- return contentView;
+ private void bindHeaderAppName(RemoteViews contentView) {
+ PackageManager packageManager = mContext.getPackageManager();
+ ApplicationInfo info = null;
+ try {
+ info = packageManager.getApplicationInfo(mContext.getApplicationInfo().packageName,
+ 0);
+ } catch (final NameNotFoundException e) {
+ return;
+ }
+ CharSequence appName = info != null ? packageManager.getApplicationLabel(info)
+ : null;
+ if (TextUtils.isEmpty(appName)) {
+ return;
+ }
+ contentView.setTextViewText(R.id.app_name_text, appName);
+ }
+
+ private void bindSmallIcon(RemoteViews contentView) {
+ contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
+ processSmallIconColor(mN.mSmallIcon, contentView);
}
/**
@@ -3161,49 +3195,6 @@
return mN.when != 0 && mN.extras.getBoolean(EXTRA_SHOW_WHEN);
}
- /**
- * Logic to find out whether the notification is going to have three lines in the contracted
- * layout. This is used to adjust the top padding.
- *
- * @return true if the notification is going to have three lines; false if the notification
- * is going to have one or two lines
- */
- private boolean hasThreeLines() {
- final CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
- final CharSequence text = mN.extras.getCharSequence(EXTRA_TEXT);
- boolean contentTextInLine2 = subText != null && text != null;
- boolean hasProgress = mStyle == null || mStyle.hasProgress();
- // If we have content text in line 2, badge goes into line 2, or line 3 otherwise
- boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2;
- boolean hasLine3 = text != null || mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null
- || mN.number > 0 || badgeInLine3;
- final Bundle ex = mN.extras;
- final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
- final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
- boolean hasLine2 = (subText != null && text != null) ||
- (hasProgress && subText == null && (max != 0 || ind));
- return hasLine2 && hasLine3;
- }
-
- /**
- * @hide
- */
- public static int calculateTopPadding(Context ctx, boolean hasThreeLines,
- float fontScale) {
- int padding = ctx.getResources().getDimensionPixelSize(hasThreeLines
- ? R.dimen.notification_top_pad_narrow
- : R.dimen.notification_top_pad);
- int largePadding = ctx.getResources().getDimensionPixelSize(hasThreeLines
- ? R.dimen.notification_top_pad_large_text_narrow
- : R.dimen.notification_top_pad_large_text);
- float largeFactor = (MathUtils.constrain(fontScale, 1.0f, LARGE_TEXT_SCALE) - 1f)
- / (LARGE_TEXT_SCALE - 1f);
-
- // Linearly interpolate the padding between large and normal with the font scale ranging
- // from 1f to LARGE_TEXT_SCALE
- return Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
- }
-
private void resetStandardTemplateWithActions(RemoteViews big) {
big.setViewVisibility(R.id.actions, View.GONE);
big.setViewVisibility(R.id.action_divider, View.GONE);
@@ -3250,18 +3241,46 @@
* Construct a RemoteViews for the final big notification layout.
*/
public RemoteViews makeBigContentView() {
+ RemoteViews result = null;
if (mN.bigContentView != null) {
return mN.bigContentView;
} else if (mStyle != null) {
- final RemoteViews styleView = mStyle.makeBigContentView();
- if (styleView != null) {
- return styleView;
- }
+ result = mStyle.makeBigContentView();
} else if (mActions.size() == 0) {
return null;
}
+ if (result == null) {
+ result = applyStandardTemplateWithActions(getBigBaseLayoutResource());
+ } else {
+ hideLine1Text(result);
+ }
+ adaptNotificationHeaderForBigContentView(result);
+ return result;
+ }
- return applyStandardTemplateWithActions(getBigBaseLayoutResource());
+ /**
+ * Construct a RemoteViews for the final notification header only
+ *
+ * @hide
+ */
+ public RemoteViews makeNotificationHeader() {
+ RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
+ R.layout.notification_template_header);
+ resetNotificationHeader(header);
+ bindNotificationHeader(header);
+ return header;
+ }
+
+ private void hideLine1Text(RemoteViews result) {
+ result.setViewVisibility(R.id.text_line_1, View.GONE);
+ }
+
+ private void adaptNotificationHeaderForBigContentView(RemoteViews result) {
+ // We have to set the collapse button instead
+ result.setImageViewResource(R.id.expand_button, R.drawable.ic_arrow_up_14dp);
+ // Apply the color again
+ result.setDrawableParameters(R.id.expand_button, false, -1, resolveColor(),
+ PorterDuff.Mode.SRC_ATOP, -1);
}
/**
@@ -3330,84 +3349,27 @@
}
/**
- * Apply any necessary background to smallIcons being used in the largeIcon spot.
+ * Apply any necessariy colors to the small icon
*/
- private void processSmallIconAsLarge(Icon largeIcon, RemoteViews contentView) {
- if (!isLegacy()) {
- contentView.setDrawableParameters(R.id.icon, false, -1,
- 0xFFFFFFFF,
+ private void processSmallIconColor(Icon smallIcon, RemoteViews contentView) {
+ if (!isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon)) {
+ contentView.setDrawableParameters(R.id.icon, false, -1, resolveColor(),
PorterDuff.Mode.SRC_ATOP, -1);
- applyLargeIconBackground(contentView);
- } else {
- if (getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
- applyLargeIconBackground(contentView);
- }
}
}
/**
- * Apply any necessary background to a largeIcon if it's a fake smallIcon (that is,
+ * Make the largeIcon dark if it's a fake smallIcon (that is,
* if it's grayscale).
*/
// TODO: also check bounds, transparency, that sort of thing.
private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) {
if (largeIcon != null && isLegacy()
&& getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
- applyLargeIconBackground(contentView);
- } else {
- removeLargeIconBackground(contentView);
- }
- }
-
- /**
- * Add a colored circle behind the largeIcon slot.
- */
- private void applyLargeIconBackground(RemoteViews contentView) {
- contentView.setInt(R.id.icon, "setBackgroundResource",
- R.drawable.notification_icon_legacy_bg);
-
- contentView.setDrawableParameters(
- R.id.icon,
- true,
- -1,
- resolveColor(),
- PorterDuff.Mode.SRC_ATOP,
- -1);
-
- int padding = mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_large_icon_circle_padding);
- contentView.setViewPadding(R.id.icon, padding, padding, padding, padding);
- }
-
- private void removeLargeIconBackground(RemoteViews contentView) {
- contentView.setInt(R.id.icon, "setBackgroundResource", 0);
- }
-
- /**
- * Recolor small icons when used in the R.id.right_icon slot.
- */
- private void processSmallRightIcon(Icon smallIcon, RemoteViews contentView) {
- if (!isLegacy()) {
- contentView.setDrawableParameters(R.id.right_icon, false, -1,
- 0xFFFFFFFF,
+ // resolve color will fall back to the default when legacy
+ contentView.setDrawableParameters(R.id.icon, false, -1, resolveColor(),
PorterDuff.Mode.SRC_ATOP, -1);
}
- final boolean gray = isLegacy()
- && smallIcon.getType() == Icon.TYPE_RESOURCE
- && getColorUtil().isGrayscaleIcon(mContext, smallIcon.getResId());
- if (!isLegacy() || gray) {
- contentView.setInt(R.id.right_icon,
- "setBackgroundResource",
- R.drawable.notification_icon_legacy_bg);
-
- contentView.setDrawableParameters(
- R.id.right_icon,
- true,
- -1,
- resolveColor(),
- PorterDuff.Mode.SRC_ATOP,
- -1);
- }
}
private void sanitizeColor() {
@@ -3416,9 +3378,9 @@
}
}
- private int resolveColor() {
+ int resolveColor() {
if (mN.color == COLOR_DEFAULT) {
- return mContext.getColor(R.color.notification_icon_bg_color);
+ return mContext.getColor(R.color.notification_icon_default_color);
}
return mN.color;
}
@@ -3622,20 +3584,9 @@
contentView.setViewVisibility(R.id.line1, View.VISIBLE);
}
- // The last line defaults to the subtext, but can be replaced by mSummaryText
- final CharSequence overflowText =
- mSummaryTextSet ? mSummaryText
- : mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT);
- if (overflowText != null) {
- contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText));
- contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE);
- contentView.setViewVisibility(R.id.line3, View.VISIBLE);
- } else {
- // Clear text in case we use the line to show the profile badge.
- contentView.setTextViewText(R.id.text, "");
- contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
- contentView.setViewVisibility(R.id.line3, View.GONE);
- }
+ // Clear text in case we use the line to show the profile badge.
+ contentView.setTextViewText(com.android.internal.R.id.text, "");
+ contentView.setViewVisibility(com.android.internal.R.id.line3, View.GONE);
return contentView;
}
@@ -3666,19 +3617,6 @@
}
/**
- * Changes the padding of the first line such that the big and small content view have the
- * same top padding.
- *
- * @hide
- */
- protected void applyTopPadding(RemoteViews contentView) {
- int topPadding = Builder.calculateTopPadding(mBuilder.mContext,
- mBuilder.hasThreeLines(),
- mBuilder.mContext.getResources().getConfiguration().fontScale);
- contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0);
- }
-
- /**
* Apply any style-specific extras to this notification before shipping it out.
* @hide
*/
@@ -3739,6 +3677,14 @@
protected boolean hasProgress() {
return true;
}
+
+ /**
+ * @hide
+ * @return Whether we should put the summary be put into the notification header
+ */
+ public boolean hasSummaryInHeader() {
+ return true;
+ }
}
/**
@@ -3846,6 +3792,16 @@
}
RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
+ if (mSummaryTextSet) {
+ contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText));
+ contentView.setViewVisibility(R.id.line3, View.VISIBLE);
+ }
+ int imageMinHeight = mBuilder.mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_big_picture_content_min_height_with_picture);
+ // We need to make space for the right image, so we're enforcing a minheight if there
+ // is a picture.
+ int minHeight = (mBuilder.mN.mLargeIcon == null) ? 0 : imageMinHeight;
+ contentView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight);
if (mBigLargeIconSet) {
mBuilder.mN.mLargeIcon = oldLargeIcon;
@@ -3853,12 +3809,7 @@
contentView.setImageViewBitmap(R.id.big_picture, mPicture);
- applyTopPadding(contentView);
-
- boolean twoTextLines = mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT) != null
- && mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT) != null;
- mBuilder.addProfileBadge(contentView,
- twoTextLines ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
+ mBuilder.addProfileBadge(contentView, R.id.profile_badge_line3);
return contentView;
}
@@ -3887,6 +3838,14 @@
}
mPicture = extras.getParcelable(EXTRA_PICTURE);
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean hasSummaryInHeader() {
+ return false;
+ }
}
/**
@@ -3983,14 +3942,11 @@
contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText));
contentView.setViewVisibility(R.id.big_text, View.VISIBLE);
contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines());
- contentView.setViewVisibility(R.id.text2, View.GONE);
-
- applyTopPadding(contentView);
-
- mBuilder.shrinkLine3Text(contentView);
mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
+ contentView.setBoolean(R.id.big_text, "setHasImage", mBuilder.mN.mLargeIcon != null);
+
return contentView;
}
@@ -4005,11 +3961,6 @@
if (hasSummary) {
lineCount -= LINES_CONSUMED_BY_SUMMARY;
}
-
- // If we have less top padding at the top, we can fit less lines.
- if (!mBuilder.hasThreeLines()) {
- lineCount--;
- }
return lineCount;
}
}
@@ -4105,8 +4056,6 @@
mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
- contentView.setViewVisibility(R.id.text2, View.GONE);
-
int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
@@ -4120,6 +4069,9 @@
final float subTextSize = mBuilder.mContext.getResources().getDimensionPixelSize(
R.dimen.notification_subtext_size);
int i=0;
+ final float density = mBuilder.mContext.getResources().getDisplayMetrics().density;
+ int topPadding = (int) (5 * density);
+ int bottomPadding = (int) (13 * density);
while (i < mTexts.size() && i < rowIds.length) {
CharSequence str = mTexts.get(i);
if (str != null && !str.equals("")) {
@@ -4129,24 +4081,29 @@
contentView.setTextViewTextSize(rowIds[i], TypedValue.COMPLEX_UNIT_PX,
subTextSize);
}
+ contentView.setViewPadding(rowIds[i], 0, topPadding, 0,
+ i == rowIds.length - 1 || i == mTexts.size() - 1 ? bottomPadding : 0);
}
i++;
}
-
- contentView.setViewVisibility(R.id.inbox_end_pad,
- mTexts.size() > 0 ? View.VISIBLE : View.GONE);
-
- contentView.setViewVisibility(R.id.inbox_more,
- mTexts.size() > rowIds.length ? View.VISIBLE : View.GONE);
-
- applyTopPadding(contentView);
-
- mBuilder.shrinkLine3Text(contentView);
-
mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
+ handleInboxImageMargin(contentView, rowIds[0]);
+
return contentView;
}
+
+ private void handleInboxImageMargin(RemoteViews contentView, int id) {
+ final int max = mBuilder.mN.extras.getInt(EXTRA_PROGRESS_MAX, 0);
+ final boolean ind = mBuilder.mN.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+ boolean hasProgress = max != 0 || ind;
+ int endMargin = 0;
+ if (mTexts.size() > 0 && mBuilder.mN.mLargeIcon != null && !hasProgress) {
+ endMargin = mBuilder.mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_content_picture_margin);
+ }
+ contentView.setViewLayoutMarginEnd(id, endMargin);
+ }
}
/**
@@ -4278,14 +4235,13 @@
}
}
- private RemoteViews generateMediaActionButton(Action action) {
+ private RemoteViews generateMediaActionButton(Action action, int color) {
final boolean tombstone = (action.actionIntent == null);
RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(),
R.layout.notification_material_media_action);
button.setImageViewIcon(R.id.action0, action.getIcon());
- button.setDrawableParameters(R.id.action0, false, -1,
- 0xFFFFFFFF,
- PorterDuff.Mode.SRC_ATOP, -1);
+ button.setDrawableParameters(R.id.action0, false, -1, color, PorterDuff.Mode.SRC_ATOP,
+ -1);
if (!tombstone) {
button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
}
@@ -4311,67 +4267,40 @@
}
final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
- final RemoteViews button = generateMediaActionButton(action);
+ final RemoteViews button = generateMediaActionButton(action,
+ mBuilder.resolveColor());
view.addView(com.android.internal.R.id.media_actions, button);
}
}
- styleText(view);
- hideRightIcon(view);
+ handleImage(view /* addPaddingToMainColumn */);
return view;
}
private RemoteViews makeMediaBigContentView() {
final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
- RemoteViews big = mBuilder.applyStandardTemplate(getBigLayoutResource(actionCount),
- false /* hasProgress */);
+ RemoteViews big = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_material_big_media,
+ false);
if (actionCount > 0) {
big.removeAllViews(com.android.internal.R.id.media_actions);
for (int i = 0; i < actionCount; i++) {
- final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i));
+ final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i),
+ mBuilder.resolveColor());
big.addView(com.android.internal.R.id.media_actions, button);
}
}
- styleText(big);
- hideRightIcon(big);
- applyTopPadding(big);
- big.setViewVisibility(android.R.id.progress, View.GONE);
+ handleImage(big);
return big;
}
- private int getBigLayoutResource(int actionCount) {
- if (actionCount <= 3) {
- return R.layout.notification_template_material_big_media_narrow;
- } else {
- return R.layout.notification_template_material_big_media;
+ private void handleImage(RemoteViews contentView) {
+ if (mBuilder.mN.mLargeIcon != null) {
+ contentView.setViewLayoutMarginEnd(R.id.line1, 0);
+ contentView.setViewLayoutMarginEnd(R.id.line3, 0);
}
}
- private void hideRightIcon(RemoteViews contentView) {
- contentView.setViewVisibility(R.id.right_icon, View.GONE);
- }
-
- /**
- * Applies the special text colors for media notifications to all text views.
- */
- private void styleText(RemoteViews contentView) {
- int primaryColor = mBuilder.mContext.getColor(
- R.color.notification_media_primary_color);
- int secondaryColor = mBuilder.mContext.getColor(
- R.color.notification_media_secondary_color);
- contentView.setTextColor(R.id.title, primaryColor);
- if (mBuilder.showsTimeOrChronometer()) {
- if (mBuilder.getAllExtras().getBoolean(EXTRA_SHOW_CHRONOMETER)) {
- contentView.setTextColor(R.id.chronometer, secondaryColor);
- } else {
- contentView.setTextColor(R.id.time, secondaryColor);
- }
- }
- contentView.setTextColor(R.id.text2, secondaryColor);
- contentView.setTextColor(R.id.text, secondaryColor);
- contentView.setTextColor(R.id.info, secondaryColor);
- }
-
/**
* @hide
*/
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index fa11221..9c18df8 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -505,8 +505,7 @@
// We made sure the IME was displayed, so also make sure it is closed
// when we go away.
- InputMethodManager imm = (InputMethodManager)getContext()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
if (imm != null) {
imm.hideSoftInputFromWindow(
getWindow().getDecorView().getWindowToken(), 0);
@@ -643,8 +642,7 @@
public void onBackPressed() {
// If the input method is covering the search dialog completely,
// e.g. in landscape mode with no hard keyboard, dismiss just the input method
- InputMethodManager imm = (InputMethodManager)getContext()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
if (imm != null && imm.isFullscreenMode() &&
imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0)) {
return;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a0102b6..b73fa50 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -625,8 +625,30 @@
* @see #MODE_WORLD_READABLE
* @see #MODE_WORLD_WRITEABLE
*/
- public abstract SharedPreferences getSharedPreferences(String name,
- int mode);
+ public abstract SharedPreferences getSharedPreferences(String name, int mode);
+
+ /**
+ * Retrieve and hold the contents of the preferences file, returning
+ * a SharedPreferences through which you can retrieve and modify its
+ * values. Only one instance of the SharedPreferences object is returned
+ * to any callers for the same name, meaning they will see each other's
+ * edits as soon as they are made.
+ *
+ * @param file Desired preferences file. If a preferences file by this name
+ * does not exist, it will be created when you retrieve an
+ * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()).
+ * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the
+ * default operation, {@link #MODE_WORLD_READABLE}
+ * and {@link #MODE_WORLD_WRITEABLE} to control permissions.
+ *
+ * @return The single {@link SharedPreferences} instance that can be used
+ * to retrieve and modify the preference values.
+ *
+ * @see #MODE_PRIVATE
+ * @see #MODE_WORLD_READABLE
+ * @see #MODE_WORLD_WRITEABLE
+ */
+ public abstract SharedPreferences getSharedPreferences(File file, int mode);
/**
* Open a private file associated with this Context's application package
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index bec1b37..a345aae 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -172,6 +172,11 @@
}
@Override
+ public SharedPreferences getSharedPreferences(File file, int mode) {
+ return mBase.getSharedPreferences(file, mode);
+ }
+
+ @Override
public FileInputStream openFileInput(String name)
throws FileNotFoundException {
return mBase.openFileInput(name);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 4a7cbc7..2178c38 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1609,6 +1609,53 @@
"android.intent.action.MANAGE_PERMISSIONS";
/**
+ * Activity action: Launch UI to review permissions for an app.
+ * The system uses this intent if permission review for apps not
+ * supporting the new runtime permissions model is enabled. In
+ * this mode a permission review is required before any of the
+ * app components can run.
+ * <p>
+ * Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose
+ * permissions will be reviewed (mandatory).
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_INTENT} specifies a pending intent to
+ * be fired after the permission review (optional).
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_REMOTE_CALLBACK} specifies a callback to
+ * be invoked after the permission review (optional).
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_RESULT_NEEDED} specifies whether the intent
+ * passed via {@link #EXTRA_INTENT} needs a result (optional).
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @see #EXTRA_PACKAGE_NAME
+ * @see #EXTRA_INTENT
+ * @see #EXTRA_REMOTE_CALLBACK
+ * @see #EXTRA_RESULT_NEEDED
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REVIEW_PERMISSIONS =
+ "android.intent.action.REVIEW_PERMISSIONS";
+
+ /**
+ * Intent extra: A callback for reporting remote result as a bundle.
+ * <p>
+ * Type: IRemoteCallback
+ * </p>
+ *
+ * @hide
+ */
+ public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
+
+ /**
* Intent extra: An app package name.
* <p>
* Type: String
@@ -1620,6 +1667,16 @@
public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
/**
+ * Intent extra: An extra for specifying whether a result is needed.
+ * <p>
+ * Type: boolean
+ * </p>
+ *
+ * @hide
+ */
+ public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
+
+ /**
* Broadcast action that requests current permission granted information. It will respond
* to the request by sending a broadcast with action defined by
* {@link #EXTRA_GET_PERMISSIONS_RESPONSE_INTENT}. The response will contain
@@ -2877,6 +2934,14 @@
"android.intent.action.USER_SWITCHED";
/**
+ * Broadcast Action: Sent when the credential-encrypted private storage has
+ * become unlocked for the target user. This is only sent to registered
+ * receivers, not manifest receivers.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
+
+ /**
* Broadcast sent to the system when a user's information changes. Carries an extra
* {@link #EXTRA_USER_HANDLE} to indicate which user's information changed.
* This is only sent to registered receivers, not manifest receivers. It is sent to all users.
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1996e0f..65e5945 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -487,6 +487,13 @@
public static final int PRIVATE_FLAG_AUTOPLAY = 1 << 7;
/**
+ * When set, at least one component inside this application is encryption aware.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE = 1 << 8;
+
+ /**
* Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
* {@hide}
*/
@@ -1054,6 +1061,11 @@
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ENCRYPTION_AWARE) != 0;
}
+ /** @hide */
+ public boolean isPartiallyEncryptionAware() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE) != 0;
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 6fe1efd..b9a42eb 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -59,7 +59,7 @@
* {@hide}
*/
interface IPackageManager {
- boolean isPackageFrozen(String packageName);
+ void checkPackageStartable(String packageName, int userId);
boolean isPackageAvailable(String packageName, int userId);
PackageInfo getPackageInfo(String packageName, int flags, int userId);
int getPackageUid(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0c28008..aa960a4 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2018,7 +2018,6 @@
*/
public static final int FLAG_PERMISSION_SYSTEM_FIXED = 1 << 4;
-
/**
* Permission flag: The permission is granted by default because it
* enables app functionality that is expected to work out-of-the-box
@@ -2030,6 +2029,14 @@
public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 1 << 5;
/**
+ * Permission flag: The permission has to be reviewed before any of
+ * the app components can run.
+ *
+ * @hide
+ */
+ public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 1 << 6;
+
+ /**
* Mask for all permission flags.
*
* @hide
@@ -4808,6 +4815,7 @@
case FLAG_PERMISSION_USER_SET: return "USER_SET";
case FLAG_PERMISSION_REVOKE_ON_UPGRADE: return "REVOKE_ON_UPGRADE";
case FLAG_PERMISSION_USER_FIXED: return "USER_FIXED";
+ case FLAG_PERMISSION_REVIEW_REQUIRED: return "REVIEW_REQUIRED";
default: return Integer.toString(flag);
}
}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 905ac5e..8bf20bf 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -122,4 +122,13 @@
* @param packageList List of package names to keep cached.
*/
public abstract void setKeepUninstalledPackages(List<String> packageList);
+
+ /**
+ * Gets whether some of the permissions used by this package require a user
+ * review before any of the app components can run.
+ * @param packageName The package name for which to check.
+ * @param userId The user under which to check.
+ * @return True a permissions review is required.
+ */
+ public abstract boolean isPermissionsReviewRequired(String packageName, int userId);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 838da37..ecb1850 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -84,9 +84,10 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.jar.StrictJarFile;
import java.util.zip.ZipEntry;
+import android.util.jar.StrictJarFile;
+
/**
* Parser for package files (APKs) on disk. This supports apps packaged either
* as a single "monolithic" APK, or apps packaged as a "cluster" of multiple
@@ -3262,6 +3263,11 @@
owner.applicationInfo.isEncryptionAware());
}
+ if (a.info.encryptionAware) {
+ owner.applicationInfo.privateFlags |=
+ ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE;
+ }
+
sa.recycle();
if (receiver && (owner.applicationInfo.privateFlags
@@ -3663,6 +3669,10 @@
p.info.encryptionAware = sa.getBoolean(
R.styleable.AndroidManifestProvider_encryptionAware,
owner.applicationInfo.isEncryptionAware());
+ if (p.info.encryptionAware) {
+ owner.applicationInfo.privateFlags |=
+ ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE;
+ }
sa.recycle();
@@ -3947,6 +3957,10 @@
s.info.encryptionAware = sa.getBoolean(
R.styleable.AndroidManifestService_encryptionAware,
owner.applicationInfo.isEncryptionAware());
+ if (s.info.encryptionAware) {
+ owner.applicationInfo.privateFlags |=
+ ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE;
+ }
sa.recycle();
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 5f27bca..3c2d503 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3329,8 +3329,8 @@
* @see CaptureRequest#SENSOR_SENSITIVITY
*/
@PublicKey
- public static final Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL =
- new Key<android.hardware.camera2.params.BlackLevelPattern>("android.sensor.dynamicBlackLevel", android.hardware.camera2.params.BlackLevelPattern.class);
+ public static final Key<float[]> SENSOR_DYNAMIC_BLACK_LEVEL =
+ new Key<float[]>("android.sensor.dynamicBlackLevel", float[].class);
/**
* <p>Maximum raw value output by sensor for this frame.</p>
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index 6a392dd..014e73f 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -16,8 +16,11 @@
package android.hardware.input;
+import android.annotation.Nullable;
import android.hardware.display.DisplayViewport;
import android.view.InputEvent;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
/**
* Input manager local system service interface.
@@ -39,4 +42,14 @@
* watching for wake events.
*/
public abstract void setInteractive(boolean interactive);
+
+ /**
+ * Notifies that InputMethodManagerService switched the current input method subtype.
+ *
+ * @param userId user id that indicates who is using the specified input method and subtype.
+ * @param inputMethodInfo {@code null} when no input method is selected.
+ * @param subtype {@code null} when {@code inputMethodInfo} does has no subtype.
+ */
+ public abstract void onInputMethodSubtypeChanged(int userId,
+ @Nullable InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype subtype);
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 10307e4..515e9a2 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1028,13 +1028,25 @@
* Guess what the network request was trying to say so that the resulting
* network is accessible via the legacy (deprecated) API such as
* requestRouteToHost.
- * This means we should try to be fairly preceise about transport and
+ *
+ * This means we should try to be fairly precise about transport and
* capability but ignore things such as networkSpecifier.
* If the request has more than one transport or capability it doesn't
* match the old legacy requests (they selected only single transport/capability)
* so this function cannot map the request to a single legacy type and
* the resulting network will not be available to the legacy APIs.
*
+ * This code is only called from the requestNetwork API (L and above).
+ *
+ * Setting a legacy type causes CONNECTIVITY_ACTION broadcasts, which are expensive
+ * because they wake up lots of apps - see http://b/23350688 . So we currently only
+ * do this for SUPL requests, which are the only ones that we know need it. If
+ * omitting these broadcasts causes unacceptable app breakage, then for backwards
+ * compatibility we can send them:
+ *
+ * if (targetSdkVersion < Build.VERSION_CODES.M) && // legacy API unsupported >= M
+ * targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP)) // requestNetwork not present < L
+ *
* TODO - This should be removed when the legacy APIs are removed.
*/
private int inferLegacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
@@ -1046,6 +1058,14 @@
return TYPE_NONE;
}
+ // Do this only for SUPL, until GpsLocationProvider is fixed. http://b/25876485 .
+ if (!netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+ // NOTE: if this causes app breakage, we should not just comment out this early return;
+ // instead, we should make this early return conditional on the requesting app's target
+ // SDK version, as described in the comment above.
+ return TYPE_NONE;
+ }
+
String type = null;
int result = TYPE_NONE;
@@ -1062,7 +1082,7 @@
type = "enableDUN";
result = TYPE_MOBILE_DUN;
} else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
- type = "enableSUPL";
+ type = "enableSUPL";
result = TYPE_MOBILE_SUPL;
// back out this hack for mms as they no longer need this and it's causing
// device slowdowns - b/23350688 (note, supl still needs this)
@@ -2541,7 +2561,7 @@
* This function behaves identically to the non-timedout version, but if a suitable
* network is not found within the given time (in milliseconds) the
* {@link NetworkCallback#unavailable} callback is called. The request must
- * still be released normally by calling {@link releaseNetworkRequest}.
+ * still be released normally by calling {@link unregisterNetworkCallback}.
*
* <p>This method requires the caller to hold either the
* {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 521df28..63f39c5 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -181,9 +181,12 @@
private static final String LOG_TAG = "AsyncTask";
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
- private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
+ // We want at least 2 threads and at most 4 threads in the core pool,
+ // preferring to have 1 less than the CPU count to avoid saturating
+ // the CPU with background work
+ private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
- private static final int KEEP_ALIVE = 1;
+ private static final int KEEP_ALIVE_SECONDS = 30;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@@ -199,9 +202,15 @@
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
- public static final Executor THREAD_POOL_EXECUTOR
- = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
- TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
+ public static final Executor THREAD_POOL_EXECUTOR;
+
+ static {
+ ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
+ CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
+ sPoolWorkQueue, sThreadFactory);
+ threadPoolExecutor.allowCoreThreadTimeOut(true);
+ THREAD_POOL_EXECUTOR = threadPoolExecutor;
+ }
/**
* An {@link Executor} that executes tasks one at a time in serial
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index f7c8662..de8b690 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -788,6 +788,18 @@
SystemProperties.getInt("ro.debuggable", 0) == 1;
/**
+ * Specifies whether the permissions needed by a legacy app should be
+ * reviewed before any of its components can run. A legacy app is one
+ * with targetSdkVersion < 23, i.e apps using the old permission model.
+ * If review is not required, permissions are reviewed before the app
+ * is installed.
+ *
+ * @hide
+ */
+ public static final boolean PERMISSIONS_REVIEW_REQUIRED =
+ SystemProperties.getInt("ro.permission_review_required", 0) == 1;
+
+ /**
* Returns the version string for the radio firmware. May return
* null (if, for instance, the radio is not currently on).
*/
diff --git a/core/java/android/os/IProcessInfoService.aidl b/core/java/android/os/IProcessInfoService.aidl
index c98daa2..62237f5 100644
--- a/core/java/android/os/IProcessInfoService.aidl
+++ b/core/java/android/os/IProcessInfoService.aidl
@@ -25,5 +25,12 @@
* to indicate that no process with the given PID exists.
*/
void getProcessStatesFromPids(in int[] pids, out int[] states);
+
+ /**
+ * For each PID in the given input array, write the current process state and OOM score
+ * for that process into the output arrays, or ActivityManager.PROCESS_STATE_NONEXISTENT
+ * in the states array to indicate that no process with the given PID exists.
+ */
+ void getProcessStatesAndOomScoresFromPids(in int[] pids, out int[] states, out int[] scores);
}
diff --git a/core/java/android/os/RemoteCallback.java b/core/java/android/os/RemoteCallback.java
index ca95bdf..89e30a9 100644
--- a/core/java/android/os/RemoteCallback.java
+++ b/core/java/android/os/RemoteCallback.java
@@ -16,88 +16,84 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
/**
- * TODO: Make this a public API? Let's see how it goes with a few use
- * cases first.
* @hide
*/
-public abstract class RemoteCallback implements Parcelable {
- final Handler mHandler;
- final IRemoteCallback mTarget;
-
- class DeliverResult implements Runnable {
- final Bundle mResult;
-
- DeliverResult(Bundle result) {
- mResult = result;
- }
-
- public void run() {
- onResult(mResult);
- }
+public final class RemoteCallback implements Parcelable {
+
+ public interface OnResultListener {
+ public void onResult(Bundle result);
}
-
- class LocalCallback extends IRemoteCallback.Stub {
- public void sendResult(Bundle bundle) {
- mHandler.post(new DeliverResult(bundle));
- }
+
+ private final OnResultListener mListener;
+ private final Handler mHandler;
+ private final IRemoteCallback mCallback;
+
+ public RemoteCallback(OnResultListener listener) {
+ this(listener, null);
}
-
- static class RemoteCallbackProxy extends RemoteCallback {
- RemoteCallbackProxy(IRemoteCallback target) {
- super(target);
+
+ public RemoteCallback(@NonNull OnResultListener listener, @Nullable Handler handler) {
+ if (listener == null) {
+ throw new NullPointerException("listener cannot be null");
}
-
- protected void onResult(Bundle bundle) {
- }
- }
-
- public RemoteCallback(Handler handler) {
+ mListener = listener;
mHandler = handler;
- mTarget = new LocalCallback();
+ mCallback = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) {
+ RemoteCallback.this.sendResult(data);
+ }
+ };
}
-
- RemoteCallback(IRemoteCallback target) {
+
+ RemoteCallback(Parcel parcel) {
+ mListener = null;
mHandler = null;
- mTarget = target;
+ mCallback = IRemoteCallback.Stub.asInterface(
+ parcel.readStrongBinder());
}
-
- public void sendResult(Bundle bundle) throws RemoteException {
- mTarget.sendResult(bundle);
- }
-
- protected abstract void onResult(Bundle bundle);
-
- public boolean equals(Object otherObj) {
- if (otherObj == null) {
- return false;
+
+ public void sendResult(@Nullable final Bundle result) {
+ // Do local dispatch
+ if (mListener != null) {
+ if (mHandler != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onResult(result);
+ }
+ });
+ } else {
+ mListener.onResult(result);
+ }
+ // Do remote dispatch
+ } else {
+ try {
+ mCallback.sendResult(result);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
}
- try {
- return mTarget.asBinder().equals(((RemoteCallback)otherObj)
- .mTarget.asBinder());
- } catch (ClassCastException e) {
- }
- return false;
}
-
- public int hashCode() {
- return mTarget.asBinder().hashCode();
- }
-
+
+ @Override
public int describeContents() {
return 0;
}
- public void writeToParcel(Parcel out, int flags) {
- out.writeStrongBinder(mTarget.asBinder());
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeStrongBinder(mCallback.asBinder());
}
public static final Parcelable.Creator<RemoteCallback> CREATOR
= new Parcelable.Creator<RemoteCallback>() {
- public RemoteCallback createFromParcel(Parcel in) {
- IBinder target = in.readStrongBinder();
- return target != null ? new RemoteCallbackProxy(
- IRemoteCallback.Stub.asInterface(target)) : null;
+ public RemoteCallback createFromParcel(Parcel parcel) {
+ return new RemoteCallback(parcel);
}
public RemoteCallback[] newArray(int size) {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 0a149bb..f26693c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -929,9 +929,6 @@
if (guest != null) {
Settings.Secure.putStringForUser(context.getContentResolver(),
Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
-
- mService.setUserRestriction(DISALLOW_SMS, true, guest.id);
- mService.setUserRestriction(DISALLOW_INSTALL_UNKNOWN_SOURCES, true, guest.id);
}
} catch (RemoteException re) {
Log.w(TAG, "Could not create a user", re);
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index db2bf1a..b7cfbea 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -41,6 +41,23 @@
void createPrintJob(in PrintJobInfo printJob);
void setPrintJobState(in PrintJobId printJobId, int status, String stateReason,
IPrintSpoolerCallbacks callback, int sequence);
+
+ /**
+ * Set the progress of this print job
+ *
+ * @param printJobId The print job to update
+ * @param progress The new progress
+ */
+ void setProgress(in PrintJobId printJobId, in float progress);
+
+ /**
+ * Set the status of this print job
+ *
+ * @param printJobId The print job to update
+ * @param status The new status, can be null
+ */
+ void setStatus(in PrintJobId printJobId, in CharSequence status);
+
void setPrintJobTag(in PrintJobId printJobId, String tag, IPrintSpoolerCallbacks callback,
int sequence);
void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId);
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 63f94fe..7148c87 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -16,10 +16,15 @@
package android.print;
+import android.annotation.FloatRange;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
import java.util.Arrays;
/**
@@ -149,9 +154,6 @@
/** How many copies to print. */
private int mCopies;
- /** Reason for the print job being in its current state. */
- private String mStateReason;
-
/** The pages to print */
private PageRange[] mPageRanges;
@@ -161,6 +163,12 @@
/** Information about the printed document. */
private PrintDocumentInfo mDocumentInfo;
+ /** The progress made on printing this job or -1 if not set. */
+ private float mProgress;
+
+ /** A short string describing the status of this job. */
+ private CharSequence mStatus;
+
/** Advanced printer specific options. */
private Bundle mAdvancedOptions;
@@ -169,7 +177,7 @@
/** @hide*/
public PrintJobInfo() {
- /* do nothing */
+ mProgress = -1;
}
/** @hide */
@@ -183,10 +191,11 @@
mTag = other.mTag;
mCreationTime = other.mCreationTime;
mCopies = other.mCopies;
- mStateReason = other.mStateReason;
mPageRanges = other.mPageRanges;
mAttributes = other.mAttributes;
mDocumentInfo = other.mDocumentInfo;
+ mProgress = other.mProgress;
+ mStatus = other.mStatus;
mCanceling = other.mCanceling;
mAdvancedOptions = other.mAdvancedOptions;
}
@@ -201,7 +210,6 @@
mTag = parcel.readString();
mCreationTime = parcel.readLong();
mCopies = parcel.readInt();
- mStateReason = parcel.readString();
Parcelable[] parcelables = parcel.readParcelableArray(null);
if (parcelables != null) {
mPageRanges = new PageRange[parcelables.length];
@@ -211,6 +219,8 @@
}
mAttributes = (PrintAttributes) parcel.readParcelable(null);
mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null);
+ mProgress = parcel.readFloat();
+ mStatus = parcel.readCharSequence();
mCanceling = (parcel.readInt() == 1);
mAdvancedOptions = parcel.readBundle();
}
@@ -227,7 +237,7 @@
/**
* Sets the unique print job id.
*
- * @param The job id.
+ * @param id The job id.
*
* @hide
*/
@@ -265,7 +275,7 @@
}
/**
- * Sets the unique target pritner id.
+ * Sets the unique target printer id.
*
* @param printerId The target printer id.
*
@@ -326,6 +336,30 @@
}
/**
+ * Sets the progress of the print job.
+ *
+ * @param progress the progress of the job
+ *
+ * @hide
+ */
+ public void setProgress(@FloatRange(from=0.0, to=1.0) float progress) {
+ Preconditions.checkArgumentInRange(progress, 0, 1, "progress");
+
+ mProgress = progress;
+ }
+
+ /**
+ * Sets the status of the print job.
+ *
+ * @param status the status of the job, can be null
+ *
+ * @hide
+ */
+ public void setStatus(@Nullable CharSequence status) {
+ mStatus = status;
+ }
+
+ /**
* Sets the owning application id.
*
* @return The owning app id.
@@ -416,30 +450,6 @@
}
/**
- * Gets the reason for the print job being in the current state.
- *
- * @return The reason, or null if there is no reason or the
- * reason is unknown.
- *
- * @hide
- */
- public String getStateReason() {
- return mStateReason;
- }
-
- /**
- * Sets the reason for the print job being in the current state.
- *
- * @param stateReason The reason, or null if there is no reason
- * or the reason is unknown.
- *
- * @hide
- */
- public void setStateReason(String stateReason) {
- mStateReason = stateReason;
- }
-
- /**
* Gets the included pages.
*
* @return The included pages or <code>null</code> if not set.
@@ -604,10 +614,11 @@
parcel.writeString(mTag);
parcel.writeLong(mCreationTime);
parcel.writeInt(mCopies);
- parcel.writeString(mStateReason);
parcel.writeParcelableArray(mPageRanges, flags);
parcel.writeParcelable(mAttributes, flags);
parcel.writeParcelable(mDocumentInfo, 0);
+ parcel.writeFloat(mProgress);
+ parcel.writeCharSequence(mStatus);
parcel.writeInt(mCanceling ? 1 : 0);
parcel.writeBundle(mAdvancedOptions);
}
@@ -631,6 +642,9 @@
builder.append(", pages: " + (mPageRanges != null
? Arrays.toString(mPageRanges) : null));
builder.append(", hasAdvancedOptions: " + (mAdvancedOptions != null));
+ builder.append(", progress: " + mProgress);
+ builder.append(", status: " + (mStatus != null
+ ? mStatus.toString() : null));
builder.append("}");
return builder.toString();
}
@@ -666,6 +680,28 @@
}
/**
+ * Get the progress that has been made printing this job.
+ *
+ * @return the print progress or -1 if not set
+ * @hide
+ */
+ @TestApi
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Get the status of this job.
+ *
+ * @return the status of this job or null if not set
+ * @hide
+ */
+ @TestApi
+ public @Nullable CharSequence getStatus() {
+ return mStatus;
+ }
+
+ /**
* Builder for creating a {@link PrintJobInfo}.
*/
public static final class Builder {
@@ -711,6 +747,28 @@
}
/**
+ * Sets the progress of the print job.
+ *
+ * @param progress the progress of the job
+ * @hide
+ */
+ public void setProgress(@FloatRange(from=0.0, to=1.0) float progress) {
+ Preconditions.checkArgumentInRange(progress, 0, 1, "progress");
+
+ mPrototype.mProgress = progress;
+ }
+
+ /**
+ * Sets the status of the print job.
+ *
+ * @param status the status of the job, can be null
+ * @hide
+ */
+ public void setStatus(@Nullable CharSequence status) {
+ mPrototype.mStatus = status;
+ }
+
+ /**
* Puts an advanced (printer specific) option.
*
* @param key The option key.
diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl
index c2dfc30..b4baa48 100644
--- a/core/java/android/printservice/IPrintServiceClient.aidl
+++ b/core/java/android/printservice/IPrintServiceClient.aidl
@@ -35,6 +35,22 @@
boolean setPrintJobTag(in PrintJobId printJobId, String tag);
oneway void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId);
+ /**
+ * Set the progress of this print job
+ *
+ * @param printJobId The print job to update
+ * @param progress The new progress
+ */
+ void setProgress(in PrintJobId printJobId, in float progress);
+
+ /**
+ * Set the status of this print job
+ *
+ * @param printJobId The print job to update
+ * @param status The new status, can be null
+ */
+ void setStatus(in PrintJobId printJobId, in CharSequence status);
+
void onPrintersAdded(in ParceledListSlice printers);
void onPrintersRemoved(in ParceledListSlice printerIds);
}
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 6fa0bdd..86fc292 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -16,6 +16,10 @@
package android.printservice;
+import android.annotation.FloatRange;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.RemoteException;
import android.print.PrintJobId;
import android.print.PrintJobInfo;
@@ -41,7 +45,7 @@
private PrintJobInfo mCachedInfo;
- PrintJob(PrintJobInfo jobInfo, IPrintServiceClient client) {
+ PrintJob(@NonNull PrintJobInfo jobInfo, @NonNull IPrintServiceClient client) {
mCachedInfo = jobInfo;
mPrintServiceClient = client;
mDocument = new PrintDocument(mCachedInfo.getId(), client,
@@ -53,6 +57,7 @@
*
* @return The id.
*/
+ @MainThread
public PrintJobId getId() {
PrintService.throwIfNotCalledOnMainThread();
return mCachedInfo.getId();
@@ -68,7 +73,8 @@
*
* @return The print job info.
*/
- public PrintJobInfo getInfo() {
+ @MainThread
+ public @NonNull PrintJobInfo getInfo() {
PrintService.throwIfNotCalledOnMainThread();
if (isInImmutableState()) {
return mCachedInfo;
@@ -90,7 +96,8 @@
*
* @return The document.
*/
- public PrintDocument getDocument() {
+ @MainThread
+ public @NonNull PrintDocument getDocument() {
PrintService.throwIfNotCalledOnMainThread();
return mDocument;
}
@@ -104,6 +111,7 @@
* @see #start()
* @see #cancel()
*/
+ @MainThread
public boolean isQueued() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_QUEUED;
@@ -117,8 +125,9 @@
*
* @see #complete()
* @see #cancel()
- * @see #fail(CharSequence)
+ * @see #fail(String)
*/
+ @MainThread
public boolean isStarted() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_STARTED;
@@ -132,8 +141,9 @@
*
* @see #start()
* @see #cancel()
- * @see #fail(CharSequence)
+ * @see #fail(String)
*/
+ @MainThread
public boolean isBlocked() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_BLOCKED;
@@ -147,6 +157,7 @@
*
* @see #complete()
*/
+ @MainThread
public boolean isCompleted() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_COMPLETED;
@@ -158,8 +169,9 @@
*
* @return Whether the print job is failed.
*
- * @see #fail(CharSequence)
+ * @see #fail(String)
*/
+ @MainThread
public boolean isFailed() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_FAILED;
@@ -173,6 +185,7 @@
*
* @see #cancel()
*/
+ @MainThread
public boolean isCancelled() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_CANCELED;
@@ -182,12 +195,16 @@
* Starts the print job. You should call this method if {@link
* #isQueued()} or {@link #isBlocked()} returns true and you started
* resumed printing.
+ * <p>
+ * This resets the print status to null. Set the new status by using {@link #setStatus}.
+ * </p>
*
* @return Whether the job was started.
*
* @see #isQueued()
* @see #isBlocked()
*/
+ @MainThread
public boolean start() {
PrintService.throwIfNotCalledOnMainThread();
final int state = getInfo().getState();
@@ -205,18 +222,20 @@
* paper to continue printing. To resume the print job call {@link
* #start()}.
*
+ * @param reason The human readable, short, and translated reason why the print job is blocked.
* @return Whether the job was blocked.
*
* @see #isStarted()
* @see #isBlocked()
*/
- public boolean block(String reason) {
+ @MainThread
+ public boolean block(@Nullable String reason) {
PrintService.throwIfNotCalledOnMainThread();
PrintJobInfo info = getInfo();
final int state = info.getState();
if (state == PrintJobInfo.STATE_STARTED
|| (state == PrintJobInfo.STATE_BLOCKED
- && !TextUtils.equals(info.getStateReason(), reason))) {
+ && !TextUtils.equals(info.getStatus(), reason))) {
return setState(PrintJobInfo.STATE_BLOCKED, reason);
}
return false;
@@ -230,6 +249,7 @@
*
* @see #isStarted()
*/
+ @MainThread
public boolean complete() {
PrintService.throwIfNotCalledOnMainThread();
if (isStarted()) {
@@ -251,7 +271,8 @@
* @see #isStarted()
* @see #isBlocked()
*/
- public boolean fail(String error) {
+ @MainThread
+ public boolean fail(@Nullable String error) {
PrintService.throwIfNotCalledOnMainThread();
if (!isInImmutableState()) {
return setState(PrintJobInfo.STATE_FAILED, error);
@@ -271,6 +292,7 @@
* @see #isQueued()
* @see #isBlocked()
*/
+ @MainThread
public boolean cancel() {
PrintService.throwIfNotCalledOnMainThread();
if (!isInImmutableState()) {
@@ -280,6 +302,39 @@
}
/**
+ * Sets the progress of this print job as a fraction of 1.
+ *
+ * @param progress The new progress
+ */
+ @MainThread
+ public void setProgress(@FloatRange(from=0.0, to=1.0) float progress) {
+ PrintService.throwIfNotCalledOnMainThread();
+
+ try {
+ mPrintServiceClient.setProgress(mCachedInfo.getId(), progress);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error setting progress for job: " + mCachedInfo.getId(), re);
+ }
+ }
+
+ /**
+ * Sets the status of this print job. This should be a human readable, short, and translated
+ * description of the current state of the print job.
+ *
+ * @param status The new status. If null the status will be empty.
+ */
+ @MainThread
+ public void setStatus(@Nullable CharSequence status) {
+ PrintService.throwIfNotCalledOnMainThread();
+
+ try {
+ mPrintServiceClient.setStatus(mCachedInfo.getId(), status);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error setting status for job: " + mCachedInfo.getId(), re);
+ }
+ }
+
+ /**
* Sets a tag that is valid in the context of a {@link PrintService}
* and is not interpreted by the system. For example, a print service
* may set as a tag the key of the print job returned by a remote
@@ -288,6 +343,7 @@
* @param tag The tag.
* @return True if the tag was set, false otherwise.
*/
+ @MainThread
public boolean setTag(String tag) {
PrintService.throwIfNotCalledOnMainThread();
if (isInImmutableState()) {
@@ -319,6 +375,7 @@
* @param key The option key.
* @return The option value.
*/
+ @MainThread
public String getAdvancedStringOption(String key) {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getAdvancedStringOption(key);
@@ -331,6 +388,7 @@
* @param key The option key.
* @return Whether the option is present.
*/
+ @MainThread
public boolean hasAdvancedOption(String key) {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().hasAdvancedOption(key);
@@ -342,6 +400,7 @@
* @param key The option key.
* @return The option value.
*/
+ @MainThread
public int getAdvancedIntOption(String key) {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getAdvancedIntOption(key);
@@ -374,14 +433,14 @@
|| state == PrintJobInfo.STATE_FAILED;
}
- private boolean setState(int state, String error) {
+ private boolean setState(int state, @Nullable String error) {
try {
if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state, error)) {
// Best effort - update the state of the cached info since
// we may not be able to re-fetch it later if the job gets
// removed from the spooler as a result of the state change.
mCachedInfo.setState(state);
- mCachedInfo.setStateReason(error);
+ mCachedInfo.setStatus(error);
return true;
}
} catch (RemoteException re) {
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 8ae899f..d53bb0d 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -67,7 +67,7 @@
* @see DocumentsProvider
*/
public final class DocumentsContract {
- private static final String TAG = "Documents";
+ private static final String TAG = "DocumentsContract";
// content://com.example/root/
// content://com.example/root/sdcard/
@@ -591,6 +591,12 @@
*/
public static final String EXTRA_ERROR = "error";
+ /**
+ * Optional result (I'm thinking boolean) answer to a question.
+ * {@hide}
+ */
+ public static final String EXTRA_RESULT = "result";
+
/** {@hide} */
public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
/** {@hide} */
@@ -601,6 +607,8 @@
public static final String METHOD_COPY_DOCUMENT = "android:copyDocument";
/** {@hide} */
public static final String METHOD_MOVE_DOCUMENT = "android:moveDocument";
+ /** {@hide} */
+ public static final String METHOD_IS_CHILD_DOCUMENT = "android:isChildDocument";
/** {@hide} */
public static final String EXTRA_URI = "uri";
@@ -1025,6 +1033,24 @@
return out.getParcelable(DocumentsContract.EXTRA_URI);
}
+ /** {@hide} */
+ public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri,
+ Uri childDocumentUri) throws RemoteException {
+
+ final Bundle in = new Bundle();
+ in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
+ in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri);
+
+ final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in);
+ if (out == null) {
+ throw new RemoteException("Failed to get a reponse from isChildDocument query.");
+ }
+ if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
+ throw new RemoteException("Response did not include result field..");
+ }
+ return out.getBoolean(DocumentsContract.EXTRA_RESULT);
+ }
+
/**
* Change the display name of an existing document.
* <p>
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index f01073b..e25ba35 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -16,11 +16,12 @@
package android.provider;
+import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
-import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
-import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
import static android.provider.DocumentsContract.buildDocumentUri;
import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
import static android.provider.DocumentsContract.buildTreeDocumentUri;
@@ -688,6 +689,16 @@
return super.call(method, arg, extras);
}
+ try {
+ return callUnchecked(method, arg, extras);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException("Failed call " + method, e);
+ }
+ }
+
+ private Bundle callUnchecked(String method, String arg, Bundle extras)
+ throws FileNotFoundException {
+
final Context context = getContext();
final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
final String authority = documentUri.getAuthority();
@@ -697,109 +708,120 @@
throw new SecurityException(
"Requested authority " + authority + " doesn't match provider " + mAuthority);
}
- enforceTree(documentUri);
final Bundle out = new Bundle();
- try {
- if (METHOD_CREATE_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri, getCallingPackage(), null);
- final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
- final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
- final String newDocumentId = createDocument(documentId, mimeType, displayName);
+ // If the URI is a tree URI performs some validation.
+ enforceTree(documentUri);
- // No need to issue new grants here, since caller either has
- // manage permission or a prefix grant. We might generate a
- // tree style URI if that's how they called us.
+ if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
+ enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+
+ final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+ final String childAuthority = childUri.getAuthority();
+ final String childId = DocumentsContract.getDocumentId(childUri);
+
+ out.putBoolean(
+ DocumentsContract.EXTRA_RESULT,
+ mAuthority.equals(childAuthority)
+ && isChildDocument(documentId, childId));
+
+ } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
+ enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+
+ final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
+ final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
+ final String newDocumentId = createDocument(documentId, mimeType, displayName);
+
+ // No need to issue new grants here, since caller either has
+ // manage permission or a prefix grant. We might generate a
+ // tree style URI if that's how they called us.
+ final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+ newDocumentId);
+ out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+
+ } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
+ enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+
+ final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
+ final String newDocumentId = renameDocument(documentId, displayName);
+
+ if (newDocumentId != null) {
final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
newDocumentId);
+
+ // If caller came in with a narrow grant, issue them a
+ // narrow grant for the newly renamed document.
+ if (!isTreeUri(newDocumentUri)) {
+ final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+ documentUri);
+ context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
+ }
+
out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
- } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri, getCallingPackage(), null);
-
- final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
- final String newDocumentId = renameDocument(documentId, displayName);
-
- if (newDocumentId != null) {
- final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
- newDocumentId);
-
- // If caller came in with a narrow grant, issue them a
- // narrow grant for the newly renamed document.
- if (!isTreeUri(newDocumentUri)) {
- final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
- documentUri);
- context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
- }
-
- out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
-
- // Original document no longer exists, clean up any grants
- revokeDocumentPermission(documentId);
- }
-
- } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri, getCallingPackage(), null);
- deleteDocument(documentId);
-
- // Document no longer exists, clean up any grants
- revokeDocumentPermission(documentId);
-
- } else if (METHOD_COPY_DOCUMENT.equals(method)) {
- final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
- final String targetId = DocumentsContract.getDocumentId(targetUri);
-
- enforceReadPermissionInner(documentUri, getCallingPackage(), null);
- enforceWritePermissionInner(targetUri, getCallingPackage(), null);
-
- final String newDocumentId = copyDocument(documentId, targetId);
-
- if (newDocumentId != null) {
- final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
- newDocumentId);
-
- if (!isTreeUri(newDocumentUri)) {
- final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
- documentUri);
- context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
- }
-
- out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
- }
-
- } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
- final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
- final String targetId = DocumentsContract.getDocumentId(targetUri);
-
- enforceReadPermissionInner(documentUri, getCallingPackage(), null);
- enforceWritePermissionInner(targetUri, getCallingPackage(), null);
-
- final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
- final String newDocumentId = moveDocument(documentId, targetId);
-
- if (newDocumentId != null) {
- final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
- newDocumentId);
-
- if (!isTreeUri(newDocumentUri)) {
- final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
- documentUri);
- context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
- }
-
- out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
- }
-
// Original document no longer exists, clean up any grants
revokeDocumentPermission(documentId);
-
- } else {
- throw new UnsupportedOperationException("Method not supported " + method);
}
- } catch (FileNotFoundException e) {
- throw new IllegalStateException("Failed call " + method, e);
+
+ } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
+ enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+ deleteDocument(documentId);
+
+ // Document no longer exists, clean up any grants
+ revokeDocumentPermission(documentId);
+
+ } else if (METHOD_COPY_DOCUMENT.equals(method)) {
+ final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+ final String targetId = DocumentsContract.getDocumentId(targetUri);
+
+ enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+ enforceWritePermissionInner(targetUri, getCallingPackage(), null);
+
+ final String newDocumentId = copyDocument(documentId, targetId);
+
+ if (newDocumentId != null) {
+ final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+ newDocumentId);
+
+ if (!isTreeUri(newDocumentUri)) {
+ final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+ documentUri);
+ context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
+ }
+
+ out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+ }
+
+ } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
+ final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+ final String targetId = DocumentsContract.getDocumentId(targetUri);
+
+ enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+ enforceWritePermissionInner(targetUri, getCallingPackage(), null);
+
+ final String newDocumentId = moveDocument(documentId, targetId);
+
+ if (newDocumentId != null) {
+ final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+ newDocumentId);
+
+ if (!isTreeUri(newDocumentUri)) {
+ final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+ documentUri);
+ context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
+ }
+
+ out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+ }
+
+ // Original document no longer exists, clean up any grants
+ revokeDocumentPermission(documentId);
+
+ } else {
+ throw new UnsupportedOperationException("Method not supported " + method);
}
+
return out;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f560f8a..c92382a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4420,8 +4420,19 @@
* to receive changes in this value.
*/
public static final String LOCATION_MODE = "location_mode";
+ /**
+ * Stores the previous location mode when {@link #LOCATION_MODE} is set to
+ * {@link #LOCATION_MODE_OFF}
+ * @hide
+ */
+ public static final String LOCATION_PREVIOUS_MODE = "location_previous_mode";
/**
+ * Sets all location providers to the previous states before location was turned off.
+ * @hide
+ */
+ public static final int LOCATION_MODE_PREVIOUS = -1;
+ /**
* Location access disabled.
*/
public static final int LOCATION_MODE_OFF = 0;
@@ -5795,6 +5806,7 @@
CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
+ CLONE_TO_MANAGED_PROFILE.add(LOCATION_PREVIOUS_MODE);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
CLONE_TO_MANAGED_PROFILE.add(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
@@ -5882,6 +5894,28 @@
}
/**
+ * Saves the current location mode into {@link #LOCATION_PREVIOUS_MODE}.
+ */
+ private static final boolean saveLocationModeForUser(ContentResolver cr, int userId) {
+ final int mode = getLocationModeForUser(cr, userId);
+ return putIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE, mode, userId);
+ }
+
+ /**
+ * Restores the current location mode from {@link #LOCATION_PREVIOUS_MODE}.
+ */
+ private static final boolean restoreLocationModeForUser(ContentResolver cr, int userId) {
+ int mode = getIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE,
+ LOCATION_MODE_HIGH_ACCURACY, userId);
+ // Make sure that the previous mode is never "off". Otherwise the user won't be able to
+ // turn on location any longer.
+ if (mode == LOCATION_MODE_OFF) {
+ mode = LOCATION_MODE_HIGH_ACCURACY;
+ }
+ return setLocationModeForUser(cr, mode, userId);
+ }
+
+ /**
* Thread-safe method for setting the location mode to one of
* {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
* {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
@@ -5899,7 +5933,11 @@
boolean gps = false;
boolean network = false;
switch (mode) {
+ case LOCATION_MODE_PREVIOUS:
+ // Retrieve the actual mode and set to that mode.
+ return restoreLocationModeForUser(cr, userId);
case LOCATION_MODE_OFF:
+ saveLocationModeForUser(cr, userId);
break;
case LOCATION_MODE_SENSORS_ONLY:
gps = true;
diff --git a/core/java/android/security/net/config/ApplicationConfig.java b/core/java/android/security/net/config/ApplicationConfig.java
index 48359d47..b627641 100644
--- a/core/java/android/security/net/config/ApplicationConfig.java
+++ b/core/java/android/security/net/config/ApplicationConfig.java
@@ -144,18 +144,4 @@
return sInstance;
}
}
-
- /** @hide */
- public static ApplicationConfig getPlatformDefault() {
- return new ApplicationConfig(new ConfigSource() {
- @Override
- public NetworkSecurityConfig getDefaultConfig() {
- return NetworkSecurityConfig.DEFAULT;
- }
- @Override
- public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
- return null;
- }
- });
- }
}
diff --git a/core/java/android/security/net/config/CertificateSource.java b/core/java/android/security/net/config/CertificateSource.java
index 386354d..2b7829e 100644
--- a/core/java/android/security/net/config/CertificateSource.java
+++ b/core/java/android/security/net/config/CertificateSource.java
@@ -22,4 +22,5 @@
/** @hide */
public interface CertificateSource {
Set<X509Certificate> getCertificates();
+ X509Certificate findBySubjectAndPublicKey(X509Certificate cert);
}
diff --git a/core/java/android/security/net/config/CertificatesEntryRef.java b/core/java/android/security/net/config/CertificatesEntryRef.java
index 2ba38c21..1d15e19 100644
--- a/core/java/android/security/net/config/CertificatesEntryRef.java
+++ b/core/java/android/security/net/config/CertificatesEntryRef.java
@@ -30,6 +30,10 @@
mOverridesPins = overridesPins;
}
+ boolean overridesPins() {
+ return mOverridesPins;
+ }
+
public Set<TrustAnchor> getTrustAnchors() {
// TODO: cache this [but handle mutable sources]
Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>();
@@ -38,4 +42,13 @@
}
return anchors;
}
+
+ public TrustAnchor findBySubjectAndPublicKey(X509Certificate cert) {
+ X509Certificate foundCert = mSource.findBySubjectAndPublicKey(cert);
+ if (foundCert == null) {
+ return null;
+ }
+
+ return new TrustAnchor(foundCert, mOverridesPins);
+ }
}
diff --git a/core/java/android/security/net/config/DirectoryCertificateSource.java b/core/java/android/security/net/config/DirectoryCertificateSource.java
new file mode 100644
index 0000000..a261e06
--- /dev/null
+++ b/core/java/android/security/net/config/DirectoryCertificateSource.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.net.config;
+
+import android.os.Environment;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Pair;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Set;
+import libcore.io.IoUtils;
+
+import com.android.org.conscrypt.Hex;
+import com.android.org.conscrypt.NativeCrypto;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * {@link CertificateSource} based on a directory where certificates are stored as individual files
+ * named after a hash of their SubjectName for more efficient lookups.
+ * @hide
+ */
+abstract class DirectoryCertificateSource implements CertificateSource {
+ private final File mDir;
+ private final Object mLock = new Object();
+ private final CertificateFactory mCertFactory;
+
+ private Set<X509Certificate> mCertificates;
+
+ protected DirectoryCertificateSource(File caDir) {
+ mDir = caDir;
+ try {
+ mCertFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+ }
+ }
+
+ protected abstract boolean isCertMarkedAsRemoved(String caFile);
+
+ @Override
+ public Set<X509Certificate> getCertificates() {
+ // TODO: loading all of these is wasteful, we should instead use a keystore style API.
+ synchronized (mLock) {
+ if (mCertificates != null) {
+ return mCertificates;
+ }
+
+ Set<X509Certificate> certs = new ArraySet<X509Certificate>();
+ if (mDir.isDirectory()) {
+ for (String caFile : mDir.list()) {
+ if (isCertMarkedAsRemoved(caFile)) {
+ continue;
+ }
+ X509Certificate cert = readCertificate(caFile);
+ if (cert != null) {
+ certs.add(cert);
+ }
+ }
+ }
+ mCertificates = certs;
+ return mCertificates;
+ }
+ }
+
+ @Override
+ public X509Certificate findBySubjectAndPublicKey(final X509Certificate cert) {
+ return findCert(cert.getSubjectX500Principal(), new CertSelector() {
+ @Override
+ public boolean match(X509Certificate ca) {
+ return ca.getPublicKey().equals(cert.getPublicKey());
+ }
+ });
+ }
+
+ private static interface CertSelector {
+ boolean match(X509Certificate cert);
+ }
+
+ private X509Certificate findCert(X500Principal subj, CertSelector selector) {
+ String hash = getHash(subj);
+ for (int index = 0; index >= 0; index++) {
+ String fileName = hash + "." + index;
+ if (!new File(mDir, fileName).exists()) {
+ break;
+ }
+ if (isCertMarkedAsRemoved(fileName)) {
+ continue;
+ }
+ X509Certificate cert = readCertificate(fileName);
+ if (!subj.equals(cert.getSubjectX500Principal())) {
+ continue;
+ }
+ if (selector.match(cert)) {
+ return cert;
+ }
+ }
+ return null;
+ }
+
+ private String getHash(X500Principal name) {
+ int hash = NativeCrypto.X509_NAME_hash_old(name);
+ return Hex.intToHexString(hash, 8);
+ }
+
+ private X509Certificate readCertificate(String file) {
+ InputStream is = null;
+ try {
+ is = new BufferedInputStream(new FileInputStream(new File(mDir, file)));
+ return (X509Certificate) mCertFactory.generateCertificate(is);
+ } catch (CertificateException | IOException e) {
+ return null;
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+}
diff --git a/core/java/android/security/net/config/KeyStoreCertificateSource.java b/core/java/android/security/net/config/KeyStoreCertificateSource.java
index 1973ef1..7a01a64 100644
--- a/core/java/android/security/net/config/KeyStoreCertificateSource.java
+++ b/core/java/android/security/net/config/KeyStoreCertificateSource.java
@@ -24,6 +24,8 @@
import java.util.Enumeration;
import java.util.Set;
+import com.android.org.conscrypt.TrustedCertificateIndex;
+
/**
* {@link CertificateSource} which provides certificates from trusted certificate entries of a
* {@link KeyStore}.
@@ -31,6 +33,7 @@
class KeyStoreCertificateSource implements CertificateSource {
private final Object mLock = new Object();
private final KeyStore mKeyStore;
+ private TrustedCertificateIndex mIndex;
private Set<X509Certificate> mCertificates;
public KeyStoreCertificateSource(KeyStore ks) {
@@ -39,27 +42,42 @@
@Override
public Set<X509Certificate> getCertificates() {
+ ensureInitialized();
+ return mCertificates;
+ }
+
+ private void ensureInitialized() {
synchronized (mLock) {
if (mCertificates != null) {
- return mCertificates;
+ return;
}
+
try {
+ TrustedCertificateIndex localIndex = new TrustedCertificateIndex();
Set<X509Certificate> certificates = new ArraySet<>(mKeyStore.size());
for (Enumeration<String> en = mKeyStore.aliases(); en.hasMoreElements();) {
String alias = en.nextElement();
- if (!mKeyStore.isCertificateEntry(alias)) {
- continue;
- }
X509Certificate cert = (X509Certificate) mKeyStore.getCertificate(alias);
if (cert != null) {
certificates.add(cert);
+ localIndex.index(cert);
}
}
+ mIndex = localIndex;
mCertificates = certificates;
- return mCertificates;
} catch (KeyStoreException e) {
throw new RuntimeException("Failed to load certificates from KeyStore", e);
}
}
}
+
+ @Override
+ public X509Certificate findBySubjectAndPublicKey(X509Certificate cert) {
+ ensureInitialized();
+ java.security.cert.TrustAnchor anchor = mIndex.findBySubjectAndPublicKey(cert);
+ if (anchor == null) {
+ return null;
+ }
+ return anchor.getTrustedCert();
+ }
}
diff --git a/core/java/android/security/net/config/ManifestConfigSource.java b/core/java/android/security/net/config/ManifestConfigSource.java
new file mode 100644
index 0000000..bf1fb8a
--- /dev/null
+++ b/core/java/android/security/net/config/ManifestConfigSource.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.net.config;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import android.util.Pair;
+import java.util.Set;
+
+/** @hide */
+public class ManifestConfigSource implements ConfigSource {
+ public static final String META_DATA_NETWORK_SECURITY_CONFIG =
+ "android.security.net.config";
+ private static final boolean DBG = true;
+ private static final String LOG_TAG = "NetworkSecurityConfig";
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+
+ private ConfigSource mConfigSource;
+
+ public ManifestConfigSource(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+ return getConfigSource().getPerDomainConfigs();
+ }
+
+ @Override
+ public NetworkSecurityConfig getDefaultConfig() {
+ return getConfigSource().getDefaultConfig();
+ }
+
+ private ConfigSource getConfigSource() {
+ synchronized (mLock) {
+ if (mConfigSource != null) {
+ return mConfigSource;
+ }
+ ApplicationInfo info;
+ try {
+ info = mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(),
+ PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException("Failed to look up ApplicationInfo", e);
+ }
+ int configResourceId = 0;
+ if (info != null && info.metaData != null) {
+ configResourceId = info.metaData.getInt(META_DATA_NETWORK_SECURITY_CONFIG);
+ }
+
+ ConfigSource source;
+ if (configResourceId != 0) {
+ boolean debugBuild = (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ if (DBG) {
+ Log.d(LOG_TAG, "Using Network Security Config from resource "
+ + mContext.getResources().getResourceEntryName(configResourceId)
+ + " debugBuild: " + debugBuild);
+ }
+ source = new XmlConfigSource(mContext, configResourceId, debugBuild);
+ } else {
+ if (DBG) {
+ Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
+ }
+ source = new DefaultConfigSource();
+ }
+ mConfigSource = source;
+ return mConfigSource;
+ }
+ }
+
+ private static final class DefaultConfigSource implements ConfigSource {
+ @Override
+ public NetworkSecurityConfig getDefaultConfig() {
+ return NetworkSecurityConfig.DEFAULT;
+ }
+
+ @Override
+ public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+ return null;
+ }
+ }
+}
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 9eab80c..2ab07b5 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -22,6 +22,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -53,6 +54,19 @@
mHstsEnforced = hstsEnforced;
mPins = pins;
mCertificatesEntryRefs = certificatesEntryRefs;
+ // Sort the certificates entry refs so that all entries that override pins come before
+ // non-override pin entries. This allows us to handle the case where a certificate is in
+ // multiple entry refs by returning the certificate from the first entry ref.
+ Collections.sort(mCertificatesEntryRefs, new Comparator<CertificatesEntryRef>() {
+ @Override
+ public int compare(CertificatesEntryRef lhs, CertificatesEntryRef rhs) {
+ if (lhs.overridesPins()) {
+ return rhs.overridesPins() ? 0 : -1;
+ } else {
+ return rhs.overridesPins() ? 1 : 0;
+ }
+ }
+ });
}
public Set<TrustAnchor> getTrustAnchors() {
@@ -63,14 +77,15 @@
// Merge trust anchors based on the X509Certificate.
// If we see the same certificate in two TrustAnchors, one with overridesPins and one
// without, the one with overridesPins wins.
+ // Because mCertificatesEntryRefs is sorted with all overridesPins anchors coming first
+ // this can be simplified to just using the first occurrence of a certificate.
Map<X509Certificate, TrustAnchor> anchorMap = new ArrayMap<>();
for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
Set<TrustAnchor> anchors = ref.getTrustAnchors();
for (TrustAnchor anchor : anchors) {
- if (anchor.overridesPins) {
- anchorMap.put(anchor.certificate, anchor);
- } else if (!anchorMap.containsKey(anchor.certificate)) {
- anchorMap.put(anchor.certificate, anchor);
+ X509Certificate cert = anchor.certificate;
+ if (!anchorMap.containsKey(cert)) {
+ anchorMap.put(cert, anchor);
}
}
}
@@ -108,6 +123,17 @@
}
}
+ /** @hide */
+ public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {
+ for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
+ TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert);
+ if (anchor != null) {
+ return anchor;
+ }
+ }
+ return null;
+ }
+
/**
* Return a {@link Builder} for the default {@code NetworkSecurityConfig}.
*
diff --git a/core/java/android/security/net/config/NetworkSecurityConfigProvider.java b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
index ac762ef..5ebc7ac 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
@@ -17,20 +17,13 @@
package android.security.net.config;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.util.Log;
import java.security.Security;
import java.security.Provider;
/** @hide */
public final class NetworkSecurityConfigProvider extends Provider {
- private static final String LOG_TAG = "NetworkSecurityConfig";
private static final String PREFIX =
NetworkSecurityConfigProvider.class.getPackage().getName() + ".";
- public static final String META_DATA_NETWORK_SECURITY_CONFIG =
- "android.security.net.config";
- private static final boolean DBG = true;
public NetworkSecurityConfigProvider() {
// TODO: More clever name than this
@@ -40,36 +33,7 @@
}
public static void install(Context context) {
- ApplicationInfo info = null;
- // TODO: This lookup shouldn't be done in the app startup path, it should be done lazily.
- try {
- info = context.getPackageManager().getApplicationInfo(context.getPackageName(),
- PackageManager.GET_META_DATA);
- } catch (PackageManager.NameNotFoundException e) {
- throw new RuntimeException("Failed to look up ApplicationInfo", e);
- }
- int configResourceId = 0;
- if (info != null && info.metaData != null) {
- configResourceId = info.metaData.getInt(META_DATA_NETWORK_SECURITY_CONFIG);
- }
-
- ApplicationConfig config;
- if (configResourceId != 0) {
- boolean debugBuild = (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
- if (DBG) {
- Log.d(LOG_TAG, "Using Network Security Config from resource "
- + context.getResources().getResourceEntryName(configResourceId)
- + " debugBuild: " + debugBuild);
- }
- ConfigSource source = new XmlConfigSource(context, configResourceId, debugBuild);
- config = new ApplicationConfig(source);
- } else {
- if (DBG) {
- Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
- }
- config = ApplicationConfig.getPlatformDefault();
- }
-
+ ApplicationConfig config = new ApplicationConfig(new ManifestConfigSource(context));
ApplicationConfig.setDefaultInstance(config);
int pos = Security.insertProviderAt(new NetworkSecurityConfigProvider(), 1);
if (pos != 1) {
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
index 2b860fa..6013c1e 100644
--- a/core/java/android/security/net/config/NetworkSecurityTrustManager.java
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -133,14 +133,8 @@
return false;
}
X509Certificate anchorCert = chain.get(chain.size() - 1);
- TrustAnchor chainAnchor = null;
- // TODO: faster lookup
- for (TrustAnchor anchor : mNetworkSecurityConfig.getTrustAnchors()) {
- if (anchor.certificate.equals(anchorCert)) {
- chainAnchor = anchor;
- break;
- }
- }
+ TrustAnchor chainAnchor =
+ mNetworkSecurityConfig.findTrustAnchorBySubjectAndPublicKey(anchorCert);
if (chainAnchor == null) {
throw new CertificateException("Trusted chain does not end in a TrustAnchor");
}
diff --git a/core/java/android/security/net/config/ResourceCertificateSource.java b/core/java/android/security/net/config/ResourceCertificateSource.java
index 06dd9d4..b007f8f 100644
--- a/core/java/android/security/net/config/ResourceCertificateSource.java
+++ b/core/java/android/security/net/config/ResourceCertificateSource.java
@@ -27,26 +27,29 @@
import java.util.Collection;
import java.util.Set;
+import com.android.org.conscrypt.TrustedCertificateIndex;
+
/**
* {@link CertificateSource} based on certificates contained in an application resource file.
* @hide
*/
public class ResourceCertificateSource implements CertificateSource {
- private Set<X509Certificate> mCertificates;
- private final int mResourceId;
- private Context mContext;
private final Object mLock = new Object();
+ private final int mResourceId;
+
+ private Set<X509Certificate> mCertificates;
+ private Context mContext;
+ private TrustedCertificateIndex mIndex;
public ResourceCertificateSource(int resourceId, Context context) {
mResourceId = resourceId;
mContext = context.getApplicationContext();
}
- @Override
- public Set<X509Certificate> getCertificates() {
+ private void ensureInitialized() {
synchronized (mLock) {
if (mCertificates != null) {
- return mCertificates;
+ return;
}
Set<X509Certificate> certificates = new ArraySet<X509Certificate>();
Collection<? extends Certificate> certs;
@@ -61,12 +64,30 @@
} finally {
IoUtils.closeQuietly(in);
}
+ TrustedCertificateIndex indexLocal = new TrustedCertificateIndex();
for (Certificate cert : certs) {
- certificates.add((X509Certificate) cert);
+ certificates.add((X509Certificate) cert);
+ indexLocal.index((X509Certificate) cert);
}
mCertificates = certificates;
+ mIndex = indexLocal;
mContext = null;
- return mCertificates;
}
}
+
+ @Override
+ public Set<X509Certificate> getCertificates() {
+ ensureInitialized();
+ return mCertificates;
+ }
+
+ @Override
+ public X509Certificate findBySubjectAndPublicKey(X509Certificate cert) {
+ ensureInitialized();
+ java.security.cert.TrustAnchor anchor = mIndex.findBySubjectAndPublicKey(cert);
+ if (anchor == null) {
+ return null;
+ }
+ return anchor.getTrustedCert();
+ }
}
diff --git a/core/java/android/security/net/config/SystemCertificateSource.java b/core/java/android/security/net/config/SystemCertificateSource.java
index 7649a97..abef7b4 100644
--- a/core/java/android/security/net/config/SystemCertificateSource.java
+++ b/core/java/android/security/net/config/SystemCertificateSource.java
@@ -18,29 +18,20 @@
import android.os.Environment;
import android.os.UserHandle;
-import android.util.ArraySet;
-import java.io.BufferedInputStream;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.IOException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Set;
-import libcore.io.IoUtils;
/**
* {@link CertificateSource} based on the system trusted CA store.
* @hide
*/
-public class SystemCertificateSource implements CertificateSource {
+public final class SystemCertificateSource extends DirectoryCertificateSource {
private static final SystemCertificateSource INSTANCE = new SystemCertificateSource();
- private Set<X509Certificate> mSystemCerts = null;
- private final Object mLock = new Object();
+ private final File mUserRemovedCaDir;
private SystemCertificateSource() {
+ super(new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts"));
+ File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
+ mUserRemovedCaDir = new File(configDir, "cacerts-removed");
}
public static SystemCertificateSource getInstance() {
@@ -48,54 +39,7 @@
}
@Override
- public Set<X509Certificate> getCertificates() {
- // TODO: loading all of these is wasteful, we should instead use a keystore style API.
- synchronized (mLock) {
- if (mSystemCerts != null) {
- return mSystemCerts;
- }
- CertificateFactory certFactory;
- try {
- certFactory = CertificateFactory.getInstance("X.509");
- } catch (CertificateException e) {
- throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
- }
-
- final String ANDROID_ROOT = System.getenv("ANDROID_ROOT");
- final File systemCaDir = new File(ANDROID_ROOT + "/etc/security/cacerts");
- final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
- final File userRemovedCaDir = new File(configDir, "cacerts-removed");
- // Sanity check
- if (!systemCaDir.isDirectory()) {
- throw new AssertionError(systemCaDir + " is not a directory");
- }
-
- Set<X509Certificate> systemCerts = new ArraySet<X509Certificate>();
- for (String caFile : systemCaDir.list()) {
- // Skip any CAs in the user's deleted directory.
- if (new File(userRemovedCaDir, caFile).exists()) {
- continue;
- }
- InputStream is = null;
- try {
- is = new BufferedInputStream(
- new FileInputStream(new File(systemCaDir, caFile)));
- systemCerts.add((X509Certificate) certFactory.generateCertificate(is));
- } catch (CertificateException | IOException e) {
- // Don't rethrow to be consistent with conscrypt's cert loading code.
- continue;
- } finally {
- IoUtils.closeQuietly(is);
- }
- }
- mSystemCerts = systemCerts;
- return mSystemCerts;
- }
- }
-
- public void onCertificateStorageChange() {
- synchronized (mLock) {
- mSystemCerts = null;
- }
+ protected boolean isCertMarkedAsRemoved(String caFile) {
+ return new File(mUserRemovedCaDir, caFile).exists();
}
}
diff --git a/core/java/android/security/net/config/UserCertificateSource.java b/core/java/android/security/net/config/UserCertificateSource.java
index e9d5aa1..1a7d924 100644
--- a/core/java/android/security/net/config/UserCertificateSource.java
+++ b/core/java/android/security/net/config/UserCertificateSource.java
@@ -18,29 +18,18 @@
import android.os.Environment;
import android.os.UserHandle;
-import android.util.ArraySet;
-import java.io.BufferedInputStream;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.IOException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Set;
-import libcore.io.IoUtils;
/**
* {@link CertificateSource} based on the user-installed trusted CA store.
* @hide
*/
-public class UserCertificateSource implements CertificateSource {
+public final class UserCertificateSource extends DirectoryCertificateSource {
private static final UserCertificateSource INSTANCE = new UserCertificateSource();
- private Set<X509Certificate> mUserCerts = null;
- private final Object mLock = new Object();
private UserCertificateSource() {
+ super(new File(
+ Environment.getUserConfigDirectory(UserHandle.myUserId()), "cacerts-added"));
}
public static UserCertificateSource getInstance() {
@@ -48,45 +37,7 @@
}
@Override
- public Set<X509Certificate> getCertificates() {
- // TODO: loading all of these is wasteful, we should instead use a keystore style API.
- synchronized (mLock) {
- if (mUserCerts != null) {
- return mUserCerts;
- }
- CertificateFactory certFactory;
- try {
- certFactory = CertificateFactory.getInstance("X.509");
- } catch (CertificateException e) {
- throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
- }
- final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
- final File userCaDir = new File(configDir, "cacerts-added");
- Set<X509Certificate> userCerts = new ArraySet<X509Certificate>();
- // If the user hasn't added any certificates the directory may not exist.
- if (userCaDir.isDirectory()) {
- for (String caFile : userCaDir.list()) {
- InputStream is = null;
- try {
- is = new BufferedInputStream(
- new FileInputStream(new File(userCaDir, caFile)));
- userCerts.add((X509Certificate) certFactory.generateCertificate(is));
- } catch (CertificateException | IOException e) {
- // Don't rethrow to be consistent with conscrypt's cert loading code.
- continue;
- } finally {
- IoUtils.closeQuietly(is);
- }
- }
- }
- mUserCerts = userCerts;
- return mUserCerts;
- }
- }
-
- public void onCertificateStorageChange() {
- synchronized (mLock) {
- mUserCerts = null;
- }
+ protected boolean isCertMarkedAsRemoved(String caFile) {
+ return false;
}
}
diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java
index 2b882d3..c7a4ccc 100644
--- a/core/java/android/speech/tts/FileSynthesisCallback.java
+++ b/core/java/android/speech/tts/FileSynthesisCallback.java
@@ -15,6 +15,7 @@
*/
package android.speech.tts;
+import android.annotation.NonNull;
import android.media.AudioFormat;
import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
import android.util.Log;
@@ -46,7 +47,6 @@
private FileChannel mFileChannel;
private final UtteranceProgressDispatcher mDispatcher;
- private final Object mCallerIdentity;
private boolean mStarted = false;
private boolean mDone = false;
@@ -54,12 +54,11 @@
/** Status code of synthesis */
protected int mStatusCode;
- FileSynthesisCallback(FileChannel fileChannel, UtteranceProgressDispatcher dispatcher,
- Object callerIdentity, boolean clientIsUsingV2) {
+ FileSynthesisCallback(@NonNull FileChannel fileChannel,
+ @NonNull UtteranceProgressDispatcher dispatcher, boolean clientIsUsingV2) {
super(clientIsUsingV2);
mFileChannel = fileChannel;
mDispatcher = dispatcher;
- mCallerIdentity = callerIdentity;
mStatusCode = TextToSpeech.SUCCESS;
}
@@ -75,9 +74,7 @@
mStatusCode = TextToSpeech.STOPPED;
cleanUp();
- if (mDispatcher != null) {
- mDispatcher.dispatchOnStop();
- }
+ mDispatcher.dispatchOnStop();
}
}
@@ -134,9 +131,7 @@
mAudioFormat = audioFormat;
mChannelCount = channelCount;
- if (mDispatcher != null) {
- mDispatcher.dispatchOnStart();
- }
+ mDispatcher.dispatchOnStart();
fileChannel = mFileChannel;
}
@@ -214,8 +209,7 @@
if (DBG) Log.d(TAG, "Request has been aborted.");
return errorCodeOnStop();
}
- if (mDispatcher != null && mStatusCode != TextToSpeech.SUCCESS &&
- mStatusCode != TextToSpeech.STOPPED) {
+ if (mStatusCode != TextToSpeech.SUCCESS && mStatusCode != TextToSpeech.STOPPED) {
mDispatcher.dispatchOnError(mStatusCode);
return TextToSpeech.ERROR;
}
@@ -239,9 +233,7 @@
synchronized (mStateLock) {
closeFile();
- if (mDispatcher != null) {
- mDispatcher.dispatchOnSuccess();
- }
+ mDispatcher.dispatchOnSuccess();
return TextToSpeech.SUCCESS;
}
} catch (IOException ex) {
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index c3aed75..8c355d8 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -1032,8 +1032,7 @@
@Override
protected AbstractSynthesisCallback createSynthesisCallback() {
- return new FileSynthesisCallback(mFileOutputStream.getChannel(),
- this, getCallerIdentity(), false);
+ return new FileSynthesisCallback(mFileOutputStream.getChannel(), this, false);
}
@Override
diff --git a/core/java/android/util/jar/StrictJarFile.java b/core/java/android/util/jar/StrictJarFile.java
new file mode 100644
index 0000000..fd57806
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarFile.java
@@ -0,0 +1,427 @@
+/*
+ * 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 android.util.jar;
+
+import dalvik.system.CloseGuard;
+import java.io.ByteArrayInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.security.cert.Certificate;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * A subset of the JarFile API implemented as a thin wrapper over
+ * system/core/libziparchive.
+ *
+ * @hide for internal use only. Not API compatible (or as forgiving) as
+ * {@link java.util.jar.JarFile}
+ */
+public final class StrictJarFile {
+
+ private final long nativeHandle;
+
+ // NOTE: It's possible to share a file descriptor with the native
+ // code, at the cost of some additional complexity.
+ private final RandomAccessFile raf;
+
+ private final StrictJarManifest manifest;
+ private final StrictJarVerifier verifier;
+
+ private final boolean isSigned;
+
+ private final CloseGuard guard = CloseGuard.get();
+ private boolean closed;
+
+ public StrictJarFile(String fileName) throws IOException, SecurityException {
+ this.nativeHandle = nativeOpenJarFile(fileName);
+ this.raf = new RandomAccessFile(fileName, "r");
+
+ try {
+ // Read the MANIFEST and signature files up front and try to
+ // parse them. We never want to accept a JAR File with broken signatures
+ // or manifests, so it's best to throw as early as possible.
+ HashMap<String, byte[]> metaEntries = getMetaEntries();
+ this.manifest = new StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
+ this.verifier = new StrictJarVerifier(fileName, manifest, metaEntries);
+ Set<String> files = manifest.getEntries().keySet();
+ for (String file : files) {
+ if (findEntry(file) == null) {
+ throw new SecurityException(fileName + ": File " + file + " in manifest does not exist");
+ }
+ }
+
+ isSigned = verifier.readCertificates() && verifier.isSignedJar();
+ } catch (IOException | SecurityException e) {
+ nativeClose(this.nativeHandle);
+ IoUtils.closeQuietly(this.raf);
+ throw e;
+ }
+
+ guard.open("close");
+ }
+
+ public StrictJarManifest getManifest() {
+ return manifest;
+ }
+
+ public Iterator<ZipEntry> iterator() throws IOException {
+ return new EntryIterator(nativeHandle, "");
+ }
+
+ public ZipEntry findEntry(String name) {
+ return nativeFindEntry(nativeHandle, name);
+ }
+
+ /**
+ * Return all certificate chains for a given {@link ZipEntry} belonging to this jar.
+ * This method MUST be called only after fully exhausting the InputStream belonging
+ * to this entry.
+ *
+ * Returns {@code null} if this jar file isn't signed or if this method is
+ * called before the stream is processed.
+ */
+ public Certificate[][] getCertificateChains(ZipEntry ze) {
+ if (isSigned) {
+ return verifier.getCertificateChains(ze.getName());
+ }
+
+ return null;
+ }
+
+ /**
+ * Return all certificates for a given {@link ZipEntry} belonging to this jar.
+ * This method MUST be called only after fully exhausting the InputStream belonging
+ * to this entry.
+ *
+ * Returns {@code null} if this jar file isn't signed or if this method is
+ * called before the stream is processed.
+ *
+ * @deprecated Switch callers to use getCertificateChains instead
+ */
+ @Deprecated
+ public Certificate[] getCertificates(ZipEntry ze) {
+ if (isSigned) {
+ Certificate[][] certChains = verifier.getCertificateChains(ze.getName());
+
+ // Measure number of certs.
+ int count = 0;
+ for (Certificate[] chain : certChains) {
+ count += chain.length;
+ }
+
+ // Create new array and copy all the certs into it.
+ Certificate[] certs = new Certificate[count];
+ int i = 0;
+ for (Certificate[] chain : certChains) {
+ System.arraycopy(chain, 0, certs, i, chain.length);
+ i += chain.length;
+ }
+
+ return certs;
+ }
+
+ return null;
+ }
+
+ public InputStream getInputStream(ZipEntry ze) {
+ final InputStream is = getZipInputStream(ze);
+
+ if (isSigned) {
+ StrictJarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
+ if (entry == null) {
+ return is;
+ }
+
+ return new JarFileInputStream(is, ze.getSize(), entry);
+ }
+
+ return is;
+ }
+
+ public void close() throws IOException {
+ if (!closed) {
+ guard.close();
+
+ nativeClose(nativeHandle);
+ IoUtils.closeQuietly(raf);
+ closed = true;
+ }
+ }
+
+ private InputStream getZipInputStream(ZipEntry ze) {
+ if (ze.getMethod() == ZipEntry.STORED) {
+ return new RAFStream(raf, ze.getDataOffset(),
+ ze.getDataOffset() + ze.getSize());
+ } else {
+ final RAFStream wrapped = new RAFStream(
+ raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
+
+ int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
+ return new ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze);
+ }
+ }
+
+ static final class EntryIterator implements Iterator<ZipEntry> {
+ private final long iterationHandle;
+ private ZipEntry nextEntry;
+
+ EntryIterator(long nativeHandle, String prefix) throws IOException {
+ iterationHandle = nativeStartIteration(nativeHandle, prefix);
+ }
+
+ public ZipEntry next() {
+ if (nextEntry != null) {
+ final ZipEntry ze = nextEntry;
+ nextEntry = null;
+ return ze;
+ }
+
+ return nativeNextEntry(iterationHandle);
+ }
+
+ public boolean hasNext() {
+ if (nextEntry != null) {
+ return true;
+ }
+
+ final ZipEntry ze = nativeNextEntry(iterationHandle);
+ if (ze == null) {
+ return false;
+ }
+
+ nextEntry = ze;
+ return true;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private HashMap<String, byte[]> getMetaEntries() throws IOException {
+ HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
+
+ Iterator<ZipEntry> entryIterator = new EntryIterator(nativeHandle, "META-INF/");
+ while (entryIterator.hasNext()) {
+ final ZipEntry entry = entryIterator.next();
+ metaEntries.put(entry.getName(), Streams.readFully(getInputStream(entry)));
+ }
+
+ return metaEntries;
+ }
+
+ static final class JarFileInputStream extends FilterInputStream {
+ private final StrictJarVerifier.VerifierEntry entry;
+
+ private long count;
+ private boolean done = false;
+
+ JarFileInputStream(InputStream is, long size, StrictJarVerifier.VerifierEntry e) {
+ super(is);
+ entry = e;
+
+ count = size;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (done) {
+ return -1;
+ }
+ if (count > 0) {
+ int r = super.read();
+ if (r != -1) {
+ entry.write(r);
+ count--;
+ } else {
+ count = 0;
+ }
+ if (count == 0) {
+ done = true;
+ entry.verify();
+ }
+ return r;
+ } else {
+ done = true;
+ entry.verify();
+ return -1;
+ }
+ }
+
+ @Override
+ public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ if (done) {
+ return -1;
+ }
+ if (count > 0) {
+ int r = super.read(buffer, byteOffset, byteCount);
+ if (r != -1) {
+ int size = r;
+ if (count < size) {
+ size = (int) count;
+ }
+ entry.write(buffer, byteOffset, size);
+ count -= size;
+ } else {
+ count = 0;
+ }
+ if (count == 0) {
+ done = true;
+ entry.verify();
+ }
+ return r;
+ } else {
+ done = true;
+ entry.verify();
+ return -1;
+ }
+ }
+
+ @Override
+ public int available() throws IOException {
+ if (done) {
+ return 0;
+ }
+ return super.available();
+ }
+
+ @Override
+ public long skip(long byteCount) throws IOException {
+ return Streams.skipByReading(this, byteCount);
+ }
+ }
+
+ /** @hide */
+ public static class ZipInflaterInputStream extends InflaterInputStream {
+ private final ZipEntry entry;
+ private long bytesRead = 0;
+
+ public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) {
+ super(is, inf, bsize);
+ this.entry = entry;
+ }
+
+ @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ final int i;
+ try {
+ i = super.read(buffer, byteOffset, byteCount);
+ } catch (IOException e) {
+ throw new IOException("Error reading data for " + entry.getName() + " near offset "
+ + bytesRead, e);
+ }
+ if (i == -1) {
+ if (entry.getSize() != bytesRead) {
+ throw new IOException("Size mismatch on inflated file: " + bytesRead + " vs "
+ + entry.getSize());
+ }
+ } else {
+ bytesRead += i;
+ }
+ return i;
+ }
+
+ @Override public int available() throws IOException {
+ if (closed) {
+ // Our superclass will throw an exception, but there's a jtreg test that
+ // explicitly checks that the InputStream returned from ZipFile.getInputStream
+ // returns 0 even when closed.
+ return 0;
+ }
+ return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead);
+ }
+ }
+
+ /**
+ * Wrap a stream around a RandomAccessFile. The RandomAccessFile is shared
+ * among all streams returned by getInputStream(), so we have to synchronize
+ * access to it. (We can optimize this by adding buffering here to reduce
+ * collisions.)
+ *
+ * <p>We could support mark/reset, but we don't currently need them.
+ *
+ * @hide
+ */
+ public static class RAFStream extends InputStream {
+ private final RandomAccessFile sharedRaf;
+ private long endOffset;
+ private long offset;
+
+
+ public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) {
+ sharedRaf = raf;
+ offset = initialOffset;
+ this.endOffset = endOffset;
+ }
+
+ public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
+ this(raf, initialOffset, raf.length());
+ }
+
+ @Override public int available() throws IOException {
+ return (offset < endOffset ? 1 : 0);
+ }
+
+ @Override public int read() throws IOException {
+ return Streams.readSingleByte(this);
+ }
+
+ @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ synchronized (sharedRaf) {
+ final long length = endOffset - offset;
+ if (byteCount > length) {
+ byteCount = (int) length;
+ }
+ sharedRaf.seek(offset);
+ int count = sharedRaf.read(buffer, byteOffset, byteCount);
+ if (count > 0) {
+ offset += count;
+ return count;
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ @Override public long skip(long byteCount) throws IOException {
+ if (byteCount > endOffset - offset) {
+ byteCount = endOffset - offset;
+ }
+ offset += byteCount;
+ return byteCount;
+ }
+ }
+
+
+ private static native long nativeOpenJarFile(String fileName) throws IOException;
+ private static native long nativeStartIteration(long nativeHandle, String prefix);
+ private static native ZipEntry nativeNextEntry(long iterationHandle);
+ private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName);
+ private static native void nativeClose(long nativeHandle);
+}
diff --git a/core/java/android/util/jar/StrictJarManifest.java b/core/java/android/util/jar/StrictJarManifest.java
new file mode 100644
index 0000000..dbb466c
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarManifest.java
@@ -0,0 +1,315 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.util.jar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.jar.Attributes;
+import libcore.io.Streams;
+
+/**
+ * The {@code StrictJarManifest} class is used to obtain attribute information for a
+ * {@code StrictJarFile} and its entries.
+ *
+ * @hide
+ */
+public class StrictJarManifest implements Cloneable {
+ static final int LINE_LENGTH_LIMIT = 72;
+
+ private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
+
+ private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
+
+ private final Attributes mainAttributes;
+ private final HashMap<String, Attributes> entries;
+
+ static final class Chunk {
+ final int start;
+ final int end;
+
+ Chunk(int start, int end) {
+ this.start = start;
+ this.end = end;
+ }
+ }
+
+ private HashMap<String, Chunk> chunks;
+
+ /**
+ * The end of the main attributes section in the manifest is needed in
+ * verification.
+ */
+ private int mainEnd;
+
+ /**
+ * Creates a new {@code StrictJarManifest} instance.
+ */
+ public StrictJarManifest() {
+ entries = new HashMap<String, Attributes>();
+ mainAttributes = new Attributes();
+ }
+
+ /**
+ * Creates a new {@code StrictJarManifest} instance using the attributes obtained
+ * from the input stream.
+ *
+ * @param is
+ * {@code InputStream} to parse for attributes.
+ * @throws IOException
+ * if an IO error occurs while creating this {@code StrictJarManifest}
+ */
+ public StrictJarManifest(InputStream is) throws IOException {
+ this();
+ read(Streams.readFully(is));
+ }
+
+ /**
+ * Creates a new {@code StrictJarManifest} instance. The new instance will have the
+ * same attributes as those found in the parameter {@code StrictJarManifest}.
+ *
+ * @param man
+ * {@code StrictJarManifest} instance to obtain attributes from.
+ */
+ @SuppressWarnings("unchecked")
+ public StrictJarManifest(StrictJarManifest man) {
+ mainAttributes = (Attributes) man.mainAttributes.clone();
+ entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man
+ .getEntries()).clone();
+ }
+
+ StrictJarManifest(byte[] manifestBytes, boolean readChunks) throws IOException {
+ this();
+ if (readChunks) {
+ chunks = new HashMap<String, Chunk>();
+ }
+ read(manifestBytes);
+ }
+
+ /**
+ * Resets the both the main attributes as well as the entry attributes
+ * associated with this {@code StrictJarManifest}.
+ */
+ public void clear() {
+ entries.clear();
+ mainAttributes.clear();
+ }
+
+ /**
+ * Returns the {@code Attributes} associated with the parameter entry
+ * {@code name}.
+ *
+ * @param name
+ * the name of the entry to obtain {@code Attributes} from.
+ * @return the Attributes for the entry or {@code null} if the entry does
+ * not exist.
+ */
+ public Attributes getAttributes(String name) {
+ return getEntries().get(name);
+ }
+
+ /**
+ * Returns a map containing the {@code Attributes} for each entry in the
+ * {@code StrictJarManifest}.
+ *
+ * @return the map of entry attributes.
+ */
+ public Map<String, Attributes> getEntries() {
+ return entries;
+ }
+
+ /**
+ * Returns the main {@code Attributes} of the {@code JarFile}.
+ *
+ * @return main {@code Attributes} associated with the source {@code
+ * JarFile}.
+ */
+ public Attributes getMainAttributes() {
+ return mainAttributes;
+ }
+
+ /**
+ * Creates a copy of this {@code StrictJarManifest}. The returned {@code StrictJarManifest}
+ * will equal the {@code StrictJarManifest} from which it was cloned.
+ *
+ * @return a copy of this instance.
+ */
+ @Override
+ public Object clone() {
+ return new StrictJarManifest(this);
+ }
+
+ /**
+ * Writes this {@code StrictJarManifest}'s name/attributes pairs to the given {@code OutputStream}.
+ * The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before
+ * calling this method, or no attributes will be written.
+ *
+ * @throws IOException
+ * If an error occurs writing the {@code StrictJarManifest}.
+ */
+ public void write(OutputStream os) throws IOException {
+ write(this, os);
+ }
+
+ /**
+ * Merges name/attribute pairs read from the input stream {@code is} into this manifest.
+ *
+ * @param is
+ * The {@code InputStream} to read from.
+ * @throws IOException
+ * If an error occurs reading the manifest.
+ */
+ public void read(InputStream is) throws IOException {
+ read(Streams.readFullyNoClose(is));
+ }
+
+ private void read(byte[] buf) throws IOException {
+ if (buf.length == 0) {
+ return;
+ }
+
+ StrictJarManifestReader im = new StrictJarManifestReader(buf, mainAttributes);
+ mainEnd = im.getEndOfMainSection();
+ im.readEntries(entries, chunks);
+ }
+
+ /**
+ * Returns the hash code for this instance.
+ *
+ * @return this {@code StrictJarManifest}'s hashCode.
+ */
+ @Override
+ public int hashCode() {
+ return mainAttributes.hashCode() ^ getEntries().hashCode();
+ }
+
+ /**
+ * Determines if the receiver is equal to the parameter object. Two {@code
+ * StrictJarManifest}s are equal if they have identical main attributes as well as
+ * identical entry attributes.
+ *
+ * @param o
+ * the object to compare against.
+ * @return {@code true} if the manifests are equal, {@code false} otherwise
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+ if (o.getClass() != this.getClass()) {
+ return false;
+ }
+ if (!mainAttributes.equals(((StrictJarManifest) o).mainAttributes)) {
+ return false;
+ }
+ return getEntries().equals(((StrictJarManifest) o).getEntries());
+ }
+
+ Chunk getChunk(String name) {
+ return chunks.get(name);
+ }
+
+ void removeChunks() {
+ chunks = null;
+ }
+
+ int getMainAttributesEnd() {
+ return mainEnd;
+ }
+
+ /**
+ * Writes out the attribute information of the specified manifest to the
+ * specified {@code OutputStream}
+ *
+ * @param manifest
+ * the manifest to write out.
+ * @param out
+ * The {@code OutputStream} to write to.
+ * @throws IOException
+ * If an error occurs writing the {@code StrictJarManifest}.
+ */
+ static void write(StrictJarManifest manifest, OutputStream out) throws IOException {
+ CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+ ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT);
+
+ Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION;
+ String version = manifest.mainAttributes.getValue(versionName);
+ if (version == null) {
+ versionName = Attributes.Name.SIGNATURE_VERSION;
+ version = manifest.mainAttributes.getValue(versionName);
+ }
+ if (version != null) {
+ writeEntry(out, versionName, version, encoder, buffer);
+ Iterator<?> entries = manifest.mainAttributes.keySet().iterator();
+ while (entries.hasNext()) {
+ Attributes.Name name = (Attributes.Name) entries.next();
+ if (!name.equals(versionName)) {
+ writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
+ }
+ }
+ }
+ out.write(LINE_SEPARATOR);
+ Iterator<String> i = manifest.getEntries().keySet().iterator();
+ while (i.hasNext()) {
+ String key = i.next();
+ writeEntry(out, Attributes.Name.NAME, key, encoder, buffer);
+ Attributes attributes = manifest.entries.get(key);
+ Iterator<?> entries = attributes.keySet().iterator();
+ while (entries.hasNext()) {
+ Attributes.Name name = (Attributes.Name) entries.next();
+ writeEntry(out, name, attributes.getValue(name), encoder, buffer);
+ }
+ out.write(LINE_SEPARATOR);
+ }
+ }
+
+ private static void writeEntry(OutputStream os, Attributes.Name name,
+ String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException {
+ String nameString = name.toString();
+ os.write(nameString.getBytes(StandardCharsets.US_ASCII));
+ os.write(VALUE_SEPARATOR);
+
+ encoder.reset();
+ bBuf.clear().limit(LINE_LENGTH_LIMIT - nameString.length() - 2);
+
+ CharBuffer cBuf = CharBuffer.wrap(value);
+
+ while (true) {
+ CoderResult r = encoder.encode(cBuf, bBuf, true);
+ if (CoderResult.UNDERFLOW == r) {
+ r = encoder.flush(bBuf);
+ }
+ os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position());
+ os.write(LINE_SEPARATOR);
+ if (CoderResult.UNDERFLOW == r) {
+ break;
+ }
+ os.write(' ');
+ bBuf.clear().limit(LINE_LENGTH_LIMIT - 1);
+ }
+ }
+}
diff --git a/core/java/android/util/jar/StrictJarManifestReader.java b/core/java/android/util/jar/StrictJarManifestReader.java
new file mode 100644
index 0000000..9881bb0
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarManifestReader.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.util.jar;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.Attributes;
+
+/**
+ * Reads a JAR file manifest. The specification is here:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jar/jar.html
+ */
+class StrictJarManifestReader {
+ // There are relatively few unique attribute names,
+ // but a manifest might have thousands of entries.
+ private final HashMap<String, Attributes.Name> attributeNameCache = new HashMap<String, Attributes.Name>();
+
+ private final ByteArrayOutputStream valueBuffer = new ByteArrayOutputStream(80);
+
+ private final byte[] buf;
+
+ private final int endOfMainSection;
+
+ private int pos;
+
+ private Attributes.Name name;
+
+ private String value;
+
+ private int consecutiveLineBreaks = 0;
+
+ public StrictJarManifestReader(byte[] buf, Attributes main) throws IOException {
+ this.buf = buf;
+ while (readHeader()) {
+ main.put(name, value);
+ }
+ this.endOfMainSection = pos;
+ }
+
+ public void readEntries(Map<String, Attributes> entries, Map<String, StrictJarManifest.Chunk> chunks) throws IOException {
+ int mark = pos;
+ while (readHeader()) {
+ if (!Attributes.Name.NAME.equals(name)) {
+ throw new IOException("Entry is not named");
+ }
+ String entryNameValue = value;
+
+ Attributes entry = entries.get(entryNameValue);
+ if (entry == null) {
+ entry = new Attributes(12);
+ }
+
+ while (readHeader()) {
+ entry.put(name, value);
+ }
+
+ if (chunks != null) {
+ if (chunks.get(entryNameValue) != null) {
+ // TODO A bug: there might be several verification chunks for
+ // the same name. I believe they should be used to update
+ // signature in order of appearance; there are two ways to fix
+ // this: either use a list of chunks, or decide on used
+ // signature algorithm in advance and reread the chunks while
+ // updating the signature; for now a defensive error is thrown
+ throw new IOException("A jar verifier does not support more than one entry with the same name");
+ }
+ chunks.put(entryNameValue, new StrictJarManifest.Chunk(mark, pos));
+ mark = pos;
+ }
+
+ entries.put(entryNameValue, entry);
+ }
+ }
+
+ public int getEndOfMainSection() {
+ return endOfMainSection;
+ }
+
+ /**
+ * Read a single line from the manifest buffer.
+ */
+ private boolean readHeader() throws IOException {
+ if (consecutiveLineBreaks > 1) {
+ // break a section on an empty line
+ consecutiveLineBreaks = 0;
+ return false;
+ }
+ readName();
+ consecutiveLineBreaks = 0;
+ readValue();
+ // if the last line break is missed, the line
+ // is ignored by the reference implementation
+ return consecutiveLineBreaks > 0;
+ }
+
+ private void readName() throws IOException {
+ int mark = pos;
+
+ while (pos < buf.length) {
+ if (buf[pos++] != ':') {
+ continue;
+ }
+
+ String nameString = new String(buf, mark, pos - mark - 1, StandardCharsets.US_ASCII);
+
+ if (buf[pos++] != ' ') {
+ throw new IOException(String.format("Invalid value for attribute '%s'", nameString));
+ }
+
+ try {
+ name = attributeNameCache.get(nameString);
+ if (name == null) {
+ name = new Attributes.Name(nameString);
+ attributeNameCache.put(nameString, name);
+ }
+ } catch (IllegalArgumentException e) {
+ // new Attributes.Name() throws IllegalArgumentException but we declare IOException
+ throw new IOException(e.getMessage());
+ }
+ return;
+ }
+ }
+
+ private void readValue() throws IOException {
+ boolean lastCr = false;
+ int mark = pos;
+ int last = pos;
+ valueBuffer.reset();
+ while (pos < buf.length) {
+ byte next = buf[pos++];
+ switch (next) {
+ case 0:
+ throw new IOException("NUL character in a manifest");
+ case '\n':
+ if (lastCr) {
+ lastCr = false;
+ } else {
+ consecutiveLineBreaks++;
+ }
+ continue;
+ case '\r':
+ lastCr = true;
+ consecutiveLineBreaks++;
+ continue;
+ case ' ':
+ if (consecutiveLineBreaks == 1) {
+ valueBuffer.write(buf, mark, last - mark);
+ mark = pos;
+ consecutiveLineBreaks = 0;
+ continue;
+ }
+ }
+
+ if (consecutiveLineBreaks >= 1) {
+ pos--;
+ break;
+ }
+ last = pos;
+ }
+
+ valueBuffer.write(buf, mark, last - mark);
+ // A bit frustrating that that Charset.forName will be called
+ // again.
+ value = valueBuffer.toString(StandardCharsets.UTF_8.name());
+ }
+}
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
new file mode 100644
index 0000000..ca2aec1
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -0,0 +1,456 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.util.jar;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import libcore.io.Base64;
+import sun.security.jca.Providers;
+import sun.security.pkcs.PKCS7;
+
+/**
+ * Non-public class used by {@link JarFile} and {@link JarInputStream} to manage
+ * the verification of signed JARs. {@code JarFile} and {@code JarInputStream}
+ * objects are expected to have a {@code JarVerifier} instance member which
+ * can be used to carry out the tasks associated with verifying a signed JAR.
+ * These tasks would typically include:
+ * <ul>
+ * <li>verification of all signed signature files
+ * <li>confirmation that all signed data was signed only by the party or parties
+ * specified in the signature block data
+ * <li>verification that the contents of all signature files (i.e. {@code .SF}
+ * files) agree with the JAR entries information found in the JAR manifest.
+ * </ul>
+ */
+class StrictJarVerifier {
+ /**
+ * List of accepted digest algorithms. This list is in order from most
+ * preferred to least preferred.
+ */
+ private static final String[] DIGEST_ALGORITHMS = new String[] {
+ "SHA-512",
+ "SHA-384",
+ "SHA-256",
+ "SHA1",
+ };
+
+ private final String jarName;
+ private final StrictJarManifest manifest;
+ private final HashMap<String, byte[]> metaEntries;
+ private final int mainAttributesEnd;
+
+ private final Hashtable<String, HashMap<String, Attributes>> signatures =
+ new Hashtable<String, HashMap<String, Attributes>>(5);
+
+ private final Hashtable<String, Certificate[]> certificates =
+ new Hashtable<String, Certificate[]>(5);
+
+ private final Hashtable<String, Certificate[][]> verifiedEntries =
+ new Hashtable<String, Certificate[][]>();
+
+ /**
+ * Stores and a hash and a message digest and verifies that massage digest
+ * matches the hash.
+ */
+ static class VerifierEntry extends OutputStream {
+
+ private final String name;
+
+ private final MessageDigest digest;
+
+ private final byte[] hash;
+
+ private final Certificate[][] certChains;
+
+ private final Hashtable<String, Certificate[][]> verifiedEntries;
+
+ VerifierEntry(String name, MessageDigest digest, byte[] hash,
+ Certificate[][] certChains, Hashtable<String, Certificate[][]> verifedEntries) {
+ this.name = name;
+ this.digest = digest;
+ this.hash = hash;
+ this.certChains = certChains;
+ this.verifiedEntries = verifedEntries;
+ }
+
+ /**
+ * Updates a digest with one byte.
+ */
+ @Override
+ public void write(int value) {
+ digest.update((byte) value);
+ }
+
+ /**
+ * Updates a digest with byte array.
+ */
+ @Override
+ public void write(byte[] buf, int off, int nbytes) {
+ digest.update(buf, off, nbytes);
+ }
+
+ /**
+ * Verifies that the digests stored in the manifest match the decrypted
+ * digests from the .SF file. This indicates the validity of the
+ * signing, not the integrity of the file, as its digest must be
+ * calculated and verified when its contents are read.
+ *
+ * @throws SecurityException
+ * if the digest value stored in the manifest does <i>not</i>
+ * agree with the decrypted digest as recovered from the
+ * <code>.SF</code> file.
+ */
+ void verify() {
+ byte[] d = digest.digest();
+ if (!MessageDigest.isEqual(d, Base64.decode(hash))) {
+ throw invalidDigest(JarFile.MANIFEST_NAME, name, name);
+ }
+ verifiedEntries.put(name, certChains);
+ }
+ }
+
+ private static SecurityException invalidDigest(String signatureFile, String name,
+ String jarName) {
+ throw new SecurityException(signatureFile + " has invalid digest for " + name +
+ " in " + jarName);
+ }
+
+ private static SecurityException failedVerification(String jarName, String signatureFile) {
+ throw new SecurityException(jarName + " failed verification of " + signatureFile);
+ }
+
+ private static SecurityException failedVerification(String jarName, String signatureFile,
+ Throwable e) {
+ throw new SecurityException(jarName + " failed verification of " + signatureFile, e);
+ }
+
+
+ /**
+ * Constructs and returns a new instance of {@code JarVerifier}.
+ *
+ * @param name
+ * the name of the JAR file being verified.
+ */
+ StrictJarVerifier(String name, StrictJarManifest manifest,
+ HashMap<String, byte[]> metaEntries) {
+ jarName = name;
+ this.manifest = manifest;
+ this.metaEntries = metaEntries;
+ this.mainAttributesEnd = manifest.getMainAttributesEnd();
+ }
+
+ /**
+ * Invoked for each new JAR entry read operation from the input
+ * stream. This method constructs and returns a new {@link VerifierEntry}
+ * which contains the certificates used to sign the entry and its hash value
+ * as specified in the JAR MANIFEST format.
+ *
+ * @param name
+ * the name of an entry in a JAR file which is <b>not</b> in the
+ * {@code META-INF} directory.
+ * @return a new instance of {@link VerifierEntry} which can be used by
+ * callers as an {@link OutputStream}.
+ */
+ VerifierEntry initEntry(String name) {
+ // If no manifest is present by the time an entry is found,
+ // verification cannot occur. If no signature files have
+ // been found, do not verify.
+ if (manifest == null || signatures.isEmpty()) {
+ return null;
+ }
+
+ Attributes attributes = manifest.getAttributes(name);
+ // entry has no digest
+ if (attributes == null) {
+ return null;
+ }
+
+ ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>();
+ Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, HashMap<String, Attributes>> entry = it.next();
+ HashMap<String, Attributes> hm = entry.getValue();
+ if (hm.get(name) != null) {
+ // Found an entry for entry name in .SF file
+ String signatureFile = entry.getKey();
+ Certificate[] certChain = certificates.get(signatureFile);
+ if (certChain != null) {
+ certChains.add(certChain);
+ }
+ }
+ }
+
+ // entry is not signed
+ if (certChains.isEmpty()) {
+ return null;
+ }
+ Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]);
+
+ for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+ final String algorithm = DIGEST_ALGORITHMS[i];
+ final String hash = attributes.getValue(algorithm + "-Digest");
+ if (hash == null) {
+ continue;
+ }
+ byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
+
+ try {
+ return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes,
+ certChainsArray, verifiedEntries);
+ } catch (NoSuchAlgorithmException ignored) {
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Add a new meta entry to the internal collection of data held on each JAR
+ * entry in the {@code META-INF} directory including the manifest
+ * file itself. Files associated with the signing of a JAR would also be
+ * added to this collection.
+ *
+ * @param name
+ * the name of the file located in the {@code META-INF}
+ * directory.
+ * @param buf
+ * the file bytes for the file called {@code name}.
+ * @see #removeMetaEntries()
+ */
+ void addMetaEntry(String name, byte[] buf) {
+ metaEntries.put(name.toUpperCase(Locale.US), buf);
+ }
+
+ /**
+ * If the associated JAR file is signed, check on the validity of all of the
+ * known signatures.
+ *
+ * @return {@code true} if the associated JAR is signed and an internal
+ * check verifies the validity of the signature(s). {@code false} if
+ * the associated JAR file has no entries at all in its {@code
+ * META-INF} directory. This situation is indicative of an invalid
+ * JAR file.
+ * <p>
+ * Will also return {@code true} if the JAR file is <i>not</i>
+ * signed.
+ * @throws SecurityException
+ * if the JAR file is signed and it is determined that a
+ * signature block file contains an invalid signature for the
+ * corresponding signature file.
+ */
+ synchronized boolean readCertificates() {
+ if (metaEntries.isEmpty()) {
+ return false;
+ }
+
+ Iterator<String> it = metaEntries.keySet().iterator();
+ while (it.hasNext()) {
+ String key = it.next();
+ if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
+ verifyCertificate(key);
+ it.remove();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Verifies that the signature computed from {@code sfBytes} matches
+ * that specified in {@code blockBytes} (which is a PKCS7 block). Returns
+ * certificates listed in the PKCS7 block. Throws a {@code GeneralSecurityException}
+ * if something goes wrong during verification.
+ */
+ static Certificate[] verifyBytes(byte[] blockBytes, byte[] sfBytes)
+ throws GeneralSecurityException {
+
+ Object obj = null;
+ try {
+
+ obj = Providers.startJarVerification();
+ PKCS7 block = new PKCS7(blockBytes);
+ if (block.verify(sfBytes) == null) {
+ throw new GeneralSecurityException("Failed to verify signature");
+ }
+ X509Certificate[] blockCerts = block.getCertificates();
+ Certificate[] signerCertChain = null;
+ if (blockCerts != null) {
+ signerCertChain = new Certificate[blockCerts.length];
+ for (int i = 0; i < blockCerts.length; ++i) {
+ signerCertChain[i] = blockCerts[i];
+ }
+ }
+ return signerCertChain;
+ } catch (IOException e) {
+ throw new GeneralSecurityException("IO exception verifying jar cert", e);
+ } finally {
+ Providers.stopJarVerification(obj);
+ }
+ }
+
+ /**
+ * @param certFile
+ */
+ private void verifyCertificate(String certFile) {
+ // Found Digital Sig, .SF should already have been read
+ String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";
+ byte[] sfBytes = metaEntries.get(signatureFile);
+ if (sfBytes == null) {
+ return;
+ }
+
+ byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME);
+ // Manifest entry is required for any verifications.
+ if (manifestBytes == null) {
+ return;
+ }
+
+ byte[] sBlockBytes = metaEntries.get(certFile);
+ try {
+ Certificate[] signerCertChain = verifyBytes(sBlockBytes, sfBytes);
+ if (signerCertChain != null) {
+ certificates.put(signatureFile, signerCertChain);
+ }
+ } catch (GeneralSecurityException e) {
+ throw failedVerification(jarName, signatureFile, e);
+ }
+
+ // Verify manifest hash in .sf file
+ Attributes attributes = new Attributes();
+ HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
+ try {
+ StrictJarManifestReader im = new StrictJarManifestReader(sfBytes, attributes);
+ im.readEntries(entries, null);
+ } catch (IOException e) {
+ return;
+ }
+
+ // Do we actually have any signatures to look at?
+ if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {
+ return;
+ }
+
+ boolean createdBySigntool = false;
+ String createdBy = attributes.getValue("Created-By");
+ if (createdBy != null) {
+ createdBySigntool = createdBy.indexOf("signtool") != -1;
+ }
+
+ // Use .SF to verify the mainAttributes of the manifest
+ // If there is no -Digest-Manifest-Main-Attributes entry in .SF
+ // file, such as those created before java 1.5, then we ignore
+ // such verification.
+ if (mainAttributesEnd > 0 && !createdBySigntool) {
+ String digestAttribute = "-Digest-Manifest-Main-Attributes";
+ if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) {
+ throw failedVerification(jarName, signatureFile);
+ }
+ }
+
+ // Use .SF to verify the whole manifest.
+ String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
+ if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) {
+ Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, Attributes> entry = it.next();
+ StrictJarManifest.Chunk chunk = manifest.getChunk(entry.getKey());
+ if (chunk == null) {
+ return;
+ }
+ if (!verify(entry.getValue(), "-Digest", manifestBytes,
+ chunk.start, chunk.end, createdBySigntool, false)) {
+ throw invalidDigest(signatureFile, entry.getKey(), jarName);
+ }
+ }
+ }
+ metaEntries.put(signatureFile, null);
+ signatures.put(signatureFile, entries);
+ }
+
+ /**
+ * Returns a <code>boolean</code> indication of whether or not the
+ * associated jar file is signed.
+ *
+ * @return {@code true} if the JAR is signed, {@code false}
+ * otherwise.
+ */
+ boolean isSignedJar() {
+ return certificates.size() > 0;
+ }
+
+ private boolean verify(Attributes attributes, String entry, byte[] data,
+ int start, int end, boolean ignoreSecondEndline, boolean ignorable) {
+ for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+ String algorithm = DIGEST_ALGORITHMS[i];
+ String hash = attributes.getValue(algorithm + entry);
+ if (hash == null) {
+ continue;
+ }
+
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ continue;
+ }
+ if (ignoreSecondEndline && data[end - 1] == '\n' && data[end - 2] == '\n') {
+ md.update(data, start, end - 1 - start);
+ } else {
+ md.update(data, start, end - start);
+ }
+ byte[] b = md.digest();
+ byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
+ return MessageDigest.isEqual(b, Base64.decode(hashBytes));
+ }
+ return ignorable;
+ }
+
+ /**
+ * Returns all of the {@link java.security.cert.Certificate} chains that
+ * were used to verify the signature on the JAR entry called
+ * {@code name}. Callers must not modify the returned arrays.
+ *
+ * @param name
+ * the name of a JAR entry.
+ * @return an array of {@link java.security.cert.Certificate} chains.
+ */
+ Certificate[][] getCertificateChains(String name) {
+ return verifiedEntries.get(name);
+ }
+
+ /**
+ * Remove all entries from the internal collection of data held about each
+ * JAR entry in the {@code META-INF} directory.
+ */
+ void removeMetaEntries() {
+ metaEntries.clear();
+ }
+}
diff --git a/core/java/android/view/IDockDividerVisibilityListener.aidl b/core/java/android/view/IDockDividerVisibilityListener.aidl
new file mode 100644
index 0000000..a7d5cda
--- /dev/null
+++ b/core/java/android/view/IDockDividerVisibilityListener.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * Listener for showing/hiding of the dock divider. Will fire when an app is shown in side by side
+ * mode and a divider should be shown.
+ *
+ * @hide
+ */
+oneway interface IDockDividerVisibilityListener {
+ void onDockDividerVisibilityChanged(boolean visible);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 64a046e..bd65532 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -29,6 +29,7 @@
import android.os.IRemoteCallback;
import android.view.IApplicationToken;
import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.IDockDividerVisibilityListener;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
import android.view.IWindowSession;
@@ -290,10 +291,10 @@
/**
* Create a screenshot of the applications currently displayed.
*
- * @param frameScale the scale to apply to the frame, only used when width = -1 and
+ * @param frameScale the scale to apply to the frame, only used when width = -1 and
* height = -1
*/
- Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight,
+ Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight,
float frameScale);
/**
@@ -348,4 +349,9 @@
* stack size.
*/
void setDockedStackResizing(boolean resizing);
+
+ /**
+ * Registers a listener that will be called when the dock divider changes its visibility.
+ */
+ void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener);
}
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
new file mode 100644
index 0000000..82f6c7f
--- /dev/null
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * A header of a notification view
+ *
+ * @hide
+ */
+@RemoteViews.RemoteView
+public class NotificationHeaderView extends LinearLayout {
+ private final int mHeaderMinWidth;
+ private View mAppName;
+ private View mSubTextView;
+ private OnClickListener mExpandClickListener;
+ private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+ private View mExpandButton;
+ private View mIcon;
+ private TextView mChildCount;
+
+ public NotificationHeaderView(Context context) {
+ this(context, null);
+ }
+
+ public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mHeaderMinWidth = getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_header_shrink_min_width);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAppName = findViewById(com.android.internal.R.id.app_name_text);
+ mSubTextView = findViewById(com.android.internal.R.id.header_sub_text);
+ mExpandButton = findViewById(com.android.internal.R.id.expand_button);
+ mIcon = findViewById(com.android.internal.R.id.icon);
+ mChildCount = (TextView) findViewById(com.android.internal.R.id.number_of_children);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
+ int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth,
+ MeasureSpec.AT_MOST);
+ int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight,
+ MeasureSpec.AT_MOST);
+ int totalWidth = getPaddingStart() + getPaddingEnd();
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ // We'll give it the rest of the space in the end
+ continue;
+ }
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec,
+ lp.leftMargin + lp.rightMargin, lp.width);
+ int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
+ lp.topMargin + lp.bottomMargin, lp.height);
+ child.measure(childWidthSpec, childHeightSpec);
+ totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
+ }
+ if (totalWidth > givenWidth) {
+ int overFlow = totalWidth - givenWidth;
+ // We are overflowing, lets shrink
+ final int appWidth = mAppName.getMeasuredWidth();
+ if (appWidth > mHeaderMinWidth) {
+ int newSize = appWidth - Math.min(appWidth - mHeaderMinWidth, overFlow);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ mAppName.measure(childWidthSpec, wrapContentHeightSpec);
+ overFlow -= appWidth - newSize;
+ }
+ if (overFlow > 0 && mSubTextView.getVisibility() != GONE) {
+ // we're still too big
+ final int subTextWidth = mSubTextView.getMeasuredWidth();
+ int newSize = Math.max(0, subTextWidth - overFlow);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ mSubTextView.measure(childWidthSpec, wrapContentHeightSpec);
+ }
+ totalWidth = givenWidth;
+ }
+ setMeasuredDimension(totalWidth, givenHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ updateTouchListener();
+ }
+
+ private void updateTouchListener() {
+ if (mExpandClickListener != null) {
+ mTouchListener.bindTouchRects();
+ }
+ }
+
+ @Override
+ public void setOnClickListener(@Nullable OnClickListener l) {
+ mExpandClickListener = l;
+ setOnTouchListener(mExpandClickListener != null ? mTouchListener : null);
+ updateTouchListener();
+ }
+
+ public void setChildCount(int childCount) {
+ if (childCount > 0) {
+ mChildCount.setText(getContext().getString(
+ com.android.internal.R.string.notification_children_count_bracketed,
+ childCount));
+ mChildCount.setVisibility(VISIBLE);
+ } else {
+ mChildCount.setVisibility(GONE);
+ }
+ }
+
+ public class HeaderTouchListener implements View.OnTouchListener {
+
+ private final ArrayList<Rect> mTouchRects = new ArrayList<>();
+ private int mTouchSlop;
+ private boolean mTrackGesture;
+ private float mDownX;
+ private float mDownY;
+
+ public HeaderTouchListener() {
+ }
+
+ public void bindTouchRects() {
+ mTouchRects.clear();
+ addRectAroundViewView(mIcon);
+ addRectAroundViewView(mExpandButton);
+ addInBetweenRect();
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ private void addInBetweenRect() {
+ final Rect r = new Rect();
+ r.top = 0;
+ r.bottom = (int) (32 * getResources().getDisplayMetrics().density);
+ Rect leftRect = mTouchRects.get(0);
+ r.left = leftRect.right;
+ Rect rightRect = mTouchRects.get(1);
+ r.right = rightRect.left;
+ mTouchRects.add(r);
+ }
+
+ private void addRectAroundViewView(View view) {
+ final Rect r = getRectAroundView(view);
+ mTouchRects.add(r);
+ }
+
+ private Rect getRectAroundView(View view) {
+ float size = 48 * getResources().getDisplayMetrics().density;
+ final Rect r = new Rect();
+ if (view.getVisibility() == GONE) {
+ view = getFirstChildNotGone();
+ r.left = (int) (view.getLeft() - size / 2.0f);
+ } else {
+ r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - size / 2.0f);
+ }
+ r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - size / 2.0f);
+ r.bottom = (int) (r.top + size);
+ r.right = (int) (r.left + size);
+ return r;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ switch (event.getActionMasked() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ mTrackGesture = false;
+ if (isInside(x, y)) {
+ mTrackGesture = true;
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mTrackGesture) {
+ if (Math.abs(mDownX - x) > mTouchSlop
+ || Math.abs(mDownY - y) > mTouchSlop) {
+ mTrackGesture = false;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mTrackGesture) {
+ mExpandClickListener.onClick(NotificationHeaderView.this);
+ }
+ break;
+ }
+ return mTrackGesture;
+ }
+
+ private boolean isInside(float x, float y) {
+ for (int i = 0; i < mTouchRects.size(); i++) {
+ Rect r = mTouchRects.get(i);
+ if (r.contains((int) x, (int) y)) {
+ mDownX = x;
+ mDownY = y;
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private View getFirstChildNotGone() {
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ return child;
+ }
+ }
+ return this;
+ }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ab1943c..da66f97 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19429,6 +19429,7 @@
*
* @attr ref android.R.styleable#View_minHeight
*/
+ @RemotableViewMethod
public void setMinimumHeight(int minHeight) {
mMinHeight = minHeight;
requestLayout();
@@ -22369,7 +22370,13 @@
* Used to track views that need (at least) a partial relayout at their current size
* during the next traversal.
*/
- final List<View> mPartialLayoutViews = new ArrayList<View>();
+ List<View> mPartialLayoutViews = new ArrayList<>();
+
+ /**
+ * Swapped with mPartialLayoutViews during layout to avoid concurrent
+ * modification. Lazily assigned during ViewRootImpl layout.
+ */
+ List<View> mEmptyPartialLayoutViews;
/**
* Used to track the identity of the current drag operation.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b503e12..3c9310d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1974,6 +1974,15 @@
final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews;
final boolean didPartialLayout;
if (!partialLayoutViews.isEmpty()) {
+ // Measurement or layout of views may result in changes to the list
+ // of partial-layout views. Swap in an "empty" list to prevent
+ // concurrent modification of the list being traversed.
+ if (mAttachInfo.mEmptyPartialLayoutViews == null) {
+ mAttachInfo.mPartialLayoutViews = new ArrayList<>();
+ } else {
+ mAttachInfo.mPartialLayoutViews = mAttachInfo.mEmptyPartialLayoutViews;
+ }
+
final int count = partialLayoutViews.size();
mInLayout = true;
for (int i = 0; i < count; i++) {
@@ -1992,9 +2001,12 @@
}
}
mInLayout = false;
- partialLayoutViews.clear();
didPartialLayout = true;
triggerGlobalLayoutListener = true;
+
+ // The traversal list becomes the new empty list.
+ partialLayoutViews.clear();
+ mAttachInfo.mEmptyPartialLayoutViews = partialLayoutViews;
} else {
didPartialLayout = false;
}
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index b3b95f8..f313893 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -61,8 +61,7 @@
setText("");
mMatches = (TextView) mCustomView.findViewById(
com.android.internal.R.id.matches);
- mInput = (InputMethodManager)
- context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ mInput = context.getSystemService(InputMethodManager.class);
mResources = context.getResources();
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 90de053..f050e49 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -5755,8 +5755,8 @@
// The editor is off in its own window; we need to be
// the one that does this.
if (editorAction == EditorInfo.IME_ACTION_DONE) {
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm =
+ getContext().getSystemService(InputMethodManager.class);
if (imm != null) {
imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index c3d0993..5146bc6 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -17,6 +17,7 @@
package android.widget;
import android.R;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
@@ -106,6 +107,8 @@
import com.android.internal.util.Preconditions;
import com.android.internal.widget.EditableInputConnection;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.text.BreakIterator;
import java.util.Arrays;
import java.util.Comparator;
@@ -4087,7 +4090,17 @@
}
}
- private class SelectionStartHandleView extends HandleView {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({HANDLE_TYPE_SELECTION_START, HANDLE_TYPE_SELECTION_END})
+ public @interface HandleType {}
+ public static final int HANDLE_TYPE_SELECTION_START = 0;
+ public static final int HANDLE_TYPE_SELECTION_END = 1;
+
+ private class SelectionHandleView extends HandleView {
+ // Indicates the handle type, selection start (HANDLE_TYPE_SELECTION_START) or selection
+ // end (HANDLE_TYPE_SELECTION_END).
+ @HandleType
+ private final int mHandleType;
// Indicates whether the cursor is making adjustments within a word.
private boolean mInWord = false;
// Difference between touch position and word boundary position.
@@ -4102,16 +4115,21 @@
// Used to save text view location.
private final int[] mTextViewLocation = new int[2];
- public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
- super(drawableLtr, drawableRtl, com.android.internal.R.id.selection_start_handle);
- ViewConfiguration viewConfiguration = ViewConfiguration.get(
- mTextView.getContext());
+ public SelectionHandleView(Drawable drawableLtr, Drawable drawableRtl, int id,
+ @HandleType int handleType) {
+ super(drawableLtr, drawableRtl, id);
+ mHandleType = handleType;
+ ViewConfiguration viewConfiguration = ViewConfiguration.get(mTextView.getContext());
mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
}
+ private boolean isStartHandle() {
+ return mHandleType == HANDLE_TYPE_SELECTION_START;
+ }
+
@Override
protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
- if (isRtlRun) {
+ if (isRtlRun == isStartHandle()) {
return drawable.getIntrinsicWidth() / 4;
} else {
return (drawable.getIntrinsicWidth() * 3) / 4;
@@ -4120,18 +4138,23 @@
@Override
protected int getHorizontalGravity(boolean isRtlRun) {
- return isRtlRun ? Gravity.LEFT : Gravity.RIGHT;
+ return (isRtlRun == isStartHandle()) ? Gravity.LEFT : Gravity.RIGHT;
}
@Override
public int getCurrentCursorOffset() {
- return mTextView.getSelectionStart();
+ return isStartHandle() ? mTextView.getSelectionStart() : mTextView.getSelectionEnd();
}
@Override
- public void updateSelection(int offset) {
- Selection.setSelection((Spannable) mTextView.getText(), offset,
- mTextView.getSelectionEnd());
+ protected void updateSelection(int offset) {
+ if (isStartHandle()) {
+ Selection.setSelection((Spannable) mTextView.getText(), offset,
+ mTextView.getSelectionEnd());
+ } else {
+ Selection.setSelection((Spannable) mTextView.getText(),
+ mTextView.getSelectionStart(), offset);
+ }
updateDrawable();
if (mTextActionMode != null) {
mTextActionMode.invalidate();
@@ -4153,35 +4176,36 @@
}
boolean positionCursor = false;
- final int selectionEnd = mTextView.getSelectionEnd();
+ final int anotherHandleOffset =
+ isStartHandle() ? mTextView.getSelectionEnd() : mTextView.getSelectionStart();
int currLine = getCurrentLineAdjustedForSlop(layout, mPreviousLineTouched, y);
int initialOffset = mTextView.getOffsetAtCoordinate(currLine, x);
- if (initialOffset >= selectionEnd) {
- // Handles have crossed, bound it to the last selected line and
+ if (isStartHandle() && initialOffset >= anotherHandleOffset
+ || !isStartHandle() && initialOffset <= anotherHandleOffset) {
+ // Handles have crossed, bound it to the first selected line and
// adjust by word / char as normal.
- currLine = layout.getLineForOffset(selectionEnd);
+ currLine = layout.getLineForOffset(anotherHandleOffset);
initialOffset = mTextView.getOffsetAtCoordinate(currLine, x);
}
int offset = initialOffset;
- int end = getWordEnd(offset);
- int start = getWordStart(offset);
+ final int wordEnd = getWordEnd(offset);
+ final int wordStart = getWordStart(offset);
if (mPrevX == UNSET_X_VALUE) {
mPrevX = x;
}
- final int selectionStart = mTextView.getSelectionStart();
- final boolean selectionStartRtl = layout.isRtlCharAt(selectionStart);
+ final int currentOffset = getCurrentCursorOffset();
+ final boolean rtlAtCurrentOffset = layout.isRtlCharAt(currentOffset);
final boolean atRtl = layout.isRtlCharAt(offset);
final boolean isLvlBoundary = layout.isLevelBoundary(offset);
- boolean isExpanding;
// We can't determine if the user is expanding or shrinking the selection if they're
// on a bi-di boundary, so until they've moved past the boundary we'll just place
// the cursor at the current position.
- if (isLvlBoundary || (selectionStartRtl && !atRtl) || (!selectionStartRtl && atRtl)) {
+ if (isLvlBoundary || (rtlAtCurrentOffset && !atRtl) || (!rtlAtCurrentOffset && atRtl)) {
// We're on a boundary or this is the first direction change -- just update
// to the current position.
mLanguageDirectionChanged = true;
@@ -4195,24 +4219,30 @@
mTouchWordDelta = 0.0f;
mLanguageDirectionChanged = false;
return;
+ }
+
+ boolean isExpanding;
+ final float xDiff = x - mPrevX;
+ if (atRtl == isStartHandle()) {
+ isExpanding = xDiff > 0 || currLine > mPreviousLineTouched;
} else {
- final float xDiff = x - mPrevX;
- if (atRtl) {
- isExpanding = xDiff > 0 || currLine > mPreviousLineTouched;
- } else {
- isExpanding = xDiff < 0 || currLine < mPreviousLineTouched;
- }
+ isExpanding = xDiff < 0 || currLine < mPreviousLineTouched;
}
if (mTextView.getHorizontallyScrolling()) {
if (positionNearEdgeOfScrollingView(x, atRtl)
- && (mTextView.getScrollX() != 0)
- && ((isExpanding && offset < selectionStart) || !isExpanding)) {
- // If we're expanding ensure that the offset is smaller than the
- // selection start, if the handle snapped to the word, the finger position
+ && ((isStartHandle() && mTextView.getScrollX() != 0)
+ || (!isStartHandle()
+ && mTextView.canScrollHorizontally(atRtl ? -1 : 1)))
+ && ((isExpanding && ((isStartHandle() && offset < currentOffset)
+ || (!isStartHandle() && offset > currentOffset)))
+ || !isExpanding)) {
+ // If we're expanding ensure that the offset is actually expanding compared to
+ // the current offset, if the handle snapped to the word, the finger position
// may be out of sync and we don't want the selection to jump back.
mTouchWordDelta = 0.0f;
- final int nextOffset = atRtl ? layout.getOffsetToRightOf(mPreviousOffset)
+ final int nextOffset = (atRtl == isStartHandle())
+ ? layout.getOffsetToRightOf(mPreviousOffset)
: layout.getOffsetToLeftOf(mPreviousOffset);
positionAndAdjustForCrossingHandles(nextOffset);
return;
@@ -4221,24 +4251,36 @@
if (isExpanding) {
// User is increasing the selection.
- if (!mInWord || currLine < mPrevLine) {
+ final boolean snapToWord = !mInWord
+ || (isStartHandle() ? currLine < mPrevLine : currLine > mPrevLine);
+ if (snapToWord) {
// Sometimes words can be broken across lines (Chinese, hyphenation).
- // We still snap to the start of the word but we only use the letters on the
+ // We still snap to the word boundary but we only use the letters on the
// current line to determine if the user is far enough into the word to snap.
- int wordStartOnCurrLine = start;
- if (layout != null && layout.getLineForOffset(start) != currLine) {
- wordStartOnCurrLine = layout.getLineStart(currLine);
+ int wordBoundary = isStartHandle() ? wordStart : wordEnd;
+ if (layout != null && layout.getLineForOffset(wordBoundary) != currLine) {
+ wordBoundary = isStartHandle() ?
+ layout.getLineStart(currLine) : layout.getLineEnd(currLine);
}
- int offsetThresholdToSnap = end - ((end - wordStartOnCurrLine) / 2);
- if (offset <= offsetThresholdToSnap || currLine < mPrevLine) {
- // User is far enough into the word or on a different
- // line so we expand by word.
- offset = start;
+ final int offsetThresholdToSnap = isStartHandle()
+ ? wordEnd - ((wordEnd - wordBoundary) / 2)
+ : wordStart + ((wordBoundary - wordStart) / 2);
+ if (isStartHandle()
+ && (offset <= offsetThresholdToSnap || currLine < mPrevLine)) {
+ // User is far enough into the word or on a different line so we expand by
+ // word.
+ offset = wordStart;
+ } else if (!isStartHandle()
+ && (offset >= offsetThresholdToSnap || currLine > mPrevLine)) {
+ // User is far enough into the word or on a different line so we expand by
+ // word.
+ offset = wordEnd;
} else {
offset = mPreviousOffset;
}
}
- if (layout != null && offset < initialOffset) {
+ if (layout != null && (isStartHandle() && offset < initialOffset)
+ || (!isStartHandle() && offset > initialOffset)) {
final float adjustedX = layout.getPrimaryHorizontal(offset);
mTouchWordDelta =
mTextView.convertToLocalHorizontalCoordinate(x) - adjustedX;
@@ -4249,12 +4291,16 @@
} else {
final int adjustedOffset =
mTextView.getOffsetAtCoordinate(currLine, x - mTouchWordDelta);
- if (adjustedOffset > mPreviousOffset || currLine > mPrevLine) {
+ final boolean shrinking = isStartHandle()
+ ? adjustedOffset > mPreviousOffset || currLine > mPrevLine
+ : adjustedOffset < mPreviousOffset || currLine < mPrevLine;
+ if (shrinking) {
// User is shrinking the selection.
- if (currLine > mPrevLine) {
+ if (currLine != mPrevLine) {
// We're on a different line, so we'll snap to word boundaries.
- offset = start;
- if (layout != null && offset < initialOffset) {
+ offset = isStartHandle() ? wordStart : wordEnd;
+ if (layout != null && (isStartHandle() && offset < initialOffset)
+ || (!isStartHandle() && offset > initialOffset)) {
final float adjustedX = layout.getPrimaryHorizontal(offset);
mTouchWordDelta =
mTextView.convertToLocalHorizontalCoordinate(x) - adjustedX;
@@ -4265,11 +4311,12 @@
offset = adjustedOffset;
}
positionCursor = true;
- } else if (adjustedOffset < mPreviousOffset) {
- // Handle has jumped to the start of the word, and the user is moving
+ } else if ((isStartHandle() && adjustedOffset < mPreviousOffset)
+ || (!isStartHandle() && adjustedOffset > mPreviousOffset)) {
+ // Handle has jumped to the word boundary, and the user is moving
// their finger towards the handle, the delta should be updated.
- mTouchWordDelta = mTextView.convertToLocalHorizontalCoordinate(x)
- - layout.getPrimaryHorizontal(mPreviousOffset);
+ mTouchWordDelta = mTextView.convertToLocalHorizontalCoordinate(x) -
+ layout.getPrimaryHorizontal(mPreviousOffset);
}
}
@@ -4280,16 +4327,6 @@
mPrevX = x;
}
- private void positionAndAdjustForCrossingHandles(int offset) {
- final int selectionEnd = mTextView.getSelectionEnd();
- if (offset >= selectionEnd) {
- // Handles can not cross and selection is at least one character.
- offset = getNextCursorOffset(selectionEnd, false);
- mTouchWordDelta = 0.0f;
- }
- positionAtCursorOffset(offset, false);
- }
-
/**
* @param offset Cursor offset. Must be in [-1, length].
* @param parentScrolled If the parent has been scrolled or not.
@@ -4312,256 +4349,28 @@
return superResult;
}
- private boolean positionNearEdgeOfScrollingView(float x, boolean atRtl) {
- mTextView.getLocationOnScreen(mTextViewLocation);
- boolean nearEdge;
- if (atRtl) {
- int rightEdge = mTextViewLocation[0] + mTextView.getWidth()
- - mTextView.getPaddingRight();
- nearEdge = x > rightEdge - mTextViewEdgeSlop;
- } else {
- int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
- nearEdge = x < leftEdge + mTextViewEdgeSlop;
- }
- return nearEdge;
- }
- }
-
- private class SelectionEndHandleView extends HandleView {
- // Indicates whether the cursor is making adjustments within a word.
- private boolean mInWord = false;
- // Difference between touch position and word boundary position.
- private float mTouchWordDelta;
- // X value of the previous updatePosition call.
- private float mPrevX;
- // Indicates if the handle has moved a boundary between LTR and RTL text.
- private boolean mLanguageDirectionChanged = false;
- // Distance from edge of horizontally scrolling text view
- // to use to switch to character mode.
- private final float mTextViewEdgeSlop;
- // Used to save the text view location.
- private final int[] mTextViewLocation = new int[2];
-
- public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
- super(drawableLtr, drawableRtl, com.android.internal.R.id.selection_end_handle);
- ViewConfiguration viewConfiguration = ViewConfiguration.get(
- mTextView.getContext());
- mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
- }
-
- @Override
- protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
- if (isRtlRun) {
- return (drawable.getIntrinsicWidth() * 3) / 4;
- } else {
- return drawable.getIntrinsicWidth() / 4;
- }
- }
-
- @Override
- protected int getHorizontalGravity(boolean isRtlRun) {
- return isRtlRun ? Gravity.RIGHT : Gravity.LEFT;
- }
-
- @Override
- public int getCurrentCursorOffset() {
- return mTextView.getSelectionEnd();
- }
-
- @Override
- public void updateSelection(int offset) {
- Selection.setSelection((Spannable) mTextView.getText(),
- mTextView.getSelectionStart(), offset);
- if (mTextActionMode != null) {
- mTextActionMode.invalidate();
- }
- updateDrawable();
- }
-
- @Override
- public void updatePosition(float x, float y) {
- final Layout layout = mTextView.getLayout();
- if (layout == null) {
- // HandleView will deal appropriately in positionAtCursorOffset when
- // layout is null.
- positionAndAdjustForCrossingHandles(mTextView.getOffsetForPosition(x, y));
- return;
- }
-
- if (mPreviousLineTouched == UNSET_LINE) {
- mPreviousLineTouched = mTextView.getLineAtCoordinate(y);
- }
-
- boolean positionCursor = false;
- final int selectionStart = mTextView.getSelectionStart();
- int currLine = getCurrentLineAdjustedForSlop(layout, mPreviousLineTouched, y);
- int initialOffset = mTextView.getOffsetAtCoordinate(currLine, x);
-
- if (initialOffset <= selectionStart) {
- // Handles have crossed, bound it to the first selected line and
- // adjust by word / char as normal.
- currLine = layout.getLineForOffset(selectionStart);
- initialOffset = mTextView.getOffsetAtCoordinate(currLine, x);
- }
-
- int offset = initialOffset;
- int end = getWordEnd(offset);
- int start = getWordStart(offset);
-
- if (mPrevX == UNSET_X_VALUE) {
- mPrevX = x;
- }
-
- final int selectionEnd = mTextView.getSelectionEnd();
- final boolean selectionEndRtl = layout.isRtlCharAt(selectionEnd);
- final boolean atRtl = layout.isRtlCharAt(offset);
- final boolean isLvlBoundary = layout.isLevelBoundary(offset);
- boolean isExpanding;
-
- // We can't determine if the user is expanding or shrinking the selection if they're
- // on a bi-di boundary, so until they've moved past the boundary we'll just place
- // the cursor at the current position.
- if (isLvlBoundary || (selectionEndRtl && !atRtl) || (!selectionEndRtl && atRtl)) {
- // We're on a boundary or this is the first direction change -- just update
- // to the current position.
- mLanguageDirectionChanged = true;
- mTouchWordDelta = 0.0f;
- positionAndAdjustForCrossingHandles(offset);
- return;
- } else if (mLanguageDirectionChanged && !isLvlBoundary) {
- // We've just moved past the boundary so update the position. After this we can
- // figure out if the user is expanding or shrinking to go by word or character.
- positionAndAdjustForCrossingHandles(offset);
- mTouchWordDelta = 0.0f;
- mLanguageDirectionChanged = false;
- return;
- } else {
- final float xDiff = x - mPrevX;
- if (atRtl) {
- isExpanding = xDiff < 0 || currLine < mPreviousLineTouched;
- } else {
- isExpanding = xDiff > 0 || currLine > mPreviousLineTouched;
- }
- }
-
- if (mTextView.getHorizontallyScrolling()) {
- if (positionNearEdgeOfScrollingView(x, atRtl)
- && mTextView.canScrollHorizontally(atRtl ? -1 : 1)
- && ((isExpanding && offset > selectionEnd) || !isExpanding)) {
- // If we're expanding ensure that the offset is actually greater than the
- // selection end, if the handle snapped to the word, the finger position
- // may be out of sync and we don't want the selection to jump back.
- mTouchWordDelta = 0.0f;
- final int nextOffset = atRtl ? layout.getOffsetToLeftOf(mPreviousOffset)
- : layout.getOffsetToRightOf(mPreviousOffset);
- positionAndAdjustForCrossingHandles(nextOffset);
- return;
- }
- }
-
- if (isExpanding) {
- // User is increasing the selection.
- if (!mInWord || currLine > mPrevLine) {
- // Sometimes words can be broken across lines (Chinese, hyphenation).
- // We still snap to the end of the word but we only use the letters on the
- // current line to determine if the user is far enough into the word to snap.
- int wordEndOnCurrLine = end;
- if (layout != null && layout.getLineForOffset(end) != currLine) {
- wordEndOnCurrLine = layout.getLineEnd(currLine);
- }
- final int offsetThresholdToSnap = start + ((wordEndOnCurrLine - start) / 2);
- if (offset >= offsetThresholdToSnap || currLine > mPrevLine) {
- // User is far enough into the word or on a different
- // line so we expand by word.
- offset = end;
- } else {
- offset = mPreviousOffset;
- }
- }
- if (offset > initialOffset) {
- final float adjustedX = layout.getPrimaryHorizontal(offset);
- mTouchWordDelta =
- adjustedX - mTextView.convertToLocalHorizontalCoordinate(x);
- } else {
- mTouchWordDelta = 0.0f;
- }
- positionCursor = true;
- } else {
- final int adjustedOffset =
- mTextView.getOffsetAtCoordinate(currLine, x + mTouchWordDelta);
- if (adjustedOffset < mPreviousOffset || currLine < mPrevLine) {
- // User is shrinking the selection.
- if (currLine < mPrevLine) {
- // We're on a different line, so we'll snap to word boundaries.
- offset = end;
- if (offset > initialOffset) {
- final float adjustedX = layout.getPrimaryHorizontal(offset);
- mTouchWordDelta =
- adjustedX - mTextView.convertToLocalHorizontalCoordinate(x);
- } else {
- mTouchWordDelta = 0.0f;
- }
- } else {
- offset = adjustedOffset;
- }
- positionCursor = true;
- } else if (adjustedOffset > mPreviousOffset) {
- // Handle has jumped to the end of the word, and the user is moving
- // their finger towards the handle, the delta should be updated.
- mTouchWordDelta = layout.getPrimaryHorizontal(mPreviousOffset)
- - mTextView.convertToLocalHorizontalCoordinate(x);
- }
- }
-
- if (positionCursor) {
- mPreviousLineTouched = currLine;
- positionAndAdjustForCrossingHandles(offset);
- }
- mPrevX = x;
- }
-
private void positionAndAdjustForCrossingHandles(int offset) {
- final int selectionStart = mTextView.getSelectionStart();
- if (offset <= selectionStart) {
+ final int anotherHandleOffset =
+ isStartHandle() ? mTextView.getSelectionEnd() : mTextView.getSelectionStart();
+ if ((isStartHandle() && offset >= anotherHandleOffset)
+ || (!isStartHandle() && offset <= anotherHandleOffset)) {
// Handles can not cross and selection is at least one character.
- offset = getNextCursorOffset(selectionStart, true);
+ offset = getNextCursorOffset(anotherHandleOffset, !isStartHandle());
mTouchWordDelta = 0.0f;
}
positionAtCursorOffset(offset, false);
}
- /**
- * @param offset Cursor offset. Must be in [-1, length].
- * @param parentScrolled If the parent has been scrolled or not.
- */
- @Override
- protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
- super.positionAtCursorOffset(offset, parentScrolled);
- mInWord = (offset != -1) && !getWordIteratorWithText().isBoundary(offset);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- boolean superResult = super.onTouchEvent(event);
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- // Reset the touch word offset and x value when the user
- // re-engages the handle.
- mTouchWordDelta = 0.0f;
- mPrevX = UNSET_X_VALUE;
- }
- return superResult;
- }
-
private boolean positionNearEdgeOfScrollingView(float x, boolean atRtl) {
mTextView.getLocationOnScreen(mTextViewLocation);
boolean nearEdge;
- if (atRtl) {
- int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
- nearEdge = x < leftEdge + mTextViewEdgeSlop;
- } else {
+ if (atRtl == isStartHandle()) {
int rightEdge = mTextViewLocation[0] + mTextView.getWidth()
- mTextView.getPaddingRight();
nearEdge = x > rightEdge - mTextViewEdgeSlop;
+ } else {
+ int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
+ nearEdge = x < leftEdge + mTextViewEdgeSlop;
}
return nearEdge;
}
@@ -4673,8 +4482,8 @@
class SelectionModifierCursorController implements CursorController {
// The cursor controller handles, lazily created when shown.
- private SelectionStartHandleView mStartHandle;
- private SelectionEndHandleView mEndHandle;
+ private SelectionHandleView mStartHandle;
+ private SelectionHandleView mEndHandle;
// The offsets of that last touch down event. Remembered to start selection there.
private int mMinTouchOffset, mMaxTouchOffset;
@@ -4718,10 +4527,14 @@
private void initHandles() {
// Lazy object creation has to be done before updatePosition() is called.
if (mStartHandle == null) {
- mStartHandle = new SelectionStartHandleView(mSelectHandleLeft, mSelectHandleRight);
+ mStartHandle = new SelectionHandleView(mSelectHandleLeft, mSelectHandleRight,
+ com.android.internal.R.id.selection_start_handle,
+ HANDLE_TYPE_SELECTION_START);
}
if (mEndHandle == null) {
- mEndHandle = new SelectionEndHandleView(mSelectHandleRight, mSelectHandleLeft);
+ mEndHandle = new SelectionHandleView(mSelectHandleRight, mSelectHandleLeft,
+ com.android.internal.R.id.selection_end_handle,
+ HANDLE_TYPE_SELECTION_END);
}
mStartHandle.show();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index ce1c108..0ed72e4d 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1638,6 +1638,47 @@
}
/**
+ * Helper action to set layout margin on a View.
+ */
+ private class ViewMarginEndAction extends Action {
+ public ViewMarginEndAction(int viewId, int end) {
+ this.viewId = viewId;
+ this.end = end;
+ }
+
+ public ViewMarginEndAction(Parcel parcel) {
+ viewId = parcel.readInt();
+ end = parcel.readInt();
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(TAG);
+ dest.writeInt(viewId);
+ dest.writeInt(end);
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+ final View target = root.findViewById(viewId);
+ if (target == null) {
+ return;
+ }
+ ViewGroup.LayoutParams layoutParams = target.getLayoutParams();
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
+ ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(end);
+ }
+ }
+
+ public String getActionName() {
+ return "ViewMarginEndAction";
+ }
+
+ int end;
+
+ public final static int TAG = 19;
+ }
+
+ /**
* Helper action to set a color filter on a compound drawable on a TextView. Supports relative
* (s/t/e/b) or cardinal (l/t/r/b) arrangement.
*/
@@ -1942,6 +1983,9 @@
case SetRemoteInputsAction.TAG:
mActions.add(new SetRemoteInputsAction(parcel));
break;
+ case ViewMarginEndAction.TAG:
+ mActions.add(new ViewMarginEndAction(parcel));
+ break;
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -2549,6 +2593,19 @@
}
/**
+ * @hide
+ * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}.
+ * Only works if the {@link View#getLayoutParams()} supports margins.
+ * Hidden for now since we don't want to support this for all different layout margins yet.
+ *
+ * @param viewId The id of the view to change
+ * @param endMargin the left padding in pixels
+ */
+ public void setViewLayoutMarginEnd(int viewId, int endMargin) {
+ addAction(new ViewMarginEndAction(viewId, endMargin));
+ }
+
+ /**
* Call a method taking one boolean on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index ad2b4a7..88f02d1 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -152,8 +152,7 @@
*/
private Runnable mShowImeRunnable = new Runnable() {
public void run() {
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
if (imm != null) {
imm.showSoftInputUnchecked(0, null);
@@ -912,8 +911,7 @@
post(mShowImeRunnable);
} else {
removeCallbacks(mShowImeRunnable);
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
if (imm != null) {
imm.hideSoftInputFromWindow(getWindowToken(), 0);
@@ -1768,8 +1766,8 @@
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus && mSearchView.hasFocus() && getVisibility() == VISIBLE) {
- InputMethodManager inputManager = (InputMethodManager) getContext()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager inputManager =
+ getContext().getSystemService(InputMethodManager.class);
inputManager.showSoftInput(this, 0);
// If in landscape mode, then make sure that
// the ime is in front of the dropdown.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 476c6a2..d666939 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6828,7 +6828,10 @@
if (mEditor != null) mEditor.prepareCursorControllers();
}
- private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
+ /**
+ * @hide
+ */
+ protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
boolean useSaved) {
Layout result = null;
@@ -7225,6 +7228,7 @@
// of the intended optimizations as part of requestLayoutForChild.
nullLayouts();
requestLayout();
+ invalidate();
}
@Override
@@ -9707,7 +9711,10 @@
}
}
- TextDirectionHeuristic getTextDirectionHeuristic() {
+ /**
+ * @hide
+ */
+ protected TextDirectionHeuristic getTextDirectionHeuristic() {
if (hasPasswordTransformationMethod()) {
// passwords fields should be LTR
return TextDirectionHeuristics.LTR;
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index c992c70..cc677cc 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -34,6 +34,7 @@
public static final int ACTION_ZEN_ALLOW_PEEK = 261;
public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
public static final int NOTIFICATION_TOPIC_NOTIFICATION = 263;
+ public static final int ACTION_DEFAULT_SMS_APP_CHANGED = 264;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 7bab446..3a00469 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
@@ -28,7 +29,9 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.Size;
import android.view.ContextThemeWrapper;
@@ -36,6 +39,7 @@
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
@@ -45,6 +49,8 @@
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.Transformation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
@@ -277,70 +283,56 @@
* A popup window used by the floating toolbar.
*
* This class is responsible for the rendering/animation of the floating toolbar.
- * It can hold one of 2 panels (i.e. main panel and overflow panel) at a time.
- * It delegates specific panel functionality to the appropriate panel.
+ * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
+ * to transition between panels.
*/
private static final class FloatingToolbarPopup {
- public static final int OVERFLOW_DIRECTION_UP = 0;
- public static final int OVERFLOW_DIRECTION_DOWN = 1;
+ /* Minimum and maximum number of items allowed in the overflow. */
+ private static final int MIN_OVERFLOW_SIZE = 2;
+ private static final int MAX_OVERFLOW_SIZE = 4;
+
+ /* The duration of the overflow button vector animation duration. */
+ private static final int OVERFLOW_BUTTON_ANIMATION_DELAY = 400;
private final Context mContext;
- private final View mParent;
+ private final View mParent; // Parent for the popup window.
private final PopupWindow mPopupWindow;
- private final ViewGroup mContentContainer;
+
+ /* Margins between the popup window and it's content. */
private final int mMarginHorizontal;
private final int mMarginVertical;
- private final Animation.AnimationListener mOnOverflowOpened =
- new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {}
+ /* View components */
+ private final ViewGroup mContentContainer; // holds all contents.
+ private final ViewGroup mMainPanel; // holds menu items that are initially displayed.
+ private final ListView mOverflowPanel; // holds menu items hidden in the overflow.
+ private final ImageButton mOverflowButton; // opens/closes the overflow.
+ /* overflow button drawables. */
+ private final Drawable mArrow;
+ private final Drawable mOverflow;
+ private final AnimatedVectorDrawable mToArrow;
+ private final AnimatedVectorDrawable mToOverflow;
- @Override
- public void onAnimationEnd(Animation animation) {
- setOverflowPanelAsContent();
- mOverflowPanel.fadeIn(true);
- }
+ private final OverflowPanelViewHelper mOverflowPanelViewHelper;
- @Override
- public void onAnimationRepeat(Animation animation) {}
- };
- private final Animation.AnimationListener mOnOverflowClosed =
- new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {}
+ /* Animation interpolators. */
+ private final Interpolator mLogAccelerateInterpolator;
+ private final Interpolator mFastOutSlowInInterpolator;
+ private final Interpolator mLinearOutSlowInInterpolator;
+ private final Interpolator mFastOutLinearInInterpolator;
- @Override
- public void onAnimationEnd(Animation animation) {
- setMainPanelAsContent();
- mMainPanel.fadeIn(true);
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
- };
+ /* Animations. */
+ private final AnimatorSet mShowAnimation;
private final AnimatorSet mDismissAnimation;
private final AnimatorSet mHideAnimation;
- private final AnimationSet mOpenOverflowAnimation = new AnimationSet(true);
- private final AnimationSet mCloseOverflowAnimation = new AnimationSet(true);
+ private final AnimationSet mOpenOverflowAnimation;
+ private final AnimationSet mCloseOverflowAnimation;
+ private final Animation.AnimationListener mOverflowAnimationListener;
- private final Runnable mOpenOverflow = new Runnable() {
- @Override
- public void run() {
- openOverflow();
- }
- };
- private final Runnable mCloseOverflow = new Runnable() {
- @Override
- public void run() {
- closeOverflow();
- }
- };
-
- private final Rect mViewPortOnScreen = new Rect();
- private final Point mCoordsOnWindow = new Point();
+ private final Rect mViewPortOnScreen = new Rect(); // portion of screen we can draw in.
+ private final Point mCoordsOnWindow = new Point(); // popup window coordinates.
+ /* Temporary data holders. Reset values before using. */
private final int[] mTmpCoords = new int[2];
private final Rect mTmpRect = new Rect();
@@ -357,12 +349,56 @@
}
};
+ /**
+ * @see OverflowPanelViewHelper#preparePopupContent().
+ */
+ private final Runnable mPreparePopupContentRTLHelper = new Runnable() {
+ @Override
+ public void run() {
+ setPanelsStatesAtRestingPosition();
+ setContentAreaAsTouchableSurface();
+ mContentContainer.setAlpha(1);
+ }
+ };
+
+ /* Runnable to reset the overflow button's drawable after an overflow transition. */
+ private final Runnable mResetOverflowButtonDrawable = new Runnable() {
+ @Override
+ public void run() {
+ if (mIsOverflowOpen) {
+ mOverflowButton.setImageDrawable(mArrow);
+ } else {
+ mOverflowButton.setImageDrawable(mOverflow);
+ }
+ }
+ };
+
private boolean mDismissed = true; // tracks whether this popup is dismissed or dismissing.
private boolean mHidden; // tracks whether this popup is hidden or hiding.
- private FloatingToolbarOverflowPanel mOverflowPanel;
- private FloatingToolbarMainPanel mMainPanel;
- private int mOverflowDirection;
+ /* Calculated sizes for panels and overflow button. */
+ private final Size mOverflowButtonSize;
+ private Size mOverflowPanelSize; // Should be null when there is no overflow.
+ private Size mMainPanelSize;
+
+ /* Item click listeners */
+ private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
+ private final View.OnClickListener mMenuItemButtonOnClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (v.getTag() instanceof MenuItem) {
+ if (mOnMenuItemClickListener != null) {
+ mOnMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
+ }
+ }
+ }
+ };
+
+ private boolean mOpenOverflowUpwards; // Whether the overflow opens upwards or downwards.
+ private boolean mIsOverflowOpen;
+
+ private int mTransitionDurationScale; // Used to scale the toolbar transition duration.
/**
* Initializes a new floating toolbar popup.
@@ -375,6 +411,48 @@
mContext = Preconditions.checkNotNull(context);
mContentContainer = createContentContainer(context);
mPopupWindow = createPopupWindow(mContentContainer);
+ mMarginHorizontal = parent.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
+ mMarginVertical = parent.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
+
+ // Interpolators
+ mLogAccelerateInterpolator = new LogAccelerateInterpolator();
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_slow_in);
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.linear_out_slow_in);
+ mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_linear_in);
+
+ // Drawables. Needed for views.
+ mArrow = mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_tooverflow, mContext.getTheme());
+ mArrow.setAutoMirrored(true);
+ mOverflow = mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_toarrow, mContext.getTheme());
+ mOverflow.setAutoMirrored(true);
+ mToArrow = (AnimatedVectorDrawable) mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_toarrow_animation, mContext.getTheme());
+ mToArrow.setAutoMirrored(true);
+ mToOverflow = (AnimatedVectorDrawable) mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_tooverflow_animation, mContext.getTheme());
+ mToOverflow.setAutoMirrored(true);
+
+ // Views
+ mOverflowButton = createOverflowButton();
+ mOverflowButtonSize = measure(mOverflowButton);
+ mMainPanel = createMainPanel();
+ mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext);
+ mOverflowPanel = createOverflowPanel();
+
+ // Animation. Need views.
+ mOverflowAnimationListener = createOverflowAnimationListener();
+ mOpenOverflowAnimation = new AnimationSet(true);
+ mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
+ mCloseOverflowAnimation = new AnimationSet(true);
+ mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
+ mShowAnimation = createEnterAnimation(mContentContainer);
mDismissAnimation = createExitAnimation(
mContentContainer,
150, // startDelay
@@ -394,35 +472,23 @@
mPopupWindow.dismiss();
}
});
- mMarginHorizontal = parent.getResources()
- .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
- mMarginVertical = parent.getResources()
- .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
}
/**
* Lays out buttons for the specified menu items.
+ * Requires a subsequent call to {@link #show()} to show the items.
*/
public void layoutMenuItems(
List<MenuItem> menuItems,
MenuItem.OnMenuItemClickListener menuItemClickListener,
int suggestedWidth) {
- Preconditions.checkNotNull(menuItems);
-
- mContentContainer.removeAllViews();
- if (mMainPanel == null) {
- mMainPanel = new FloatingToolbarMainPanel(mContext, mOpenOverflow);
- }
- List<MenuItem> overflowMenuItems =
- mMainPanel.layoutMenuItems(menuItems, getToolbarWidth(suggestedWidth));
- mMainPanel.setOnMenuItemClickListener(menuItemClickListener);
- if (!overflowMenuItems.isEmpty()) {
- if (mOverflowPanel == null) {
- mOverflowPanel =
- new FloatingToolbarOverflowPanel(mContext, mCloseOverflow);
- }
- mOverflowPanel.setMenuItems(overflowMenuItems);
- mOverflowPanel.setOnMenuItemClickListener(menuItemClickListener);
+ mOnMenuItemClickListener = menuItemClickListener;
+ cancelOverflowAnimations();
+ clearPanels();
+ menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth));
+ if (!menuItems.isEmpty()) {
+ // Add remaining items to the overflow.
+ layoutOverflowPanelItems(menuItems);
}
updatePopupSize();
}
@@ -443,20 +509,13 @@
cancelDismissAndHideAnimations();
cancelOverflowAnimations();
- // Make sure a panel is set as the content.
- if (mContentContainer.getChildCount() == 0) {
- setMainPanelAsContent();
- // If we're yet to show the popup, set the container visibility to zero.
- // The "show" animation will make this visible.
- mContentContainer.setAlpha(0);
- }
refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
preparePopupContent();
// We need to specify the position in window coordinates.
// TODO: Consider to use PopupWindow.setLayoutInScreenEnabled(true) so that we can
- // specify the popup poision in screen coordinates.
- mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, mCoordsOnWindow.x,
- mCoordsOnWindow.y);
+ // specify the popup position in screen coordinates.
+ mPopupWindow.showAtLocation(
+ mParent, Gravity.NO_GRAVITY, mCoordsOnWindow.x, mCoordsOnWindow.y);
setTouchableSurfaceInsetsComputer();
runShowAnimation();
}
@@ -472,6 +531,7 @@
mHidden = false;
mDismissed = true;
mHideAnimation.cancel();
+
runDismissAnimation();
setZeroTouchableSurface();
}
@@ -521,104 +581,90 @@
preparePopupContent();
// We need to specify the position in window coordinates.
// TODO: Consider to use PopupWindow.setLayoutInScreenEnabled(true) so that we can
- // specify the popup poision in screen coordinates.
- mPopupWindow.update(mCoordsOnWindow.x, mCoordsOnWindow.y, getWidth(), getHeight());
- }
-
- /**
- * Returns the width of this popup.
- */
- public int getWidth() {
- return mPopupWindow.getWidth();
- }
-
- /**
- * Returns the height of this popup.
- */
- public int getHeight() {
- return mPopupWindow.getHeight();
- }
-
- /**
- * Returns the context this popup is running in.
- */
- public Context getContext() {
- return mContext;
+ // specify the popup position in screen coordinates.
+ mPopupWindow.update(
+ mCoordsOnWindow.x, mCoordsOnWindow.y,
+ mPopupWindow.getWidth(), mPopupWindow.getHeight());
}
private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) {
refreshViewPort();
- int x = contentRectOnScreen.centerX() - getWidth() / 2;
+ int x = contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2;
// Update x so that the toolbar isn't rendered behind the nav bar in landscape.
- x = Math.max(0, Math.min(x, mViewPortOnScreen.right - getWidth()));
+ x = Math.max(0, Math.min(x, mViewPortOnScreen.right - mPopupWindow.getWidth()));
- int y;
+ final int y;
- int availableHeightAboveContent = contentRectOnScreen.top - mViewPortOnScreen.top;
- int availableHeightBelowContent = mViewPortOnScreen.bottom - contentRectOnScreen.bottom;
+ final int availableHeightAboveContent =
+ contentRectOnScreen.top - mViewPortOnScreen.top;
+ final int availableHeightBelowContent =
+ mViewPortOnScreen.bottom - contentRectOnScreen.bottom;
- if (mOverflowPanel == null) { // There is no overflow.
- if (availableHeightAboveContent >= getToolbarHeightWithVerticalMargin()) {
+ final int margin = 2 * mMarginVertical;
+ final int toolbarHeightWithVerticalMargin = getLineHeight(mContext) + margin;
+
+ if (!hasOverflow()) {
+ if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin) {
// There is enough space at the top of the content.
- y = contentRectOnScreen.top - getToolbarHeightWithVerticalMargin();
- } else if (availableHeightBelowContent >= getToolbarHeightWithVerticalMargin()) {
+ y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
+ } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin) {
// There is enough space at the bottom of the content.
y = contentRectOnScreen.bottom;
- } else if (availableHeightBelowContent >= getEstimatedToolbarHeight(mContext)) {
+ } else if (availableHeightBelowContent >= getLineHeight(mContext)) {
// Just enough space to fit the toolbar with no vertical margins.
y = contentRectOnScreen.bottom - mMarginVertical;
} else {
// Not enough space. Prefer to position as high as possible.
y = Math.max(
mViewPortOnScreen.top,
- contentRectOnScreen.top - getToolbarHeightWithVerticalMargin());
+ contentRectOnScreen.top - toolbarHeightWithVerticalMargin);
}
- } else { // There is an overflow.
- int margin = 2 * mMarginVertical;
- int minimumOverflowHeightWithMargin = mOverflowPanel.getMinimumHeight() + margin;
- int availableHeightThroughContentDown = mViewPortOnScreen.bottom -
- contentRectOnScreen.top + getToolbarHeightWithVerticalMargin();
- int availableHeightThroughContentUp = contentRectOnScreen.bottom -
- mViewPortOnScreen.top + getToolbarHeightWithVerticalMargin();
+ } else {
+ // Has an overflow.
+ final int minimumOverflowHeightWithMargin =
+ calculateOverflowHeight(MIN_OVERFLOW_SIZE) + margin;
+ final int availableHeightThroughContentDown = mViewPortOnScreen.bottom -
+ contentRectOnScreen.top + toolbarHeightWithVerticalMargin;
+ final int availableHeightThroughContentUp = contentRectOnScreen.bottom -
+ mViewPortOnScreen.top + toolbarHeightWithVerticalMargin;
if (availableHeightAboveContent >= minimumOverflowHeightWithMargin) {
// There is enough space at the top of the content rect for the overflow.
// Position above and open upwards.
updateOverflowHeight(availableHeightAboveContent - margin);
- y = contentRectOnScreen.top - getHeight();
- mOverflowDirection = OVERFLOW_DIRECTION_UP;
- } else if (availableHeightAboveContent >= getToolbarHeightWithVerticalMargin()
+ y = contentRectOnScreen.top - mPopupWindow.getHeight();
+ mOpenOverflowUpwards = true;
+ } else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin
&& availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) {
// There is enough space at the top of the content rect for the main panel
// but not the overflow.
// Position above but open downwards.
updateOverflowHeight(availableHeightThroughContentDown - margin);
- y = contentRectOnScreen.top - getToolbarHeightWithVerticalMargin();
- mOverflowDirection = OVERFLOW_DIRECTION_DOWN;
+ y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
+ mOpenOverflowUpwards = false;
} else if (availableHeightBelowContent >= minimumOverflowHeightWithMargin) {
// There is enough space at the bottom of the content rect for the overflow.
// Position below and open downwards.
updateOverflowHeight(availableHeightBelowContent - margin);
y = contentRectOnScreen.bottom;
- mOverflowDirection = OVERFLOW_DIRECTION_DOWN;
- } else if (availableHeightBelowContent >= getToolbarHeightWithVerticalMargin()
+ mOpenOverflowUpwards = false;
+ } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin
&& mViewPortOnScreen.height() >= minimumOverflowHeightWithMargin) {
// There is enough space at the bottom of the content rect for the main panel
// but not the overflow.
// Position below but open upwards.
updateOverflowHeight(availableHeightThroughContentUp - margin);
- y = contentRectOnScreen.bottom + getToolbarHeightWithVerticalMargin() -
- getHeight();
- mOverflowDirection = OVERFLOW_DIRECTION_UP;
+ y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin -
+ mPopupWindow.getHeight();
+ mOpenOverflowUpwards = true;
} else {
// Not enough space.
// Position at the top of the view port and open downwards.
updateOverflowHeight(mViewPortOnScreen.height() - margin);
y = mViewPortOnScreen.top;
- mOverflowDirection = OVERFLOW_DIRECTION_DOWN;
+ mOpenOverflowUpwards = false;
}
- mOverflowPanel.setOverflowDirection(mOverflowDirection);
}
// We later specify the location of PopupWindow relative to the attached window.
@@ -639,15 +685,11 @@
mCoordsOnWindow.set(x - windowLeftOnScreen, y - windowTopOnScreen);
}
- private int getToolbarHeightWithVerticalMargin() {
- return getEstimatedToolbarHeight(mContext) + mMarginVertical * 2;
- }
-
/**
* Performs the "show" animation on the floating popup.
*/
private void runShowAnimation() {
- createEnterAnimation(mContentContainer).start();
+ mShowAnimation.start();
}
/**
@@ -670,42 +712,16 @@
}
private void cancelOverflowAnimations() {
- if (mOpenOverflowAnimation.hasStarted()
- && !mOpenOverflowAnimation.hasEnded()) {
- // Remove the animation listener, stop the animation,
- // then trigger the lister explicitly so it is not posted
- // to the message queue.
- mOpenOverflowAnimation.setAnimationListener(null);
- mContentContainer.clearAnimation();
- mOnOverflowOpened.onAnimationEnd(null);
- }
- if (mCloseOverflowAnimation.hasStarted()
- && !mCloseOverflowAnimation.hasEnded()) {
- // Remove the animation listener, stop the animation,
- // then trigger the lister explicitly so it is not posted
- // to the message queue.
- mCloseOverflowAnimation.setAnimationListener(null);
- mContentContainer.clearAnimation();
- mOnOverflowClosed.onAnimationEnd(null);
- }
+ mContentContainer.clearAnimation();
+ mMainPanel.animate().cancel();
+ mOverflowPanel.animate().cancel();
+ mToArrow.stop();
+ mToOverflow.stop();
}
- /**
- * Opens the floating toolbar overflow.
- * This method should not be called if menu items have not been laid out with
- * {@link #layoutMenuItems(java.util.List, MenuItem.OnMenuItemClickListener, int)}.
- *
- * @throws IllegalStateException if called when menu items have not been laid out.
- */
private void openOverflow() {
- Preconditions.checkState(mMainPanel != null);
- Preconditions.checkState(mOverflowPanel != null);
-
- mMainPanel.fadeOut(true);
- Size overflowPanelSize = mOverflowPanel.measure();
- final int targetWidth = overflowPanelSize.getWidth();
- final int targetHeight = overflowPanelSize.getHeight();
- final boolean morphUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
+ final int targetWidth = mOverflowPanelSize.getWidth();
+ final int targetHeight = mOverflowPanelSize.getHeight();
final int startWidth = mContentContainer.getWidth();
final int startHeight = mContentContainer.getHeight();
final float startY = mContentContainer.getY();
@@ -714,230 +730,281 @@
Animation widthAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
- params.width = startWidth + deltaWidth;
- mContentContainer.setLayoutParams(params);
+ setWidth(mContentContainer, startWidth + deltaWidth);
if (isRTL()) {
mContentContainer.setX(left);
+
+ // Lock the panels in place.
+ mMainPanel.setX(0);
+ mOverflowPanel.setX(0);
} else {
mContentContainer.setX(right - mContentContainer.getWidth());
+
+ // Offset the panels' positions so they look like they're locked in place
+ // on the screen.
+ mMainPanel.setX(mContentContainer.getWidth() - startWidth);
+ mOverflowPanel.setX(mContentContainer.getWidth() - targetWidth);
}
}
};
Animation heightAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
- params.height = startHeight + deltaHeight;
- mContentContainer.setLayoutParams(params);
- if (morphUpwards) {
- float y = startY - (mContentContainer.getHeight() - startHeight);
- mContentContainer.setY(y);
+ setHeight(mContentContainer, startHeight + deltaHeight);
+ if (mOpenOverflowUpwards) {
+ mContentContainer.setY(
+ startY - (mContentContainer.getHeight() - startHeight));
+ positionContentYCoordinatesIfOpeningOverflowUpwards();
}
}
};
- widthAnimation.setDuration(240);
- heightAnimation.setDuration(180);
- heightAnimation.setStartOffset(60);
+ final float overflowButtonStartX = mOverflowButton.getX();
+ final float overflowButtonTargetX = isRTL() ?
+ overflowButtonStartX + targetWidth - mOverflowButton.getWidth() :
+ overflowButtonStartX - targetWidth + mOverflowButton.getWidth();
+ Animation overflowButtonAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float overflowButtonX = overflowButtonStartX
+ + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
+ float deltaContainerWidth = isRTL() ?
+ 0 :
+ mContentContainer.getWidth() - startWidth;
+ float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
+ mOverflowButton.setX(actualOverflowButtonX);
+ }
+ };
+ widthAnimation.setInterpolator(mLogAccelerateInterpolator);
+ widthAnimation.setDuration(getAdjustedDuration(250));
+ heightAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ heightAnimation.setDuration(getAdjustedDuration(250));
+ overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ overflowButtonAnimation.setDuration(getAdjustedDuration(250));
mOpenOverflowAnimation.getAnimations().clear();
- mOpenOverflowAnimation.setAnimationListener(mOnOverflowOpened);
+ mOpenOverflowAnimation.getAnimations().clear();
mOpenOverflowAnimation.addAnimation(widthAnimation);
mOpenOverflowAnimation.addAnimation(heightAnimation);
+ mOpenOverflowAnimation.addAnimation(overflowButtonAnimation);
mContentContainer.startAnimation(mOpenOverflowAnimation);
+ mIsOverflowOpen = true;
+ mMainPanel.animate()
+ .alpha(0).withLayer()
+ .setInterpolator(mLinearOutSlowInInterpolator)
+ .setDuration(250)
+ .start();
+ mOverflowPanel.setAlpha(1); // fadeIn in 0ms.
}
- /**
- * Opens the floating toolbar overflow.
- * This method should not be called if menu items have not been laid out with
- * {@link #layoutMenuItems(java.util.List, MenuItem.OnMenuItemClickListener, int)}.
- *
- * @throws IllegalStateException if called when menu items have not been laid out.
- */
private void closeOverflow() {
- Preconditions.checkState(mMainPanel != null);
- Preconditions.checkState(mOverflowPanel != null);
-
- mOverflowPanel.fadeOut(true);
- Size mainPanelSize = mMainPanel.measure();
- final int targetWidth = mainPanelSize.getWidth();
- final int targetHeight = mainPanelSize.getHeight();
+ final int targetWidth = mMainPanelSize.getWidth();
final int startWidth = mContentContainer.getWidth();
- final int startHeight = mContentContainer.getHeight();
- final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
- final boolean morphedUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
final float left = mContentContainer.getX();
final float right = left + mContentContainer.getWidth();
Animation widthAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
- params.width = startWidth + deltaWidth;
- mContentContainer.setLayoutParams(params);
+ setWidth(mContentContainer, startWidth + deltaWidth);
if (isRTL()) {
mContentContainer.setX(left);
+
+ // Lock the panels in place.
+ mMainPanel.setX(0);
+ mOverflowPanel.setX(0);
} else {
mContentContainer.setX(right - mContentContainer.getWidth());
+
+ // Offset the panels' positions so they look like they're locked in place
+ // on the screen.
+ mMainPanel.setX(mContentContainer.getWidth() - targetWidth);
+ mOverflowPanel.setX(mContentContainer.getWidth() - startWidth);
}
}
};
+ final int targetHeight = mMainPanelSize.getHeight();
+ final int startHeight = mContentContainer.getHeight();
+ final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
Animation heightAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
- params.height = startHeight + deltaHeight;
- mContentContainer.setLayoutParams(params);
- if (morphedUpwards) {
+ setHeight(mContentContainer, startHeight + deltaHeight);
+ if (mOpenOverflowUpwards) {
mContentContainer.setY(bottom - mContentContainer.getHeight());
+ positionContentYCoordinatesIfOpeningOverflowUpwards();
}
}
};
- widthAnimation.setDuration(150);
- widthAnimation.setStartOffset(150);
- heightAnimation.setDuration(210);
+ final float overflowButtonStartX = mOverflowButton.getX();
+ final float overflowButtonTargetX = isRTL() ?
+ overflowButtonStartX - startWidth + mOverflowButton.getWidth() :
+ overflowButtonStartX + startWidth - mOverflowButton.getWidth();
+ Animation overflowButtonAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float overflowButtonX = overflowButtonStartX
+ + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
+ float deltaContainerWidth = isRTL() ?
+ 0 :
+ mContentContainer.getWidth() - startWidth;
+ float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
+ mOverflowButton.setX(actualOverflowButtonX);
+ }
+ };
+ widthAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ widthAnimation.setDuration(getAdjustedDuration(250));
+ heightAnimation.setInterpolator(mLogAccelerateInterpolator);
+ heightAnimation.setDuration(getAdjustedDuration(250));
+ overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ overflowButtonAnimation.setDuration(getAdjustedDuration(250));
mCloseOverflowAnimation.getAnimations().clear();
- mCloseOverflowAnimation.setAnimationListener(mOnOverflowClosed);
mCloseOverflowAnimation.addAnimation(widthAnimation);
mCloseOverflowAnimation.addAnimation(heightAnimation);
+ mCloseOverflowAnimation.addAnimation(overflowButtonAnimation);
mContentContainer.startAnimation(mCloseOverflowAnimation);
+ mIsOverflowOpen = false;
+ mMainPanel.animate()
+ .alpha(1).withLayer()
+ .setInterpolator(mFastOutLinearInInterpolator)
+ .setDuration(100)
+ .start();
+ mOverflowPanel.animate()
+ .alpha(0).withLayer()
+ .setInterpolator(mLinearOutSlowInInterpolator)
+ .setDuration(150)
+ .start();
}
- /**
- * Prepares the content container for show and update calls.
- */
- private void preparePopupContent() {
- // Reset visibility.
- if (mMainPanel != null) {
- mMainPanel.fadeIn(false);
- }
- if (mOverflowPanel != null) {
- mOverflowPanel.fadeIn(false);
- }
+ private void setPanelsStatesAtRestingPosition() {
+ mOverflowButton.setEnabled(true);
- // Reset position.
- if (isMainPanelContent()) {
- positionMainPanel();
- }
- if (isOverflowPanelContent()) {
- positionOverflowPanel();
- }
- }
+ if (mIsOverflowOpen) {
+ // Set open state.
+ final Size containerSize = mOverflowPanelSize;
+ setSize(mContentContainer, containerSize);
+ mMainPanel.setAlpha(0);
+ mOverflowPanel.setAlpha(1);
+ mOverflowButton.setImageDrawable(mArrow);
- private boolean isMainPanelContent() {
- return mMainPanel != null
- && mContentContainer.getChildAt(0) == mMainPanel.getView();
- }
+ // Update x-coordinates depending on RTL state.
+ if (isRTL()) {
+ mContentContainer.setX(mMarginHorizontal); // align left
+ mMainPanel.setX(0); // align left
+ mOverflowButton.setX( // align right
+ containerSize.getWidth() - mOverflowButtonSize.getWidth());
+ mOverflowPanel.setX(0); // align left
+ } else {
+ mContentContainer.setX( // align right
+ mMarginHorizontal +
+ mMainPanelSize.getWidth() - containerSize.getWidth());
+ mMainPanel.setX(-mContentContainer.getX()); // align right
+ mOverflowButton.setX(0); // align left
+ mOverflowPanel.setX(0); // align left
+ }
- private boolean isOverflowPanelContent() {
- return mOverflowPanel != null
- && mContentContainer.getChildAt(0) == mOverflowPanel.getView();
- }
-
- /**
- * Sets the current content to be the main view panel.
- */
- private void setMainPanelAsContent() {
- // This should never be called if the main panel has not been initialized.
- Preconditions.checkNotNull(mMainPanel);
- mContentContainer.removeAllViews();
- Size mainPanelSize = mMainPanel.measure();
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
- params.width = mainPanelSize.getWidth();
- params.height = mainPanelSize.getHeight();
- mContentContainer.setLayoutParams(params);
- mContentContainer.addView(mMainPanel.getView());
- setContentAreaAsTouchableSurface();
- }
-
- /**
- * Sets the current content to be the overflow view panel.
- */
- private void setOverflowPanelAsContent() {
- // This should never be called if the overflow panel has not been initialized.
- Preconditions.checkNotNull(mOverflowPanel);
- mContentContainer.removeAllViews();
- Size overflowPanelSize = mOverflowPanel.measure();
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
- params.width = overflowPanelSize.getWidth();
- params.height = overflowPanelSize.getHeight();
- mContentContainer.setLayoutParams(params);
- mContentContainer.addView(mOverflowPanel.getView());
- setContentAreaAsTouchableSurface();
- }
-
- /**
- * Places the main view panel at the appropriate resting coordinates.
- */
- private void positionMainPanel() {
- Preconditions.checkNotNull(mMainPanel);
- mContentContainer.setX(mMarginHorizontal);
-
- float y = mMarginVertical;
- if (mOverflowDirection == OVERFLOW_DIRECTION_UP) {
- y = getHeight()
- - (mMainPanel.getView().getMeasuredHeight() + mMarginVertical);
- }
- mContentContainer.setY(y);
- setContentAreaAsTouchableSurface();
- }
-
- /**
- * Places the main view panel at the appropriate resting coordinates.
- */
- private void positionOverflowPanel() {
- Preconditions.checkNotNull(mOverflowPanel);
- float x;
- if (isRTL()) {
- x = mMarginHorizontal;
+ // Update y-coordinates depending on overflow's open direction.
+ if (mOpenOverflowUpwards) {
+ mContentContainer.setY(mMarginVertical); // align top
+ mMainPanel.setY( // align bottom
+ containerSize.getHeight() - mContentContainer.getHeight());
+ mOverflowButton.setY( // align bottom
+ containerSize.getHeight() - mOverflowButtonSize.getHeight());
+ mOverflowPanel.setY(0); // align top
+ } else {
+ // opens downwards.
+ mContentContainer.setY(mMarginVertical); // align top
+ mMainPanel.setY(0); // align top
+ mOverflowButton.setY(0); // align top
+ mOverflowPanel.setY(mOverflowButtonSize.getHeight()); // align bottom
+ }
} else {
- x = mPopupWindow.getWidth()
- - (mOverflowPanel.getView().getMeasuredWidth() + mMarginHorizontal);
+ if (hasOverflow()) {
+ // overflow not open. Set closed state.
+ final Size containerSize = mMainPanelSize;
+ setSize(mContentContainer, containerSize);
+ mMainPanel.setAlpha(1);
+ mOverflowPanel.setAlpha(0);
+ mOverflowButton.setImageDrawable(mOverflow);
+
+ // Update x-coordinates depending on RTL state.
+ if (isRTL()) {
+ mContentContainer.setX(mMarginHorizontal); // align left
+ mMainPanel.setX(0); // align left
+ mOverflowButton.setX(0); // align left
+ mOverflowPanel.setX(0); // align left
+ } else {
+ mContentContainer.setX(mMarginHorizontal); // align left
+ mMainPanel.setX(0); // align left
+ mOverflowButton.setX( // align right
+ containerSize.getWidth() - mOverflowButtonSize.getWidth());
+ mOverflowPanel.setX( // align right
+ containerSize.getWidth() - mOverflowPanelSize.getWidth());
+ }
+
+ // Update y-coordinates depending on overflow's open direction.
+ if (mOpenOverflowUpwards) {
+ mContentContainer.setY( // align bottom
+ mMarginVertical +
+ mOverflowPanelSize.getHeight() - containerSize.getHeight());
+ mMainPanel.setY(0); // align top
+ mOverflowButton.setY(0); // align top
+ mOverflowPanel.setY( // align bottom
+ containerSize.getHeight() - mOverflowPanelSize.getHeight());
+ } else {
+ // opens downwards.
+ mContentContainer.setY(mMarginVertical); // align top
+ mMainPanel.setY(0); // align top
+ mOverflowButton.setY(0); // align top
+ mOverflowPanel.setY(mOverflowButtonSize.getHeight()); // align bottom
+ }
+ } else {
+ mContentContainer.setX(mMarginHorizontal);
+ mContentContainer.setY(mMarginVertical);
+ }
}
- mContentContainer.setX(x);
- mContentContainer.setY(mMarginVertical);
- setContentAreaAsTouchableSurface();
}
- private void updateOverflowHeight(int height) {
- if (mOverflowPanel != null) {
- mOverflowPanel.setSuggestedHeight(height);
-
- // Re-measure the popup and it's contents.
- boolean mainPanelContent = isMainPanelContent();
- boolean overflowPanelContent = isOverflowPanelContent();
- mContentContainer.removeAllViews(); // required to update popup size.
+ private void updateOverflowHeight(int suggestedHeight) {
+ if (hasOverflow()) {
+ final int maxItemSize = (suggestedHeight - mOverflowButtonSize.getHeight()) /
+ getLineHeight(mContext);
+ final int newHeight = calculateOverflowHeight(maxItemSize);
+ if (mOverflowPanelSize.getHeight() != newHeight) {
+ mOverflowPanelSize = new Size(mOverflowPanelSize.getWidth(), newHeight);
+ }
+ setSize(mOverflowPanel, mOverflowPanelSize);
+ if (mIsOverflowOpen) {
+ setSize(mContentContainer, mOverflowPanelSize);
+ if (mOpenOverflowUpwards) {
+ final int deltaHeight = mOverflowPanelSize.getHeight() - newHeight;
+ mContentContainer.setY(mContentContainer.getY() + deltaHeight);
+ mOverflowButton.setY(mOverflowButton.getY() - deltaHeight);
+ }
+ } else {
+ setSize(mContentContainer, mMainPanelSize);
+ }
updatePopupSize();
- // Reset the appropriate content.
- if (mainPanelContent) {
- setMainPanelAsContent();
- }
- if (overflowPanelContent) {
- setOverflowPanelAsContent();
- }
}
}
private void updatePopupSize() {
int width = 0;
int height = 0;
- if (mMainPanel != null) {
- Size mainPanelSize = mMainPanel.measure();
- width = mainPanelSize.getWidth();
- height = mainPanelSize.getHeight();
+ if (mMainPanelSize != null) {
+ width = Math.max(width, mMainPanelSize.getWidth());
+ height = Math.max(height, mMainPanelSize.getHeight());
}
- if (mOverflowPanel != null) {
- Size overflowPanelSize = mOverflowPanel.measure();
- width = Math.max(width, overflowPanelSize.getWidth());
- height = Math.max(height, overflowPanelSize.getHeight());
+ if (mOverflowPanelSize != null) {
+ width = Math.max(width, mOverflowPanelSize.getWidth());
+ height = Math.max(height, mOverflowPanelSize.getHeight());
}
mPopupWindow.setWidth(width + mMarginHorizontal * 2);
mPopupWindow.setHeight(height + mMarginVertical * 2);
+ maybeComputeTransitionDurationScale();
}
-
private void refreshViewPort() {
mParent.getWindowVisibleDisplayFrame(mViewPortOnScreen);
}
@@ -947,7 +1014,7 @@
return !mTmpRect.equals(mViewPortOnScreen);
}
- private int getToolbarWidth(int suggestedWidth) {
+ private int getAdjustedToolbarWidth(int suggestedWidth) {
int width = suggestedWidth;
refreshViewPort();
int maximumWidth = mViewPortOnScreen.width() - 2 * mParent.getResources()
@@ -971,11 +1038,17 @@
* Sets the touchable region of this popup to be the area occupied by its content.
*/
private void setContentAreaAsTouchableSurface() {
- if (!mPopupWindow.isShowing()) {
- mContentContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ Preconditions.checkNotNull(mMainPanelSize);
+ final int width;
+ final int height;
+ if (mIsOverflowOpen) {
+ Preconditions.checkNotNull(mOverflowPanelSize);
+ width = mOverflowPanelSize.getWidth();
+ height = mOverflowPanelSize.getHeight();
+ } else {
+ width = mMainPanelSize.getWidth();
+ height = mMainPanelSize.getHeight();
}
- int width = mContentContainer.getMeasuredWidth();
- int height = mContentContainer.getMeasuredHeight();
mTouchableRegion.set(
(int) mContentContainer.getX(),
(int) mContentContainer.getY(),
@@ -997,45 +1070,12 @@
}
private boolean isRTL() {
- return mContentContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ return mContext.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL;
}
- }
- /**
- * A widget that holds the primary menu items in the floating toolbar.
- */
- private static final class FloatingToolbarMainPanel {
-
- private final Context mContext;
- private final ViewGroup mContentView;
- private final View.OnClickListener mMenuItemButtonOnClickListener =
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (v.getTag() instanceof MenuItem) {
- if (mOnMenuItemClickListener != null) {
- mOnMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
- }
- }
- }
- };
- private final ViewFader viewFader;
- private final Runnable mOpenOverflow;
-
- private View mOpenOverflowButton;
- private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
-
- /**
- * Initializes a floating toolbar popup main view panel.
- *
- * @param context
- * @param openOverflow The code that opens the toolbar popup overflow.
- */
- public FloatingToolbarMainPanel(Context context, Runnable openOverflow) {
- mContext = Preconditions.checkNotNull(context);
- mContentView = new LinearLayout(context);
- viewFader = new ViewFader(mContentView);
- mOpenOverflow = Preconditions.checkNotNull(openOverflow);
+ private boolean hasOverflow() {
+ return mOverflowPanelSize != null;
}
/**
@@ -1044,16 +1084,14 @@
*
* @return The menu items that are not included in this main panel.
*/
- public List<MenuItem> layoutMenuItems(List<MenuItem> menuItems, int width) {
+ public List<MenuItem> layoutMainPanelItems(
+ List<MenuItem> menuItems, final int toolbarWidth) {
Preconditions.checkNotNull(menuItems);
- // Reserve space for the "open overflow" button.
- final int toolbarWidth = width - getEstimatedOpenOverflowButtonWidth(mContext);
-
int availableWidth = toolbarWidth;
final LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems);
- mContentView.removeAllViews();
+ mMainPanel.removeAllViews();
boolean isFirstItem = true;
while (!remainingMenuItems.isEmpty()) {
@@ -1081,59 +1119,119 @@
menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
- if (menuItemButtonWidth <= availableWidth) {
+ // Check if we can fit an item while reserving space for the overflowButton.
+ boolean canFitWithOverflow =
+ menuItemButtonWidth <= availableWidth - mOverflowButtonSize.getWidth();
+ boolean canFitNoOverflow =
+ remainingMenuItems.size() == 1 && menuItemButtonWidth <= availableWidth;
+ if (canFitWithOverflow || canFitNoOverflow) {
setButtonTagAndClickListener(menuItemButton, menuItem);
- mContentView.addView(menuItemButton);
+ mMainPanel.addView(menuItemButton);
ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
params.width = menuItemButtonWidth;
menuItemButton.setLayoutParams(params);
availableWidth -= menuItemButtonWidth;
remainingMenuItems.pop();
} else {
- if (mOpenOverflowButton == null) {
- mOpenOverflowButton = LayoutInflater.from(mContext)
- .inflate(R.layout.floating_popup_open_overflow_button, null);
- mOpenOverflowButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mOpenOverflowButton != null) {
- mOpenOverflow.run();
- }
- }
- });
- }
- mContentView.addView(mOpenOverflowButton);
+ // Reserve space for overflowButton.
+ mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0);
break;
}
}
+ mMainPanelSize = measure(mMainPanel);
return remainingMenuItems;
}
- public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
- mOnMenuItemClickListener = listener;
- }
+ private void layoutOverflowPanelItems(List<MenuItem> menuItems) {
+ ArrayAdapter<MenuItem> overflowPanelAdapter =
+ (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+ overflowPanelAdapter.clear();
+ final int size = menuItems.size();
+ for (int i = 0; i < size; i++) {
+ overflowPanelAdapter.add(menuItems.get(i));
+ }
+ mOverflowPanel.setAdapter(overflowPanelAdapter);
+ if (mOpenOverflowUpwards) {
+ mOverflowPanel.setY(0);
+ } else {
+ mOverflowPanel.setY(mOverflowButtonSize.getHeight());
+ }
- public View getView() {
- return mContentView;
- }
-
- public void fadeIn(boolean animate) {
- viewFader.fadeIn(animate);
- }
-
- public void fadeOut(boolean animate) {
- viewFader.fadeOut(animate);
+ int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth());
+ int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE);
+ mOverflowPanelSize = new Size(width, height);
+ setSize(mOverflowPanel, mOverflowPanelSize);
}
/**
- * Returns how big this panel's view should be.
- * This method should only be called when the view has not been attached to a parent
- * otherwise it will throw an illegal state.
+ * Resets the content container and appropriately position it's panels.
*/
- public Size measure() throws IllegalStateException {
- Preconditions.checkState(mContentView.getParent() == null);
- mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
+ private void preparePopupContent() {
+ mContentContainer.removeAllViews();
+
+ // Add views in the specified order so they stack up as expected.
+ // Order: overflowPanel, mainPanel, overflowButton.
+ if (hasOverflow()) {
+ mContentContainer.addView(mOverflowPanel);
+ }
+ mContentContainer.addView(mMainPanel);
+ if (hasOverflow()) {
+ mContentContainer.addView(mOverflowButton);
+ }
+ setPanelsStatesAtRestingPosition();
+ setContentAreaAsTouchableSurface();
+
+ // The positioning of contents in RTL is wrong when the view is first rendered.
+ // Hide the view and post a runnable to recalculate positions and render the view.
+ // TODO: Investigate why this happens and fix.
+ if (isRTL()) {
+ mContentContainer.setAlpha(0);
+ mContentContainer.post(mPreparePopupContentRTLHelper);
+ }
+ }
+
+ /**
+ * Clears out the panels and their container. Resets their calculated sizes.
+ */
+ private void clearPanels() {
+ mOverflowPanelSize = null;
+ mMainPanelSize = null;
+ mIsOverflowOpen = false;
+ mMainPanel.removeAllViews();
+ ArrayAdapter<MenuItem> overflowPanelAdapter =
+ (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+ overflowPanelAdapter.clear();
+ mOverflowPanel.setAdapter(overflowPanelAdapter);
+ mContentContainer.removeAllViews();
+ }
+
+ private void positionContentYCoordinatesIfOpeningOverflowUpwards() {
+ if (mOpenOverflowUpwards) {
+ mMainPanel.setY(mContentContainer.getHeight() - mMainPanelSize.getHeight());
+ mOverflowButton.setY(mContentContainer.getHeight() - mOverflowButton.getHeight());
+ mOverflowPanel.setY(mContentContainer.getHeight() - mOverflowPanelSize.getHeight());
+ }
+ }
+
+ private int getOverflowWidth() {
+ int overflowWidth = 0;
+ final int count = mOverflowPanel.getAdapter().getCount();
+ for (int i = 0; i < count; i++) {
+ MenuItem menuItem = (MenuItem) mOverflowPanel.getAdapter().getItem(i);
+ overflowWidth =
+ Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth);
+ }
+ return overflowWidth;
+ }
+
+ private int calculateOverflowHeight(int maxItemSize) {
+ // Maximum of 4 items, minimum of 2 if the overflow has to scroll.
+ int actualSize = Math.min(
+ MAX_OVERFLOW_SIZE,
+ Math.min(
+ Math.max(MIN_OVERFLOW_SIZE, maxItemSize),
+ mOverflowPanel.getCount()));
+ return actualSize * getLineHeight(mContext) + mOverflowButtonSize.getHeight();
}
private void setButtonTagAndClickListener(View menuItemButton, MenuItem menuItem) {
@@ -1144,281 +1242,326 @@
button.setTag(menuItem);
button.setOnClickListener(mMenuItemButtonOnClickListener);
}
- }
-
-
- /**
- * A widget that holds the overflow items in the floating toolbar.
- */
- private static final class FloatingToolbarOverflowPanel {
-
- private final LinearLayout mContentView;
- private final ViewGroup mBackButtonContainer;
- private final View mBackButton;
- private final ListView mListView;
- private final TextView mListViewItemWidthCalculator;
- private final ViewFader mViewFader;
- private final Runnable mCloseOverflow;
-
- private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
- private int mOverflowWidth;
- private int mSuggestedHeight;
/**
- * Initializes a floating toolbar popup overflow view panel.
- *
- * @param context
- * @param closeOverflow The code that closes the toolbar popup's overflow.
+ * NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.*
+ * animations. See comment about this in the code.
*/
- public FloatingToolbarOverflowPanel(Context context, Runnable closeOverflow) {
- mCloseOverflow = Preconditions.checkNotNull(closeOverflow);
+ private int getAdjustedDuration(int originalDuration) {
+ if (mTransitionDurationScale < 150) {
+ // For smaller transition, decrease the time.
+ return Math.max(originalDuration - 50, 0);
+ } else if (mTransitionDurationScale > 300) {
+ // For bigger transition, increase the time.
+ return originalDuration + 50;
+ }
- mContentView = new LinearLayout(context);
- mContentView.setOrientation(LinearLayout.VERTICAL);
- mViewFader = new ViewFader(mContentView);
+ // Scale the animation duration with getDurationScale(). This allows
+ // android.view.animation.* animations to scale just like android.animation.* animations
+ // when animator duration scale is adjusted in "Developer Options".
+ // For this reason, do not use this method for android.animation.* animations.
+ return (int) (originalDuration * ValueAnimator.getDurationScale());
+ }
- mBackButton = LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_close_overflow_button, null);
- mBackButton.setOnClickListener(new View.OnClickListener() {
+ private void maybeComputeTransitionDurationScale() {
+ if (mMainPanelSize == null || mOverflowPanel == null) {
+ int w = mMainPanelSize.getWidth() - mOverflowPanelSize.getWidth();
+ int h = mOverflowPanelSize.getHeight() - mMainPanelSize.getHeight();
+ mTransitionDurationScale = (int) (Math.sqrt(w * w + h * h) /
+ mContentContainer.getContext().getResources().getDisplayMetrics().density);
+ }
+ }
+
+ private ViewGroup createMainPanel() {
+ ViewGroup mainPanel = new LinearLayout(mContext) {
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (isOverflowAnimating()) {
+ // Update widthMeasureSpec to make sure that this view is not clipped
+ // as we offset it's coordinates with respect to it's parent.
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ mMainPanelSize.getWidth(),
+ MeasureSpec.EXACTLY);
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Intercept the touch event while the overflow is animating.
+ return isOverflowAnimating();
+ }
+ };
+ return mainPanel;
+ }
+
+ private ImageButton createOverflowButton() {
+ final ImageButton overflowButton = (ImageButton) LayoutInflater.from(mContext)
+ .inflate(R.layout.floating_popup_overflow_button, null);
+ overflowButton.setImageDrawable(mOverflow);
+ overflowButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- mCloseOverflow.run();
+ final Drawable drawable = overflowButton.getDrawable();
+ if (mIsOverflowOpen) {
+ overflowButton.setImageDrawable(mToOverflow);
+ mToOverflow.start();
+ closeOverflow();
+ } else {
+ overflowButton.setImageDrawable(mToArrow);
+ mToArrow.start();
+ openOverflow();
+ }
+ overflowButton.postDelayed(
+ mResetOverflowButtonDrawable, OVERFLOW_BUTTON_ANIMATION_DELAY);
}
});
- mBackButtonContainer = new LinearLayout(context);
- mBackButtonContainer.addView(mBackButton);
+ return overflowButton;
+ }
- mListView = createOverflowListView();
- mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ private ListView createOverflowPanel() {
+ final ListView overflowPanel = new ListView(FloatingToolbarPopup.this.mContext) {
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Update heightMeasureSpec to make sure that this view is not clipped
+ // as we offset it's coordinates with respect to it's parent.
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ mOverflowPanelSize.getHeight() - mOverflowButtonSize.getHeight(),
+ MeasureSpec.EXACTLY);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (isOverflowAnimating()) {
+ // Eat the touch event.
+ return true;
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+ };
+ overflowPanel.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ overflowPanel.setDivider(null);
+ overflowPanel.setDividerHeight(0);
+
+ final ArrayAdapter adapter =
+ new ArrayAdapter<MenuItem>(mContext, 0) {
+ @Override
+ public int getViewTypeCount() {
+ return mOverflowPanelViewHelper.getViewTypeCount();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mOverflowPanelViewHelper.getItemViewType(getItem(position));
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return mOverflowPanelViewHelper.getView(
+ getItem(position), mOverflowPanelSize.getWidth(), convertView);
+ }
+ };
+ overflowPanel.setAdapter(adapter);
+
+ overflowPanel.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- MenuItem menuItem = (MenuItem) mListView.getAdapter().getItem(position);
+ MenuItem menuItem = (MenuItem) overflowPanel.getAdapter().getItem(position);
if (mOnMenuItemClickListener != null) {
mOnMenuItemClickListener.onMenuItemClick(menuItem);
}
}
});
- mContentView.addView(mListView);
- mContentView.addView(mBackButtonContainer);
+ return overflowPanel;
+ }
- mListViewItemWidthCalculator = createOverflowMenuItemButton(context);
- mListViewItemWidthCalculator.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ private boolean isOverflowAnimating() {
+ final boolean overflowOpening = mOpenOverflowAnimation.hasStarted()
+ && !mOpenOverflowAnimation.hasEnded();
+ final boolean overflowClosing = mCloseOverflowAnimation.hasStarted()
+ && !mCloseOverflowAnimation.hasEnded();
+ return overflowOpening || overflowClosing;
+ }
+
+ private Animation.AnimationListener createOverflowAnimationListener() {
+ Animation.AnimationListener listener = new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ // Disable the overflow button while it's animating.
+ // It will be re-enabled when the animation stops.
+ mOverflowButton.setEnabled(false);
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // Posting this because it seems like this is called before the animation
+ // actually ends.
+ mContentContainer.post(new Runnable() {
+ @Override
+ public void run() {
+ setPanelsStatesAtRestingPosition();
+ setContentAreaAsTouchableSurface();
+ }
+ });
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ };
+ return listener;
+ }
+
+ private static Size measure(View view) {
+ Preconditions.checkState(view.getParent() == null);
+ view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ return new Size(view.getMeasuredWidth(), view.getMeasuredHeight());
+ }
+
+ private static void setSize(View view, int width, int height) {
+ view.setMinimumWidth(width);
+ view.setMinimumHeight(height);
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ params = (params == null) ? new ViewGroup.LayoutParams(0, 0) : params;
+ params.width = width;
+ params.height = height;
+ view.setLayoutParams(params);
+ }
+
+ private static void setSize(View view, Size size) {
+ setSize(view, size.getWidth(), size.getHeight());
+ }
+
+ private static void setWidth(View view, int width) {
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ setSize(view, width, params.height);
+ }
+
+ private static void setHeight(View view, int height) {
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ setSize(view, params.width, height);
+ }
+
+ private static int getLineHeight(Context context) {
+ return context.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_height);
}
/**
- * Sets the menu items to be displayed in the overflow.
+ * A custom interpolator used for various floating toolbar animations.
*/
- public void setMenuItems(List<MenuItem> menuItems) {
- ArrayAdapter overflowListViewAdapter = (ArrayAdapter) mListView.getAdapter();
- overflowListViewAdapter.clear();
- overflowListViewAdapter.addAll(menuItems);
- setListViewHeight();
- setOverflowWidth();
- }
+ private static final class LogAccelerateInterpolator implements Interpolator {
- public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
- mOnMenuItemClickListener = listener;
- }
+ private static final int BASE = 100;
+ private static final float LOGS_SCALE = 1f / computeLog(1, BASE);
- /**
- * Notifies the overflow of the current direction in which the overflow will be opened.
- *
- * @param overflowDirection {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_UP}
- * or {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_DOWN}.
- */
- public void setOverflowDirection(int overflowDirection) {
- mContentView.removeView(mBackButtonContainer);
- int index = (overflowDirection == FloatingToolbarPopup.OVERFLOW_DIRECTION_UP)? 1 : 0;
- mContentView.addView(mBackButtonContainer, index);
- }
-
- public void setSuggestedHeight(int height) {
- mSuggestedHeight = height;
- setListViewHeight();
- }
-
- public int getMinimumHeight() {
- return mContentView.getContext().getResources().
- getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height)
- + getEstimatedToolbarHeight(mContentView.getContext());
- }
-
- /**
- * Returns the content view of the overflow.
- */
- public View getView() {
- return mContentView;
- }
-
- public void fadeIn(boolean animate) {
- mViewFader.fadeIn(animate);
- }
-
- public void fadeOut(boolean animate) {
- mViewFader.fadeOut(animate);
- }
-
- /**
- * Returns how big this panel's view should be.
- * This method should only be called when the view has not been attached to a parent.
- *
- * @throws IllegalStateException
- */
- public Size measure() {
- Preconditions.checkState(mContentView.getParent() == null);
- mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
- }
-
- private void setListViewHeight() {
- int itemHeight = getEstimatedToolbarHeight(mContentView.getContext());
- int height = mListView.getAdapter().getCount() * itemHeight;
- int maxHeight = mContentView.getContext().getResources().
- getDimensionPixelSize(R.dimen.floating_toolbar_maximum_overflow_height);
- int minHeight = mContentView.getContext().getResources().
- getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height);
- int suggestedListViewHeight = mSuggestedHeight - (mSuggestedHeight % itemHeight)
- - itemHeight; // reserve space for the back button.
- ViewGroup.LayoutParams params = mListView.getLayoutParams();
- if (suggestedListViewHeight <= 0) {
- // Invalid height. Use the maximum height available.
- params.height = Math.min(maxHeight, height);
- } else if (suggestedListViewHeight < minHeight) {
- // Height is smaller than minimum allowed. Use minimum height.
- params.height = minHeight;
- } else {
- // Use the suggested height. Cap it at the maximum available height.
- params.height = Math.min(Math.min(suggestedListViewHeight, maxHeight), height);
+ private static float computeLog(float t, int base) {
+ return (float) (1 - Math.pow(base, -t));
}
- mListView.setLayoutParams(params);
+
+ @Override
+ public float getInterpolation(float t) {
+ return 1 - computeLog(1 - t, BASE) * LOGS_SCALE;
+ }
}
- private void setOverflowWidth() {
- mOverflowWidth = 0;
- for (int i = 0; i < mListView.getAdapter().getCount(); i++) {
- MenuItem menuItem = (MenuItem) mListView.getAdapter().getItem(i);
+ /**
+ * A helper for generating views for the overflow panel.
+ */
+ private static final class OverflowPanelViewHelper {
+
+ private static final int NUM_OF_VIEW_TYPES = 2;
+ private static final int VIEW_TYPE_STRING_TITLE = 0;
+ private static final int VIEW_TYPE_ICON_ONLY = 1;
+
+ private final TextView mStringTitleViewCalculator;
+ private final View mIconOnlyViewCalculator;
+
+ private final Context mContext;
+
+ public OverflowPanelViewHelper(Context context) {
+ mContext = Preconditions.checkNotNull(context);
+ mStringTitleViewCalculator = getStringTitleView(null, 0, null);
+ mIconOnlyViewCalculator = getIconOnlyView(null, 0, null);
+ }
+
+ public int getViewTypeCount() {
+ return NUM_OF_VIEW_TYPES;
+ }
+
+ public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
Preconditions.checkNotNull(menuItem);
- mListViewItemWidthCalculator.setText(menuItem.getTitle());
- mListViewItemWidthCalculator.measure(
- MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- mOverflowWidth = Math.max(
- mListViewItemWidthCalculator.getMeasuredWidth(), mOverflowWidth);
+ if (getItemViewType(menuItem) == VIEW_TYPE_ICON_ONLY) {
+ return getIconOnlyView(menuItem, minimumWidth, convertView);
+ }
+ return getStringTitleView(menuItem, minimumWidth, convertView);
}
- }
- private ListView createOverflowListView() {
- final Context context = mContentView.getContext();
- final ListView overflowListView = new ListView(context);
- overflowListView.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
- overflowListView.setDivider(null);
- overflowListView.setDividerHeight(0);
-
- final int viewTypeCount = 2;
- final int stringLabelViewType = 0;
- final int iconOnlyViewType = 1;
- final ArrayAdapter overflowListViewAdapter =
- new ArrayAdapter<MenuItem>(context, 0) {
- @Override
- public int getViewTypeCount() {
- return viewTypeCount;
- }
-
- @Override
- public int getItemViewType(int position) {
- if (isIconOnlyMenuItem(getItem(position))) {
- return iconOnlyViewType;
- }
- return stringLabelViewType;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (getItemViewType(position) == iconOnlyViewType) {
- return getIconOnlyView(position, convertView);
- }
- return getStringTitleView(position, convertView);
- }
-
- private View getStringTitleView(int position, View convertView) {
- TextView menuButton;
- if (convertView != null) {
- menuButton = (TextView) convertView;
- } else {
- menuButton = createOverflowMenuItemButton(context);
- }
- MenuItem menuItem = getItem(position);
- menuButton.setText(menuItem.getTitle());
- menuButton.setContentDescription(menuItem.getTitle());
- menuButton.setMinimumWidth(mOverflowWidth);
- return menuButton;
- }
-
- private View getIconOnlyView(int position, View convertView) {
- View menuButton;
- if (convertView != null) {
- menuButton = convertView;
- } else {
- menuButton = LayoutInflater.from(context).inflate(
- R.layout.floating_popup_overflow_image_list_item, null);
- }
- MenuItem menuItem = getItem(position);
- ((ImageView) menuButton
- .findViewById(R.id.floating_toolbar_menu_item_image_button))
- .setImageDrawable(menuItem.getIcon());
- menuButton.setMinimumWidth(mOverflowWidth);
- return menuButton;
- }
- };
- overflowListView.setAdapter(overflowListViewAdapter);
- return overflowListView;
- }
- }
-
-
- /**
- * A helper for fading in or out a view.
- */
- private static final class ViewFader {
-
- private static final int FADE_OUT_DURATION = 250;
- private static final int FADE_IN_DURATION = 150;
-
- private final View mView;
- private final ObjectAnimator mFadeOutAnimation;
- private final ObjectAnimator mFadeInAnimation;
-
- private ViewFader(View view) {
- mView = Preconditions.checkNotNull(view);
- mFadeOutAnimation = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0)
- .setDuration(FADE_OUT_DURATION);
- mFadeInAnimation = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1)
- .setDuration(FADE_IN_DURATION);
- }
-
- public void fadeIn(boolean animate) {
- cancelFadeAnimations();
- if (animate) {
- mFadeInAnimation.start();
- } else {
- mView.setAlpha(1);
+ public int getItemViewType(MenuItem menuItem) {
+ Preconditions.checkNotNull(menuItem);
+ if (isIconOnlyMenuItem(menuItem)) {
+ return VIEW_TYPE_ICON_ONLY;
+ }
+ return VIEW_TYPE_STRING_TITLE;
}
- }
- public void fadeOut(boolean animate) {
- cancelFadeAnimations();
- if (animate) {
- mFadeOutAnimation.start();
- } else {
- mView.setAlpha(0);
+ public int calculateWidth(MenuItem menuItem) {
+ final View calculator;
+ if (isIconOnlyMenuItem(menuItem)) {
+ ((ImageView) mIconOnlyViewCalculator
+ .findViewById(R.id.floating_toolbar_menu_item_image_button))
+ .setImageDrawable(menuItem.getIcon());
+ calculator = mIconOnlyViewCalculator;
+ } else {
+ mStringTitleViewCalculator.setText(menuItem.getTitle());
+ calculator = mStringTitleViewCalculator;
+ }
+ calculator.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ return calculator.getMeasuredWidth();
}
- }
- private void cancelFadeAnimations() {
- mFadeInAnimation.cancel();
- mFadeOutAnimation.cancel();
+ private TextView getStringTitleView(
+ MenuItem menuItem, int minimumWidth, View convertView) {
+ TextView menuButton;
+ if (convertView != null) {
+ menuButton = (TextView) convertView;
+ } else {
+ menuButton = (TextView) LayoutInflater.from(mContext)
+ .inflate(R.layout.floating_popup_overflow_list_item, null);
+ menuButton.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ }
+ if (menuItem != null) {
+ menuButton.setText(menuItem.getTitle());
+ menuButton.setContentDescription(menuItem.getTitle());
+ menuButton.setMinimumWidth(minimumWidth);
+ }
+ return menuButton;
+ }
+
+ private View getIconOnlyView(
+ MenuItem menuItem, int minimumWidth, View convertView) {
+ View menuButton;
+ if (convertView != null) {
+ menuButton = convertView;
+ } else {
+ menuButton = LayoutInflater.from(mContext).inflate(
+ R.layout.floating_popup_overflow_image_list_item, null);
+ menuButton.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ }
+ if (menuItem != null) {
+ ((ImageView) menuButton
+ .findViewById(R.id.floating_toolbar_menu_item_image_button))
+ .setImageDrawable(menuItem.getIcon());
+ menuButton.setMinimumWidth(minimumWidth);
+ }
+ return menuButton;
+ }
}
}
@@ -1453,14 +1596,6 @@
return menuItemButton;
}
- /**
- * Creates and returns a styled floating toolbar overflow list view item.
- */
- private static TextView createOverflowMenuItemButton(Context context) {
- return (TextView) LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_overflow_list_item, null);
- }
-
private static ViewGroup createContentContainer(Context context) {
ViewGroup contentContainer = (ViewGroup) LayoutInflater.from(context)
.inflate(R.layout.floating_popup_container, null);
@@ -1468,7 +1603,7 @@
return contentContainer;
}
- private static PopupWindow createPopupWindow(View content) {
+ private static PopupWindow createPopupWindow(ViewGroup content) {
ViewGroup popupContentHolder = new LinearLayout(content.getContext());
PopupWindow popupWindow = new PopupWindow(popupContentHolder);
// TODO: Use .setLayoutInScreenEnabled(true) instead of .setClippingEnabled(false)
@@ -1490,11 +1625,9 @@
* @param view The view to animate
*/
private static AnimatorSet createEnterAnimation(View view) {
- AnimatorSet animation = new AnimatorSet();
+ AnimatorSet animation = new AnimatorSet();
animation.playTogether(
- ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150),
- // Make sure that view.x is always fixed throughout the duration of this animation.
- ObjectAnimator.ofFloat(view, View.X, view.getX(), view.getX()));
+ ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150));
return animation;
}
@@ -1525,13 +1658,4 @@
a.recycle();
return new ContextThemeWrapper(originalContext, themeId);
}
-
- private static int getEstimatedToolbarHeight(Context context) {
- return context.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_height);
- }
-
- private static int getEstimatedOpenOverflowButtonWidth(Context context) {
- return context.getResources()
- .getDimensionPixelSize(R.dimen.floating_toolbar_menu_button_minimum_width);
- }
-}
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
new file mode 100644
index 0000000..c4ed2e1
--- /dev/null
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -0,0 +1,89 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.text.BoringLayout;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+/**
+ * A TextView that can float around an image on the end.
+ *
+ * @hide
+ */
+@RemoteViews.RemoteView
+public class ImageFloatingTextView extends TextView {
+
+ private boolean mHasImage;
+
+ public ImageFloatingTextView(Context context) {
+ this(context, null);
+ }
+
+ public ImageFloatingTextView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ImageFloatingTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
+ Layout.Alignment alignment, boolean shouldEllipsize,
+ TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
+ CharSequence text = getText() == null ? "" : getText();
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
+ getPaint(), wantWidth)
+ .setAlignment(alignment)
+ .setTextDirection(getTextDirectionHeuristic())
+ .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
+ .setIncludePad(getIncludeFontPadding())
+ .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
+ // we set the endmargin on the first 2 lines. this works just in our case but that's
+ // sufficient for now.
+ int endMargin = (int) (getResources().getDisplayMetrics().density * 52);
+ int[] margins = mHasImage ? new int[] {endMargin, endMargin, 0} : null;
+ if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ builder.setIndents(margins, null);
+ } else {
+ builder.setIndents(null, margins);
+ }
+
+ return builder.build();
+ }
+
+ @RemotableViewMethod
+ public void setHasImage(boolean hasImage) {
+ mHasImage = hasImage;
+ // The new layout will be automatically created when the text is
+ // set again by the notification.
+ }
+}
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index e339c44..592576b 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -137,7 +137,7 @@
super(c);
setFocusableInTouchMode(true);
- mIm = (InputManager)c.getSystemService(Context.INPUT_SERVICE);
+ mIm = c.getSystemService(InputManager.class);
mVC = ViewConfiguration.get(c);
mTextPaint = new Paint();
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 4d648ce..0473016f 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -93,6 +93,7 @@
android_util_Process.cpp \
android_util_StringBlock.cpp \
android_util_XmlBlock.cpp \
+ android_util_jar_StrictJarFile.cpp \
android_graphics_Canvas.cpp \
android_graphics_Picture.cpp \
android/graphics/AutoDecodeCancel.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index bd41c5d..f6f45b5 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -176,6 +176,7 @@
extern int register_android_app_ActivityThread(JNIEnv *env);
extern int register_android_app_NativeActivity(JNIEnv *env);
extern int register_android_media_RemoteDisplay(JNIEnv *env);
+extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
extern int register_android_view_InputChannel(JNIEnv* env);
extern int register_android_view_InputDevice(JNIEnv* env);
extern int register_android_view_InputEventReceiver(JNIEnv* env);
@@ -1359,6 +1360,7 @@
REG_JNI(register_android_app_backup_FullBackup),
REG_JNI(register_android_app_ActivityThread),
REG_JNI(register_android_app_NativeActivity),
+ REG_JNI(register_android_util_jar_StrictJarFile),
REG_JNI(register_android_view_InputChannel),
REG_JNI(register_android_view_InputEventReceiver),
REG_JNI(register_android_view_InputEventSender),
@@ -1374,6 +1376,8 @@
REG_JNI(register_android_animation_PropertyValuesHolder),
REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
REG_JNI(register_com_android_internal_net_NetworkStatsFactory),
+
+
};
/*
diff --git a/core/jni/android_util_jar_StrictJarFile.cpp b/core/jni/android_util_jar_StrictJarFile.cpp
new file mode 100644
index 0000000..7f8f708
--- /dev/null
+++ b/core/jni/android_util_jar_StrictJarFile.cpp
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#define LOG_TAG "StrictJarFile"
+
+#include <memory>
+#include <string>
+
+#include "JNIHelp.h"
+#include "JniConstants.h"
+#include "ScopedLocalRef.h"
+#include "ScopedUtfChars.h"
+#include "jni.h"
+#include "ziparchive/zip_archive.h"
+#include "cutils/log.h"
+
+namespace android {
+
+// The method ID for ZipEntry.<init>(String,String,JJJIII[BJJ)
+static jmethodID zipEntryCtor;
+
+static void throwIoException(JNIEnv* env, const int32_t errorCode) {
+ jniThrowException(env, "java/io/IOException", ErrorCodeString(errorCode));
+}
+
+static jobject newZipEntry(JNIEnv* env, const ZipEntry& entry, jstring entryName) {
+ return env->NewObject(JniConstants::zipEntryClass,
+ zipEntryCtor,
+ entryName,
+ NULL, // comment
+ static_cast<jlong>(entry.crc32),
+ static_cast<jlong>(entry.compressed_length),
+ static_cast<jlong>(entry.uncompressed_length),
+ static_cast<jint>(entry.method),
+ static_cast<jint>(0), // time
+ NULL, // byte[] extra
+ static_cast<jlong>(entry.offset));
+}
+
+static jlong StrictJarFile_nativeOpenJarFile(JNIEnv* env, jobject, jstring fileName) {
+ ScopedUtfChars fileChars(env, fileName);
+ if (fileChars.c_str() == NULL) {
+ return static_cast<jlong>(-1);
+ }
+
+ ZipArchiveHandle handle;
+ int32_t error = OpenArchive(fileChars.c_str(), &handle);
+ if (error) {
+ CloseArchive(handle);
+ throwIoException(env, error);
+ return static_cast<jlong>(-1);
+ }
+
+ return reinterpret_cast<jlong>(handle);
+}
+
+class IterationHandle {
+ public:
+ IterationHandle() :
+ cookie_(NULL) {
+ }
+
+ void** CookieAddress() {
+ return &cookie_;
+ }
+
+ ~IterationHandle() {
+ EndIteration(cookie_);
+ }
+
+ private:
+ void* cookie_;
+};
+
+
+static jlong StrictJarFile_nativeStartIteration(JNIEnv* env, jobject, jlong nativeHandle,
+ jstring prefix) {
+ ScopedUtfChars prefixChars(env, prefix);
+ if (prefixChars.c_str() == NULL) {
+ return static_cast<jlong>(-1);
+ }
+
+ IterationHandle* handle = new IterationHandle();
+ int32_t error = 0;
+ if (prefixChars.size() == 0) {
+ error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+ handle->CookieAddress(), NULL, NULL);
+ } else {
+ ZipString entry_name(prefixChars.c_str());
+ error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+ handle->CookieAddress(), &entry_name, NULL);
+ }
+
+ if (error) {
+ throwIoException(env, error);
+ return static_cast<jlong>(-1);
+ }
+
+ return reinterpret_cast<jlong>(handle);
+}
+
+static jobject StrictJarFile_nativeNextEntry(JNIEnv* env, jobject, jlong iterationHandle) {
+ ZipEntry data;
+ ZipString entryName;
+
+ IterationHandle* handle = reinterpret_cast<IterationHandle*>(iterationHandle);
+ const int32_t error = Next(*handle->CookieAddress(), &data, &entryName);
+ if (error) {
+ delete handle;
+ return NULL;
+ }
+
+ std::unique_ptr<char[]> entryNameCString(new char[entryName.name_length + 1]);
+ memcpy(entryNameCString.get(), entryName.name, entryName.name_length);
+ entryNameCString[entryName.name_length] = '\0';
+ ScopedLocalRef<jstring> entryNameString(env, env->NewStringUTF(entryNameCString.get()));
+
+ return newZipEntry(env, data, entryNameString.get());
+}
+
+static jobject StrictJarFile_nativeFindEntry(JNIEnv* env, jobject, jlong nativeHandle,
+ jstring entryName) {
+ ScopedUtfChars entryNameChars(env, entryName);
+ if (entryNameChars.c_str() == NULL) {
+ return NULL;
+ }
+
+ ZipEntry data;
+ const int32_t error = FindEntry(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+ ZipString(entryNameChars.c_str()), &data);
+ if (error) {
+ return NULL;
+ }
+
+ return newZipEntry(env, data, entryName);
+}
+
+static void StrictJarFile_nativeClose(JNIEnv*, jobject, jlong nativeHandle) {
+ CloseArchive(reinterpret_cast<ZipArchiveHandle>(nativeHandle));
+}
+
+static JNINativeMethod gMethods[] = {
+ NATIVE_METHOD(StrictJarFile, nativeOpenJarFile, "(Ljava/lang/String;)J"),
+ NATIVE_METHOD(StrictJarFile, nativeStartIteration, "(JLjava/lang/String;)J"),
+ NATIVE_METHOD(StrictJarFile, nativeNextEntry, "(J)Ljava/util/zip/ZipEntry;"),
+ NATIVE_METHOD(StrictJarFile, nativeFindEntry, "(JLjava/lang/String;)Ljava/util/zip/ZipEntry;"),
+ NATIVE_METHOD(StrictJarFile, nativeClose, "(J)V"),
+};
+
+void register_android_util_jar_StrictJarFile(JNIEnv* env) {
+ jniRegisterNativeMethods(env, "android/util/jar/StrictJarFile", gMethods, NELEM(gMethods));
+
+ zipEntryCtor = env->GetMethodID(JniConstants::zipEntryClass, "<init>",
+ "(Ljava/lang/String;Ljava/lang/String;JJJII[BJ)V");
+ LOG_ALWAYS_FATAL_IF(zipEntryCtor == NULL, "Unable to find ZipEntry.<init>");
+}
+
+}; // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 57338be..568b601 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1877,6 +1877,11 @@
<permission android:name="android.permission.MANAGE_APP_TOKENS"
android:protectionLevel="signature" />
+ <!-- Allows System UI to register listeners for events from Window Manager.
+ @hide -->
+ <permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows the application to temporarily freeze the screen for a
full-screen transition. -->
<permission android:name="android.permission.FREEZE_SCREEN"
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_1_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_1_animation.xml
new file mode 100644
index 0000000..e6a28bb
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_1_animation.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="600"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -6.5,0.0 c 1.08333,-1.0 5.41667,-5.0 6.5,-6.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_4" />
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="rotation"
+ android:valueFrom="-45.0"
+ android:valueTo="-45.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="rotation"
+ android:valueFrom="-45.0"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_0_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_0_animation.xml
new file mode 100644
index 0000000..c2414de
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_0_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="200"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,0.0 c 0.75,0.0 3.75,0.0 4.5,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_5" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_animation.xml
new file mode 100644
index 0000000..593a0ea
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="450"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 4.5,0.0 c -0.75,0.0 -3.75,0.0 -4.5,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_0" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_2_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_2_animation.xml
new file mode 100644
index 0000000..5b09a41
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_2_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="600"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -8.0,0.0 c 1.33333,0.0 6.66667,0.0 8.0,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_3" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_0_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_0_animation.xml
new file mode 100644
index 0000000..dccbc67
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_0_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="250"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,0.0 c 1.66667,0.0 8.5,0.0 10.0,0.0 c 0.13052,0.0 -0.83333,0.0 -1.0,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_2" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_animation.xml
new file mode 100644
index 0000000..c1c0c1c
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="516"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 9.0,0.0 c -1.5,0.0 -7.5,0.0 -9.0,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_6" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_3_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_3_animation.xml
new file mode 100644
index 0000000..df69bf7
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_3_animation.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="600"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -6.5,0.0 c 1.08333,1.0 5.41667,5.0 6.5,6.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_4" />
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="rotation"
+ android:valueFrom="45.0"
+ android:valueTo="45.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="rotation"
+ android:valueFrom="45.0"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_0_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_0_animation.xml
new file mode 100644
index 0000000..c2414de
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_0_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="200"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,0.0 c 0.75,0.0 3.75,0.0 4.5,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_5" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_animation.xml
new file mode 100644
index 0000000..593a0ea
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="450"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 4.5,0.0 c -0.75,0.0 -3.75,0.0 -4.5,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_0" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_4_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_4_animation.xml
new file mode 100644
index 0000000..a375330
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_4_animation.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,6.0 c -1.08333,-1.0 -5.41667,-5.0 -6.5,-6.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="rotation"
+ android:valueFrom="0.0"
+ android:valueTo="45.0"
+ android:valueType="floatType"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_1" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_5_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_5_animation.xml
new file mode 100644
index 0000000..ae7ee47
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_5_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="250"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,0.0 c -1.33333,0.0 -6.66667,0.0 -8.0,0.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_6_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_6_animation.xml
new file mode 100644
index 0000000..3b872af
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_6_animation.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,-6.0 c -1.08333,1.0 -5.41667,5.0 -6.5,6.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="rotation"
+ android:valueFrom="0.0"
+ android:valueTo="-45.0"
+ android:valueType="floatType"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_1" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_1_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_1_animation.xml
new file mode 100644
index 0000000..94e04b4
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_1_animation.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -4.54266574364,-1.0 l 9.08533148727,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -9.08533148727,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.54266574364,-1.0 l 9.08533148727,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -9.08533148727,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueTo="M -3.54375000003,-1.21249999999 l 7.08750000005,0.0 c 0.669645259129,0.0 1.21249999999,0.542854740865 1.21249999999,1.21249999999 l 0.0,0.0 c 0.0,0.669645259129 -0.542854740865,1.21249999999 -1.21249999999,1.21249999999 l -7.08750000005,0.0 c -0.669645259129,0.0 -1.21249999999,-0.542854740865 -1.21249999999,-1.21249999999 l 0.0,0.0 c 0.0,-0.669645259129 0.542854740865,-1.21249999999 1.21249999999,-1.21249999999 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -3.54375000003,-1.21249999999 l 7.08750000005,0.0 c 0.669645259129,0.0 1.21249999999,0.542854740865 1.21249999999,1.21249999999 l 0.0,0.0 c 0.0,0.669645259129 -0.542854740865,1.21249999999 -1.21249999999,1.21249999999 l -7.08750000005,0.0 c -0.669645259129,0.0 -1.21249999999,-0.542854740865 -1.21249999999,-1.21249999999 l 0.0,0.0 c 0.0,-0.669645259129 0.542854740865,-1.21249999999 1.21249999999,-1.21249999999 Z"
+ android:valueTo="M -1.68627963028,-1.62527119327 l 3.37255926056,0.0 c 0.897612494333,0.0 1.62527119327,0.727658698938 1.62527119327,1.62527119327 l 0.0,0.0 c 0.0,0.897612494333 -0.727658698938,1.62527119327 -1.62527119327,1.62527119327 l -3.37255926056,0.0 c -0.897612494333,0.0 -1.62527119327,-0.727658698938 -1.62527119327,-1.62527119327 l 0.0,0.0 c 0.0,-0.897612494333 0.727658698938,-1.62527119327 1.62527119327,-1.62527119327 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.68627963028,-1.62527119327 l 3.37255926056,0.0 c 0.897612494333,0.0 1.62527119327,0.727658698938 1.62527119327,1.62527119327 l 0.0,0.0 c 0.0,0.897612494333 -0.727658698938,1.62527119327 -1.62527119327,1.62527119327 l -3.37255926056,0.0 c -0.897612494333,0.0 -1.62527119327,-0.727658698938 -1.62527119327,-1.62527119327 l 0.0,0.0 c 0.0,-0.897612494333 0.727658698938,-1.62527119327 1.62527119327,-1.62527119327 Z"
+ android:valueTo="M -0.866611597071,-1.8074196451 l 1.73322319414,0.0 c 0.998210306475,0.0 1.8074196451,0.80920933862 1.8074196451,1.8074196451 l 0.0,0.0 c 0.0,0.998210306475 -0.80920933862,1.8074196451 -1.8074196451,1.8074196451 l -1.73322319414,0.0 c -0.998210306475,0.0 -1.8074196451,-0.80920933862 -1.8074196451,-1.8074196451 l 0.0,0.0 c 0.0,-0.998210306475 0.80920933862,-1.8074196451 1.8074196451,-1.8074196451 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.866611597071,-1.8074196451 l 1.73322319414,0.0 c 0.998210306475,0.0 1.8074196451,0.80920933862 1.8074196451,1.8074196451 l 0.0,0.0 c 0.0,0.998210306475 -0.80920933862,1.8074196451 -1.8074196451,1.8074196451 l -1.73322319414,0.0 c -0.998210306475,0.0 -1.8074196451,-0.80920933862 -1.8074196451,-1.8074196451 l 0.0,0.0 c 0.0,-0.998210306475 0.80920933862,-1.8074196451 1.8074196451,-1.8074196451 Z"
+ android:valueTo="M -0.416762258226,-1.90738616484 l 0.833524516453,0.0 c 1.05342029082,0.0 1.90738616484,0.853965874019 1.90738616484,1.90738616484 l 0.0,0.0 c 0.0,1.05342029082 -0.853965874019,1.90738616484 -1.90738616484,1.90738616484 l -0.833524516453,0.0 c -1.05342029082,0.0 -1.90738616484,-0.853965874019 -1.90738616484,-1.90738616484 l 0.0,0.0 c 0.0,-1.05342029082 0.853965874019,-1.90738616484 1.90738616484,-1.90738616484 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.416762258226,-1.90738616484 l 0.833524516453,0.0 c 1.05342029082,0.0 1.90738616484,0.853965874019 1.90738616484,1.90738616484 l 0.0,0.0 c 0.0,1.05342029082 -0.853965874019,1.90738616484 -1.90738616484,1.90738616484 l -0.833524516453,0.0 c -1.05342029082,0.0 -1.90738616484,-0.853965874019 -1.90738616484,-1.90738616484 l 0.0,0.0 c 0.0,-1.05342029082 0.853965874019,-1.90738616484 1.90738616484,-1.90738616484 Z"
+ android:valueTo="M -0.163643891635,-1.96363469075 l 0.32728778327,0.0 c 1.08448549388,0.0 1.96363469075,0.87914919687 1.96363469075,1.96363469075 l 0.0,0.0 c 0.0,1.08448549388 -0.87914919687,1.96363469075 -1.96363469075,1.96363469075 l -0.32728778327,0.0 c -1.08448549388,0.0 -1.96363469075,-0.87914919687 -1.96363469075,-1.96363469075 l 0.0,0.0 c 0.0,-1.08448549388 0.87914919687,-1.96363469075 1.96363469075,-1.96363469075 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.163643891635,-1.96363469075 l 0.32728778327,0.0 c 1.08448549388,0.0 1.96363469075,0.87914919687 1.96363469075,1.96363469075 l 0.0,0.0 c 0.0,1.08448549388 -0.87914919687,1.96363469075 -1.96363469075,1.96363469075 l -0.32728778327,0.0 c -1.08448549388,0.0 -1.96363469075,-0.87914919687 -1.96363469075,-1.96363469075 l 0.0,0.0 c 0.0,-1.08448549388 0.87914919687,-1.96363469075 1.96363469075,-1.96363469075 Z"
+ android:valueTo="M -0.0368976091105,-1.99180053131 l 0.073795218221,0.0 c 1.10004105809,0.0 1.99180053131,0.891759473223 1.99180053131,1.99180053131 l 0.0,0.0 c 0.0,1.10004105809 -0.891759473223,1.99180053131 -1.99180053131,1.99180053131 l -0.073795218221,0.0 c -1.10004105809,0.0 -1.99180053131,-0.891759473223 -1.99180053131,-1.99180053131 l 0.0,0.0 c 0.0,-1.10004105809 0.891759473223,-1.99180053131 1.99180053131,-1.99180053131 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0368976091105,-1.99180053131 l 0.073795218221,0.0 c 1.10004105809,0.0 1.99180053131,0.891759473223 1.99180053131,1.99180053131 l 0.0,0.0 c 0.0,1.10004105809 -0.891759473223,1.99180053131 -1.99180053131,1.99180053131 l -0.073795218221,0.0 c -1.10004105809,0.0 -1.99180053131,-0.891759473223 -1.99180053131,-1.99180053131 l 0.0,0.0 c 0.0,-1.10004105809 0.891759473223,-1.99180053131 1.99180053131,-1.99180053131 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_2_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_2_animation.xml
new file mode 100644
index 0000000..423f8d7
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_2_animation.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -4.54266574364,-1.0 l 9.08533148727,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -9.08533148727,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.54266574364,-1.0 l 9.08533148727,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -9.08533148727,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueTo="M -2.49262086719,-1.44608425174 l 4.98524173437,0.0 c 0.79865027916,0.0 1.44608425174,0.647433972576 1.44608425174,1.44608425174 l 0.0,0.0 c 0.0,0.79865027916 -0.647433972576,1.44608425174 -1.44608425174,1.44608425174 l -4.98524173437,0.0 c -0.79865027916,0.0 -1.44608425174,-0.647433972576 -1.44608425174,-1.44608425174 l 0.0,0.0 c 0.0,-0.79865027916 0.647433972576,-1.44608425174 1.44608425174,-1.44608425174 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -2.49262086719,-1.44608425174 l 4.98524173437,0.0 c 0.79865027916,0.0 1.44608425174,0.647433972576 1.44608425174,1.44608425174 l 0.0,0.0 c 0.0,0.79865027916 -0.647433972576,1.44608425174 -1.44608425174,1.44608425174 l -4.98524173437,0.0 c -0.79865027916,0.0 -1.44608425174,-0.647433972576 -1.44608425174,-1.44608425174 l 0.0,0.0 c 0.0,-0.79865027916 0.647433972576,-1.44608425174 1.44608425174,-1.44608425174 Z"
+ android:valueTo="M -1.39285008918,-1.69047775796 l 2.78570017836,0.0 c 0.933625085597,0.0 1.69047775796,0.756852672362 1.69047775796,1.69047775796 l 0.0,0.0 c 0.0,0.933625085597 -0.756852672362,1.69047775796 -1.69047775796,1.69047775796 l -2.78570017836,0.0 c -0.933625085597,0.0 -1.69047775796,-0.756852672362 -1.69047775796,-1.69047775796 l 0.0,0.0 c 0.0,-0.933625085597 0.756852672362,-1.69047775796 1.69047775796,-1.69047775796 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.39285008918,-1.69047775796 l 2.78570017836,0.0 c 0.933625085597,0.0 1.69047775796,0.756852672362 1.69047775796,1.69047775796 l 0.0,0.0 c 0.0,0.933625085597 -0.756852672362,1.69047775796 -1.69047775796,1.69047775796 l -2.78570017836,0.0 c -0.933625085597,0.0 -1.69047775796,-0.756852672362 -1.69047775796,-1.69047775796 l 0.0,0.0 c 0.0,-0.933625085597 0.756852672362,-1.69047775796 1.69047775796,-1.69047775796 Z"
+ android:valueTo="M -0.77117021921,-1.82862884018 l 1.54234043842,0.0 c 1.00992382147,0.0 1.82862884018,0.818705018702 1.82862884018,1.82862884018 l 0.0,0.0 c 0.0,1.00992382147 -0.818705018702,1.82862884018 -1.82862884018,1.82862884018 l -1.54234043842,0.0 c -1.00992382147,0.0 -1.82862884018,-0.818705018702 -1.82862884018,-1.82862884018 l 0.0,0.0 c 0.0,-1.00992382147 0.818705018702,-1.82862884018 1.82862884018,-1.82862884018 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.77117021921,-1.82862884018 l 1.54234043842,0.0 c 1.00992382147,0.0 1.82862884018,0.818705018702 1.82862884018,1.82862884018 l 0.0,0.0 c 0.0,1.00992382147 -0.818705018702,1.82862884018 -1.82862884018,1.82862884018 l -1.54234043842,0.0 c -1.00992382147,0.0 -1.82862884018,-0.818705018702 -1.82862884018,-1.82862884018 l 0.0,0.0 c 0.0,-1.00992382147 0.818705018702,-1.82862884018 1.82862884018,-1.82862884018 Z"
+ android:valueTo="M -0.387649645988,-1.91385563422 l 0.775299291975,0.0 c 1.0569932801,0.0 1.91385563422,0.856862354124 1.91385563422,1.91385563422 l 0.0,0.0 c 0.0,1.0569932801 -0.856862354124,1.91385563422 -1.91385563422,1.91385563422 l -0.775299291975,0.0 c -1.0569932801,0.0 -1.91385563422,-0.856862354124 -1.91385563422,-1.91385563422 l 0.0,0.0 c 0.0,-1.0569932801 0.856862354124,-1.91385563422 1.91385563422,-1.91385563422 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.387649645988,-1.91385563422 l 0.775299291975,0.0 c 1.0569932801,0.0 1.91385563422,0.856862354124 1.91385563422,1.91385563422 l 0.0,0.0 c 0.0,1.0569932801 -0.856862354124,1.91385563422 -1.91385563422,1.91385563422 l -0.775299291975,0.0 c -1.0569932801,0.0 -1.91385563422,-0.856862354124 -1.91385563422,-1.91385563422 l 0.0,0.0 c 0.0,-1.0569932801 0.856862354124,-1.91385563422 1.91385563422,-1.91385563422 Z"
+ android:valueTo="M -0.156963485208,-1.96511922551 l 0.313926970417,0.0 c 1.08530537979,0.0 1.96511922551,0.879813845722 1.96511922551,1.96511922551 l 0.0,0.0 c 0.0,1.08530537979 -0.879813845722,1.96511922551 -1.96511922551,1.96511922551 l -0.313926970417,0.0 c -1.08530537979,0.0 -1.96511922551,-0.879813845722 -1.96511922551,-1.96511922551 l 0.0,0.0 c 0.0,-1.08530537979 0.879813845722,-1.96511922551 1.96511922551,-1.96511922551 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.156963485208,-1.96511922551 l 0.313926970417,0.0 c 1.08530537979,0.0 1.96511922551,0.879813845722 1.96511922551,1.96511922551 l 0.0,0.0 c 0.0,1.08530537979 -0.879813845722,1.96511922551 -1.96511922551,1.96511922551 l -0.313926970417,0.0 c -1.08530537979,0.0 -1.96511922551,-0.879813845722 -1.96511922551,-1.96511922551 l 0.0,0.0 c 0.0,-1.08530537979 0.879813845722,-1.96511922551 1.96511922551,-1.96511922551 Z"
+ android:valueTo="M -0.0362224951386,-1.99195055664 l 0.0724449902773,0.0 c 1.10012391479,0.0 1.99195055664,0.89182664185 1.99195055664,1.99195055664 l 0.0,0.0 c 0.0,1.10012391479 -0.89182664185,1.99195055664 -1.99195055664,1.99195055664 l -0.0724449902773,0.0 c -1.10012391479,0.0 -1.99195055664,-0.89182664185 -1.99195055664,-1.99195055664 l 0.0,0.0 c 0.0,-1.10012391479 0.89182664185,-1.99195055664 1.99195055664,-1.99195055664 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0362224951386,-1.99195055664 l 0.0724449902773,0.0 c 1.10012391479,0.0 1.99195055664,0.89182664185 1.99195055664,1.99195055664 l 0.0,0.0 c 0.0,1.10012391479 -0.89182664185,1.99195055664 -1.99195055664,1.99195055664 l -0.0724449902773,0.0 c -1.10012391479,0.0 -1.99195055664,-0.89182664185 -1.99195055664,-1.99195055664 l 0.0,0.0 c 0.0,-1.10012391479 0.89182664185,-1.99195055664 1.99195055664,-1.99195055664 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_3_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_3_animation.xml
new file mode 100644
index 0000000..444f6b6
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_3_animation.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="pathData"
+ android:valueFrom="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -6.04266574364,-1.0 l 12.0853314873,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -12.0853314873,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -6.04266574364,-1.0 l 12.0853314873,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -12.0853314873,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueTo="M -3.32349448958,-1.44608425174 l 6.64698897916,0.0 c 0.79865027916,0.0 1.44608425174,0.647433972576 1.44608425174,1.44608425174 l 0.0,0.0 c 0.0,0.79865027916 -0.647433972576,1.44608425174 -1.44608425174,1.44608425174 l -6.64698897916,0.0 c -0.79865027916,0.0 -1.44608425174,-0.647433972576 -1.44608425174,-1.44608425174 l 0.0,0.0 c 0.0,-0.79865027916 0.647433972576,-1.44608425174 1.44608425174,-1.44608425174 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -3.32349448958,-1.44608425174 l 6.64698897916,0.0 c 0.79865027916,0.0 1.44608425174,0.647433972576 1.44608425174,1.44608425174 l 0.0,0.0 c 0.0,0.79865027916 -0.647433972576,1.44608425174 -1.44608425174,1.44608425174 l -6.64698897916,0.0 c -0.79865027916,0.0 -1.44608425174,-0.647433972576 -1.44608425174,-1.44608425174 l 0.0,0.0 c 0.0,-0.79865027916 0.647433972576,-1.44608425174 1.44608425174,-1.44608425174 Z"
+ android:valueTo="M -1.85713345224,-1.69047775796 l 3.71426690449,0.0 c 0.933625085597,0.0 1.69047775796,0.756852672362 1.69047775796,1.69047775796 l 0.0,0.0 c 0.0,0.933625085597 -0.756852672362,1.69047775796 -1.69047775796,1.69047775796 l -3.71426690449,0.0 c -0.933625085597,0.0 -1.69047775796,-0.756852672362 -1.69047775796,-1.69047775796 l 0.0,0.0 c 0.0,-0.933625085597 0.756852672362,-1.69047775796 1.69047775796,-1.69047775796 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.85713345224,-1.69047775796 l 3.71426690449,0.0 c 0.933625085597,0.0 1.69047775796,0.756852672362 1.69047775796,1.69047775796 l 0.0,0.0 c 0.0,0.933625085597 -0.756852672362,1.69047775796 -1.69047775796,1.69047775796 l -3.71426690449,0.0 c -0.933625085597,0.0 -1.69047775796,-0.756852672362 -1.69047775796,-1.69047775796 l 0.0,0.0 c 0.0,-0.933625085597 0.756852672362,-1.69047775796 1.69047775796,-1.69047775796 Z"
+ android:valueTo="M -1.02822695895,-1.82862884018 l 2.05645391789,0.0 c 1.00992382147,0.0 1.82862884018,0.818705018702 1.82862884018,1.82862884018 l 0.0,0.0 c 0.0,1.00992382147 -0.818705018702,1.82862884018 -1.82862884018,1.82862884018 l -2.05645391789,0.0 c -1.00992382147,0.0 -1.82862884018,-0.818705018702 -1.82862884018,-1.82862884018 l 0.0,0.0 c 0.0,-1.00992382147 0.818705018702,-1.82862884018 1.82862884018,-1.82862884018 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.02822695895,-1.82862884018 l 2.05645391789,0.0 c 1.00992382147,0.0 1.82862884018,0.818705018702 1.82862884018,1.82862884018 l 0.0,0.0 c 0.0,1.00992382147 -0.818705018702,1.82862884018 -1.82862884018,1.82862884018 l -2.05645391789,0.0 c -1.00992382147,0.0 -1.82862884018,-0.818705018702 -1.82862884018,-1.82862884018 l 0.0,0.0 c 0.0,-1.00992382147 0.818705018702,-1.82862884018 1.82862884018,-1.82862884018 Z"
+ android:valueTo="M -0.51686619465,-1.91385563422 l 1.0337323893,0.0 c 1.0569932801,0.0 1.91385563422,0.856862354124 1.91385563422,1.91385563422 l 0.0,0.0 c 0.0,1.0569932801 -0.856862354124,1.91385563422 -1.91385563422,1.91385563422 l -1.0337323893,0.0 c -1.0569932801,0.0 -1.91385563422,-0.856862354124 -1.91385563422,-1.91385563422 l 0.0,0.0 c 0.0,-1.0569932801 0.856862354124,-1.91385563422 1.91385563422,-1.91385563422 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.51686619465,-1.91385563422 l 1.0337323893,0.0 c 1.0569932801,0.0 1.91385563422,0.856862354124 1.91385563422,1.91385563422 l 0.0,0.0 c 0.0,1.0569932801 -0.856862354124,1.91385563422 -1.91385563422,1.91385563422 l -1.0337323893,0.0 c -1.0569932801,0.0 -1.91385563422,-0.856862354124 -1.91385563422,-1.91385563422 l 0.0,0.0 c 0.0,-1.0569932801 0.856862354124,-1.91385563422 1.91385563422,-1.91385563422 Z"
+ android:valueTo="M -0.209284646944,-1.96511922551 l 0.418569293889,0.0 c 1.08530537979,0.0 1.96511922551,0.879813845722 1.96511922551,1.96511922551 l 0.0,0.0 c 0.0,1.08530537979 -0.879813845722,1.96511922551 -1.96511922551,1.96511922551 l -0.418569293889,0.0 c -1.08530537979,0.0 -1.96511922551,-0.879813845722 -1.96511922551,-1.96511922551 l 0.0,0.0 c 0.0,-1.08530537979 0.879813845722,-1.96511922551 1.96511922551,-1.96511922551 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.209284646944,-1.96511922551 l 0.418569293889,0.0 c 1.08530537979,0.0 1.96511922551,0.879813845722 1.96511922551,1.96511922551 l 0.0,0.0 c 0.0,1.08530537979 -0.879813845722,1.96511922551 -1.96511922551,1.96511922551 l -0.418569293889,0.0 c -1.08530537979,0.0 -1.96511922551,-0.879813845722 -1.96511922551,-1.96511922551 l 0.0,0.0 c 0.0,-1.08530537979 0.879813845722,-1.96511922551 1.96511922551,-1.96511922551 Z"
+ android:valueTo="M -0.0482966601849,-1.99195055664 l 0.0965933203697,0.0 c 1.10012391479,0.0 1.99195055664,0.89182664185 1.99195055664,1.99195055664 l 0.0,0.0 c 0.0,1.10012391479 -0.89182664185,1.99195055664 -1.99195055664,1.99195055664 l -0.0965933203697,0.0 c -1.10012391479,0.0 -1.99195055664,-0.89182664185 -1.99195055664,-1.99195055664 l 0.0,0.0 c 0.0,-1.10012391479 0.89182664185,-1.99195055664 1.99195055664,-1.99195055664 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0482966601849,-1.99195055664 l 0.0965933203697,0.0 c 1.10012391479,0.0 1.99195055664,0.89182664185 1.99195055664,1.99195055664 l 0.0,0.0 c 0.0,1.10012391479 -0.89182664185,1.99195055664 -1.99195055664,1.99195055664 l -0.0965933203697,0.0 c -1.10012391479,0.0 -1.99195055664,-0.89182664185 -1.99195055664,-1.99195055664 l 0.0,0.0 c 0.0,-1.10012391479 0.89182664185,-1.99195055664 1.99195055664,-1.99195055664 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_4_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_4_animation.xml
new file mode 100644
index 0000000..db294c7
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_4_animation.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="100"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M -4.0950630677,-1.30952224204 l 8.1901261354,0.0 c 0.177619793131,0.0 0.32160908516,0.143989292029 0.32160908516,0.32160908516 l 0.0,1.97582631376 c 0.0,0.177619793131 -0.143989292029,0.32160908516 -0.32160908516,0.32160908516 l -8.1901261354,0.0 c -0.177619793131,0.0 -0.32160908516,-0.143989292029 -0.32160908516,-0.32160908516 l 0.0,-1.97582631376 c 0.0,-0.177619793131 0.143989292029,-0.32160908516 0.32160908516,-0.32160908516 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="50"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.0950630677,-1.30952224204 l 8.1901261354,0.0 c 0.177619793131,0.0 0.32160908516,0.143989292029 0.32160908516,0.32160908516 l 0.0,1.97582631376 c 0.0,0.177619793131 -0.143989292029,0.32160908516 -0.32160908516,0.32160908516 l -8.1901261354,0.0 c -0.177619793131,0.0 -0.32160908516,-0.143989292029 -0.32160908516,-0.32160908516 l 0.0,-1.97582631376 c 0.0,-0.177619793131 0.143989292029,-0.32160908516 0.32160908516,-0.32160908516 Z"
+ android:valueTo="M -5.11454284324,-1.11013061622 l 10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.11454284324,-1.11013061622 l 10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.41755510258,-1.02355568498 l 10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.41755510258,-1.02355568498 l 10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_5_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_5_animation.xml
new file mode 100644
index 0000000..86e4dd6
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_5_animation.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="50"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M -0.510644950991,-1.91489250817 l 1.02128990198,0.0 c 1.05756592977,0.0 1.91489250817,0.857326578401 1.91489250817,1.91489250817 l 0.0,0.0 c 0.0,1.05756592977 -0.857326578401,1.91489250817 -1.91489250817,1.91489250817 l -1.02128990198,0.0 c -1.05756592977,0.0 -1.91489250817,-0.857326578401 -1.91489250817,-1.91489250817 l 0.0,0.0 c 0.0,-1.05756592977 0.857326578401,-1.91489250817 1.91489250817,-1.91489250817 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.510644950991,-1.91489250817 l 1.02128990198,0.0 c 1.05756592977,0.0 1.91489250817,0.857326578401 1.91489250817,1.91489250817 l 0.0,0.0 c 0.0,1.05756592977 -0.857326578401,1.91489250817 -1.91489250817,1.91489250817 l -1.02128990198,0.0 c -1.05756592977,0.0 -1.91489250817,-0.857326578401 -1.91489250817,-1.91489250817 l 0.0,0.0 c 0.0,-1.05756592977 0.857326578401,-1.91489250817 1.91489250817,-1.91489250817 Z"
+ android:valueTo="M -3.66172292328,-1.54166666667 l 7.32344584656,0.0 c 0.347908322704,0.0 0.629943743386,0.282035420682 0.629943743386,0.629943743386 l 0.0,1.82344584656 c 0.0,0.347908322704 -0.282035420682,0.629943743386 -0.629943743386,0.629943743386 l -7.32344584656,0.0 c -0.347908322704,0.0 -0.629943743386,-0.282035420682 -0.629943743386,-0.629943743386 l 0.0,-1.82344584656 c 0.0,-0.347908322704 0.282035420682,-0.629943743386 0.629943743386,-0.629943743386 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M -3.66172292328,-1.54166666667 l 7.32344584656,0.0 c 0.347908322704,0.0 0.629943743386,0.282035420682 0.629943743386,0.629943743386 l 0.0,1.82344584656 c 0.0,0.347908322704 -0.282035420682,0.629943743386 -0.629943743386,0.629943743386 l -7.32344584656,0.0 c -0.347908322704,0.0 -0.629943743386,-0.282035420682 -0.629943743386,-0.629943743386 l 0.0,-1.82344584656 c 0.0,-0.347908322704 0.282035420682,-0.629943743386 0.629943743386,-0.629943743386 Z"
+ android:valueTo="M -5.80605656225,-1.22447422869 l 11.6121131245,0.0 c 0.0395282866537,0.0 0.0715722943065,0.0320440076528 0.0715722943065,0.0715722943065 l 0.0,2.30580386877 c 0.0,0.0395282866537 -0.0320440076528,0.0715722943065 -0.0715722943065,0.0715722943065 l -11.6121131245,0.0 c -0.0395282866537,0.0 -0.0715722943065,-0.0320440076528 -0.0715722943065,-0.0715722943065 l 0.0,-2.30580386877 c 0.0,-0.0395282866537 0.0320440076528,-0.0715722943065 0.0715722943065,-0.0715722943065 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.80605656225,-1.22447422869 l 11.6121131245,0.0 c 0.0395282866537,0.0 0.0715722943065,0.0320440076528 0.0715722943065,0.0715722943065 l 0.0,2.30580386877 c 0.0,0.0395282866537 -0.0320440076528,0.0715722943065 -0.0715722943065,0.0715722943065 l -11.6121131245,0.0 c -0.0395282866537,0.0 -0.0715722943065,-0.0320440076528 -0.0715722943065,-0.0715722943065 l 0.0,-2.30580386877 c 0.0,-0.0395282866537 0.0320440076528,-0.0715722943065 0.0715722943065,-0.0715722943065 Z"
+ android:valueTo="M -6.60386380145,-1.07922723971 l 13.2077276029,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.15845447942 c 0.0,0.0 0.0,0.0 0.0,0.0 l -13.2077276029,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.15845447942 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M -6.60386380145,-1.07922723971 l 13.2077276029,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.15845447942 c 0.0,0.0 0.0,0.0 0.0,0.0 l -13.2077276029,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.15845447942 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -6.91679014084,-1.01664197183 l 13.8335802817,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.03328394367 c 0.0,0.0 0.0,0.0 0.0,0.0 l -13.8335802817,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.03328394367 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M -6.91679014084,-1.01664197183 l 13.8335802817,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.03328394367 c 0.0,0.0 0.0,0.0 0.0,0.0 l -13.8335802817,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.03328394367 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_6_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_6_animation.xml
new file mode 100644
index 0000000..db294c7
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_6_animation.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="100"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M -4.0950630677,-1.30952224204 l 8.1901261354,0.0 c 0.177619793131,0.0 0.32160908516,0.143989292029 0.32160908516,0.32160908516 l 0.0,1.97582631376 c 0.0,0.177619793131 -0.143989292029,0.32160908516 -0.32160908516,0.32160908516 l -8.1901261354,0.0 c -0.177619793131,0.0 -0.32160908516,-0.143989292029 -0.32160908516,-0.32160908516 l 0.0,-1.97582631376 c 0.0,-0.177619793131 0.143989292029,-0.32160908516 0.32160908516,-0.32160908516 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="50"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.0950630677,-1.30952224204 l 8.1901261354,0.0 c 0.177619793131,0.0 0.32160908516,0.143989292029 0.32160908516,0.32160908516 l 0.0,1.97582631376 c 0.0,0.177619793131 -0.143989292029,0.32160908516 -0.32160908516,0.32160908516 l -8.1901261354,0.0 c -0.177619793131,0.0 -0.32160908516,-0.143989292029 -0.32160908516,-0.32160908516 l 0.0,-1.97582631376 c 0.0,-0.177619793131 0.143989292029,-0.32160908516 0.32160908516,-0.32160908516 Z"
+ android:valueTo="M -5.11454284324,-1.11013061622 l 10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.11454284324,-1.11013061622 l 10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.41755510258,-1.02355568498 l 10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.41755510258,-1.02355568498 l 10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_1_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_1_animation.xml
new file mode 100644
index 0000000..3869ced
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_1_animation.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="400"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -6.5,0.0 c 1.08333,-1.0 5.41667,-5.0 6.5,-6.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="rotation"
+ android:valueFrom="-45.0"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_1_pivot_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_1_pivot_animation.xml
new file mode 100644
index 0000000..90010a75
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_1_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 4.5,0.0 c -0.75,0.0 -3.75,0.0 -4.5,0.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_2_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_2_animation.xml
new file mode 100644
index 0000000..b5e031e
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_2_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -8.0,0.0 c 1.33333,0.0 6.66667,0.0 8.0,0.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_2_pivot_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_2_pivot_animation.xml
new file mode 100644
index 0000000..55c72c6e
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_2_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="216"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 9.0,0.0 c -1.5,0.0 -7.5,0.0 -9.0,0.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_3_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_3_animation.xml
new file mode 100644
index 0000000..0524f2a
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_3_animation.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="400"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -6.5,0.0 c 1.08333,1.0 5.41667,5.0 6.5,6.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="rotation"
+ android:valueFrom="45.0"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_3_pivot_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_3_pivot_animation.xml
new file mode 100644
index 0000000..90010a75
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_3_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 4.5,0.0 c -0.75,0.0 -3.75,0.0 -4.5,0.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_path_1_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_1_animation.xml
new file mode 100644
index 0000000..ced8cf5
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_1_animation.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.349609375,-1.04296875 l 10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.349609375,-1.04296875 l 10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -4.57327108594,-1.25 l 9.14654217189,0.0 c 0.0285690903566,0.0 0.0517289140556,0.023159823699 0.0517289140556,0.0517289140556 l 0.0,2.39654217189 c 0.0,0.0285690903566 -0.023159823699,0.0517289140556 -0.0517289140556,0.0517289140556 l -9.14654217189,0.0 c -0.0285690903566,0.0 -0.0517289140556,-0.023159823699 -0.0517289140556,-0.0517289140556 l 0.0,-2.39654217189 c 0.0,-0.0285690903566 0.023159823699,-0.0517289140556 0.0517289140556,-0.0517289140556 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.57327108594,-1.25 l 9.14654217189,0.0 c 0.0285690903566,0.0 0.0517289140556,0.023159823699 0.0517289140556,0.0517289140556 l 0.0,2.39654217189 c 0.0,0.0285690903566 -0.023159823699,0.0517289140556 -0.0517289140556,0.0517289140556 l -9.14654217189,0.0 c -0.0285690903566,0.0 -0.0517289140556,-0.023159823699 -0.0517289140556,-0.0517289140556 l 0.0,-2.39654217189 c 0.0,-0.0285690903566 0.023159823699,-0.0517289140556 0.0517289140556,-0.0517289140556 Z"
+ android:valueTo="M -3.04137381938,-1.55960748018 l 6.08274763876,0.0 c 0.2761423749,0.0 0.5,0.2238576251 0.5,0.5 l 0.0,2.11921496035 c 0.0,0.2761423749 -0.2238576251,0.5 -0.5,0.5 l -6.08274763876,0.0 c -0.2761423749,0.0 -0.5,-0.2238576251 -0.5,-0.5 l 0.0,-2.11921496035 c 0.0,-0.2761423749 0.2238576251,-0.5 0.5,-0.5 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -3.04137381938,-1.55960748018 l 6.08274763876,0.0 c 0.2761423749,0.0 0.5,0.2238576251 0.5,0.5 l 0.0,2.11921496035 c 0.0,0.2761423749 -0.2238576251,0.5 -0.5,0.5 l -6.08274763876,0.0 c -0.2761423749,0.0 -0.5,-0.2238576251 -0.5,-0.5 l 0.0,-2.11921496035 c 0.0,-0.2761423749 0.2238576251,-0.5 0.5,-0.5 Z"
+ android:valueTo="M -1.55858989728,-1.77552577131 l 3.11717979456,0.0 c 0.677691994437,0.0 1.22706990313,0.549377908693 1.22706990313,1.22706990313 l 0.0,1.09691173636 c 0.0,0.677691994437 -0.549377908693,1.22706990313 -1.22706990313,1.22706990313 l -3.11717979456,0.0 c -0.677691994437,0.0 -1.22706990313,-0.549377908693 -1.22706990313,-1.22706990313 l 0.0,-1.09691173636 c 0.0,-0.677691994437 0.549377908693,-1.22706990313 1.22706990313,-1.22706990313 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.55858989728,-1.77552577131 l 3.11717979456,0.0 c 0.677691994437,0.0 1.22706990313,0.549377908693 1.22706990313,1.22706990313 l 0.0,1.09691173636 c 0.0,0.677691994437 -0.549377908693,1.22706990313 -1.22706990313,1.22706990313 l -3.11717979456,0.0 c -0.677691994437,0.0 -1.22706990313,-0.549377908693 -1.22706990313,-1.22706990313 l 0.0,-1.09691173636 c 0.0,-0.677691994437 0.549377908693,-1.22706990313 1.22706990313,-1.22706990313 Z"
+ android:valueTo="M -0.706008791281,-1.89447268498 l 1.41201758256,0.0 c 0.918635554655,0.0 1.66333681129,0.744701256633 1.66333681129,1.66333681129 l 0.0,0.462271747384 c 0.0,0.918635554655 -0.744701256633,1.66333681129 -1.66333681129,1.66333681129 l -1.41201758256,0.0 c -0.918635554655,0.0 -1.66333681129,-0.744701256633 -1.66333681129,-1.66333681129 l 0.0,-0.462271747384 c 0.0,-0.918635554655 0.744701256633,-1.66333681129 1.66333681129,-1.66333681129 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.706008791281,-1.89447268498 l 1.41201758256,0.0 c 0.918635554655,0.0 1.66333681129,0.744701256633 1.66333681129,1.66333681129 l 0.0,0.462271747384 c 0.0,0.918635554655 -0.744701256633,1.66333681129 -1.66333681129,1.66333681129 l -1.41201758256,0.0 c -0.918635554655,0.0 -1.66333681129,-0.744701256633 -1.66333681129,-1.66333681129 l 0.0,-0.462271747384 c 0.0,-0.918635554655 0.744701256633,-1.66333681129 1.66333681129,-1.66333681129 Z"
+ android:valueTo="M -0.265730251554,-1.95936709392 l 0.531460503108,0.0 c 1.03635400439,0.0 1.87648491973,0.840130915331 1.87648491973,1.87648491973 l 0.0,0.165764348389 c 0.0,1.03635400439 -0.840130915331,1.87648491973 -1.87648491973,1.87648491973 l -0.531460503108,0.0 c -1.03635400439,0.0 -1.87648491973,-0.840130915331 -1.87648491973,-1.87648491973 l 0.0,-0.165764348389 c 0.0,-1.03635400439 0.840130915331,-1.87648491973 1.87648491973,-1.87648491973 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.265730251554,-1.95936709392 l 0.531460503108,0.0 c 1.03635400439,0.0 1.87648491973,0.840130915331 1.87648491973,1.87648491973 l 0.0,0.165764348389 c 0.0,1.03635400439 -0.840130915331,1.87648491973 -1.87648491973,1.87648491973 l -0.531460503108,0.0 c -1.03635400439,0.0 -1.87648491973,-0.840130915331 -1.87648491973,-1.87648491973 l 0.0,-0.165764348389 c 0.0,-1.03635400439 0.840130915331,-1.87648491973 1.87648491973,-1.87648491973 Z"
+ android:valueTo="M -0.0581061000545,-1.99098433926 l 0.116212200109,0.0 c 1.08990562844,0.0 1.97344871252,0.883543084083 1.97344871252,1.97344871252 l 0.0,0.0350712534878 c 0.0,1.08990562844 -0.883543084083,1.97344871252 -1.97344871252,1.97344871252 l -0.116212200109,0.0 c -1.08990562844,0.0 -1.97344871252,-0.883543084083 -1.97344871252,-1.97344871252 l 0.0,-0.0350712534878 c 0.0,-1.08990562844 0.883543084083,-1.97344871252 1.97344871252,-1.97344871252 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0581061000545,-1.99098433926 l 0.116212200109,0.0 c 1.08990562844,0.0 1.97344871252,0.883543084083 1.97344871252,1.97344871252 l 0.0,0.0350712534878 c 0.0,1.08990562844 -0.883543084083,1.97344871252 -1.97344871252,1.97344871252 l -0.116212200109,0.0 c -1.08990562844,0.0 -1.97344871252,-0.883543084083 -1.97344871252,-1.97344871252 l 0.0,-0.0350712534878 c 0.0,-1.08990562844 0.883543084083,-1.97344871252 1.97344871252,-1.97344871252 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_path_2_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_2_animation.xml
new file mode 100644
index 0000000..ced8cf5
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_2_animation.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.349609375,-1.04296875 l 10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.349609375,-1.04296875 l 10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -4.57327108594,-1.25 l 9.14654217189,0.0 c 0.0285690903566,0.0 0.0517289140556,0.023159823699 0.0517289140556,0.0517289140556 l 0.0,2.39654217189 c 0.0,0.0285690903566 -0.023159823699,0.0517289140556 -0.0517289140556,0.0517289140556 l -9.14654217189,0.0 c -0.0285690903566,0.0 -0.0517289140556,-0.023159823699 -0.0517289140556,-0.0517289140556 l 0.0,-2.39654217189 c 0.0,-0.0285690903566 0.023159823699,-0.0517289140556 0.0517289140556,-0.0517289140556 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.57327108594,-1.25 l 9.14654217189,0.0 c 0.0285690903566,0.0 0.0517289140556,0.023159823699 0.0517289140556,0.0517289140556 l 0.0,2.39654217189 c 0.0,0.0285690903566 -0.023159823699,0.0517289140556 -0.0517289140556,0.0517289140556 l -9.14654217189,0.0 c -0.0285690903566,0.0 -0.0517289140556,-0.023159823699 -0.0517289140556,-0.0517289140556 l 0.0,-2.39654217189 c 0.0,-0.0285690903566 0.023159823699,-0.0517289140556 0.0517289140556,-0.0517289140556 Z"
+ android:valueTo="M -3.04137381938,-1.55960748018 l 6.08274763876,0.0 c 0.2761423749,0.0 0.5,0.2238576251 0.5,0.5 l 0.0,2.11921496035 c 0.0,0.2761423749 -0.2238576251,0.5 -0.5,0.5 l -6.08274763876,0.0 c -0.2761423749,0.0 -0.5,-0.2238576251 -0.5,-0.5 l 0.0,-2.11921496035 c 0.0,-0.2761423749 0.2238576251,-0.5 0.5,-0.5 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -3.04137381938,-1.55960748018 l 6.08274763876,0.0 c 0.2761423749,0.0 0.5,0.2238576251 0.5,0.5 l 0.0,2.11921496035 c 0.0,0.2761423749 -0.2238576251,0.5 -0.5,0.5 l -6.08274763876,0.0 c -0.2761423749,0.0 -0.5,-0.2238576251 -0.5,-0.5 l 0.0,-2.11921496035 c 0.0,-0.2761423749 0.2238576251,-0.5 0.5,-0.5 Z"
+ android:valueTo="M -1.55858989728,-1.77552577131 l 3.11717979456,0.0 c 0.677691994437,0.0 1.22706990313,0.549377908693 1.22706990313,1.22706990313 l 0.0,1.09691173636 c 0.0,0.677691994437 -0.549377908693,1.22706990313 -1.22706990313,1.22706990313 l -3.11717979456,0.0 c -0.677691994437,0.0 -1.22706990313,-0.549377908693 -1.22706990313,-1.22706990313 l 0.0,-1.09691173636 c 0.0,-0.677691994437 0.549377908693,-1.22706990313 1.22706990313,-1.22706990313 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.55858989728,-1.77552577131 l 3.11717979456,0.0 c 0.677691994437,0.0 1.22706990313,0.549377908693 1.22706990313,1.22706990313 l 0.0,1.09691173636 c 0.0,0.677691994437 -0.549377908693,1.22706990313 -1.22706990313,1.22706990313 l -3.11717979456,0.0 c -0.677691994437,0.0 -1.22706990313,-0.549377908693 -1.22706990313,-1.22706990313 l 0.0,-1.09691173636 c 0.0,-0.677691994437 0.549377908693,-1.22706990313 1.22706990313,-1.22706990313 Z"
+ android:valueTo="M -0.706008791281,-1.89447268498 l 1.41201758256,0.0 c 0.918635554655,0.0 1.66333681129,0.744701256633 1.66333681129,1.66333681129 l 0.0,0.462271747384 c 0.0,0.918635554655 -0.744701256633,1.66333681129 -1.66333681129,1.66333681129 l -1.41201758256,0.0 c -0.918635554655,0.0 -1.66333681129,-0.744701256633 -1.66333681129,-1.66333681129 l 0.0,-0.462271747384 c 0.0,-0.918635554655 0.744701256633,-1.66333681129 1.66333681129,-1.66333681129 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.706008791281,-1.89447268498 l 1.41201758256,0.0 c 0.918635554655,0.0 1.66333681129,0.744701256633 1.66333681129,1.66333681129 l 0.0,0.462271747384 c 0.0,0.918635554655 -0.744701256633,1.66333681129 -1.66333681129,1.66333681129 l -1.41201758256,0.0 c -0.918635554655,0.0 -1.66333681129,-0.744701256633 -1.66333681129,-1.66333681129 l 0.0,-0.462271747384 c 0.0,-0.918635554655 0.744701256633,-1.66333681129 1.66333681129,-1.66333681129 Z"
+ android:valueTo="M -0.265730251554,-1.95936709392 l 0.531460503108,0.0 c 1.03635400439,0.0 1.87648491973,0.840130915331 1.87648491973,1.87648491973 l 0.0,0.165764348389 c 0.0,1.03635400439 -0.840130915331,1.87648491973 -1.87648491973,1.87648491973 l -0.531460503108,0.0 c -1.03635400439,0.0 -1.87648491973,-0.840130915331 -1.87648491973,-1.87648491973 l 0.0,-0.165764348389 c 0.0,-1.03635400439 0.840130915331,-1.87648491973 1.87648491973,-1.87648491973 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.265730251554,-1.95936709392 l 0.531460503108,0.0 c 1.03635400439,0.0 1.87648491973,0.840130915331 1.87648491973,1.87648491973 l 0.0,0.165764348389 c 0.0,1.03635400439 -0.840130915331,1.87648491973 -1.87648491973,1.87648491973 l -0.531460503108,0.0 c -1.03635400439,0.0 -1.87648491973,-0.840130915331 -1.87648491973,-1.87648491973 l 0.0,-0.165764348389 c 0.0,-1.03635400439 0.840130915331,-1.87648491973 1.87648491973,-1.87648491973 Z"
+ android:valueTo="M -0.0581061000545,-1.99098433926 l 0.116212200109,0.0 c 1.08990562844,0.0 1.97344871252,0.883543084083 1.97344871252,1.97344871252 l 0.0,0.0350712534878 c 0.0,1.08990562844 -0.883543084083,1.97344871252 -1.97344871252,1.97344871252 l -0.116212200109,0.0 c -1.08990562844,0.0 -1.97344871252,-0.883543084083 -1.97344871252,-1.97344871252 l 0.0,-0.0350712534878 c 0.0,-1.08990562844 0.883543084083,-1.97344871252 1.97344871252,-1.97344871252 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0581061000545,-1.99098433926 l 0.116212200109,0.0 c 1.08990562844,0.0 1.97344871252,0.883543084083 1.97344871252,1.97344871252 l 0.0,0.0350712534878 c 0.0,1.08990562844 -0.883543084083,1.97344871252 -1.97344871252,1.97344871252 l -0.116212200109,0.0 c -1.08990562844,0.0 -1.97344871252,-0.883543084083 -1.97344871252,-1.97344871252 l 0.0,-0.0350712534878 c 0.0,-1.08990562844 0.883543084083,-1.97344871252 1.97344871252,-1.97344871252 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_path_3_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_3_animation.xml
new file mode 100644
index 0000000..cb29410
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_3_animation.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="50"
+ android:propertyName="pathData"
+ android:valueFrom="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -4.36843359242,-1.49992262412 l 8.73686718484,0.0 c 0.0728757880921,0.0 0.131953286993,0.0590774989007 0.131953286993,0.131953286993 l 0.0,2.73593867425 c 0.0,0.0728757880921 -0.0590774989007,0.131953286993 -0.131953286993,0.131953286993 l -8.73686718484,0.0 c -0.0728757880921,0.0 -0.131953286993,-0.0590774989007 -0.131953286993,-0.131953286993 l 0.0,-2.73593867425 c 0.0,-0.0728757880921 0.0590774989007,-0.131953286993 0.131953286993,-0.131953286993 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.36843359242,-1.49992262412 l 8.73686718484,0.0 c 0.0728757880921,0.0 0.131953286993,0.0590774989007 0.131953286993,0.131953286993 l 0.0,2.73593867425 c 0.0,0.0728757880921 -0.0590774989007,0.131953286993 -0.131953286993,0.131953286993 l -8.73686718484,0.0 c -0.0728757880921,0.0 -0.131953286993,-0.0590774989007 -0.131953286993,-0.131953286993 l 0.0,-2.73593867425 c 0.0,-0.0728757880921 0.0590774989007,-0.131953286993 0.131953286993,-0.131953286993 Z"
+ android:valueTo="M -2.7976112102,-1.69047775796 l 5.59522242041,0.0 c 0.41421356235,0.0 0.75,0.33578643765 0.75,0.75 l 0.0,1.88095551592 c 0.0,0.41421356235 -0.33578643765,0.75 -0.75,0.75 l -5.59522242041,0.0 c -0.41421356235,0.0 -0.75,-0.33578643765 -0.75,-0.75 l 0.0,-1.88095551592 c 0.0,-0.41421356235 0.33578643765,-0.75 0.75,-0.75 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -2.7976112102,-1.69047775796 l 5.59522242041,0.0 c 0.41421356235,0.0 0.75,0.33578643765 0.75,0.75 l 0.0,1.88095551592 c 0.0,0.41421356235 -0.33578643765,0.75 -0.75,0.75 l -5.59522242041,0.0 c -0.41421356235,0.0 -0.75,-0.33578643765 -0.75,-0.75 l 0.0,-1.88095551592 c 0.0,-0.41421356235 0.33578643765,-0.75 0.75,-0.75 Z"
+ android:valueTo="M -1.5412962309,-1.81003891076 l 3.08259246181,0.0 c 0.777898159561,0.0 1.4085092153,0.630611055735 1.4085092153,1.4085092153 l 0.0,0.803059390927 c 0.0,0.777898159561 -0.630611055735,1.4085092153 -1.4085092153,1.4085092153 l -3.08259246181,0.0 c -0.777898159561,0.0 -1.4085092153,-0.630611055735 -1.4085092153,-1.4085092153 l 0.0,-0.803059390927 c 0.0,-0.777898159561 0.630611055735,-1.4085092153 1.4085092153,-1.4085092153 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.5412962309,-1.81003891076 l 3.08259246181,0.0 c 0.777898159561,0.0 1.4085092153,0.630611055735 1.4085092153,1.4085092153 l 0.0,0.803059390927 c 0.0,0.777898159561 -0.630611055735,1.4085092153 -1.4085092153,1.4085092153 l -3.08259246181,0.0 c -0.777898159561,0.0 -1.4085092153,-0.630611055735 -1.4085092153,-1.4085092153 l 0.0,-0.803059390927 c 0.0,-0.777898159561 0.630611055735,-1.4085092153 1.4085092153,-1.4085092153 Z"
+ android:valueTo="M -0.798718330914,-1.88987363368 l 1.59743666183,0.0 c 0.967555109393,0.0 1.75191350068,0.784358391285 1.75191350068,1.75191350068 l 0.0,0.275920266008 c 0.0,0.967555109393 -0.784358391285,1.75191350068 -1.75191350068,1.75191350068 l -1.59743666183,0.0 c -0.967555109393,0.0 -1.75191350068,-0.784358391285 -1.75191350068,-1.75191350068 l 0.0,-0.275920266008 c 0.0,-0.967555109393 0.784358391285,-1.75191350068 1.75191350068,-1.75191350068 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.798718330914,-1.88987363368 l 1.59743666183,0.0 c 0.967555109393,0.0 1.75191350068,0.784358391285 1.75191350068,1.75191350068 l 0.0,0.275920266008 c 0.0,0.967555109393 -0.784358391285,1.75191350068 -1.75191350068,1.75191350068 l -1.59743666183,0.0 c -0.967555109393,0.0 -1.75191350068,-0.784358391285 -1.75191350068,-1.75191350068 l 0.0,-0.275920266008 c 0.0,-0.967555109393 0.784358391285,-1.75191350068 1.75191350068,-1.75191350068 Z"
+ android:valueTo="M -0.366220962052,-1.94300934217 l 0.732441924103,0.0 c 1.05968660322,0.0 1.91873232712,0.859045723904 1.91873232712,1.91873232712 l 0.0,0.0485540300878 c 0.0,1.05968660322 -0.859045723904,1.91873232712 -1.91873232712,1.91873232712 l -0.732441924103,0.0 c -1.05968660322,0.0 -1.91873232712,-0.859045723904 -1.91873232712,-1.91873232712 l 0.0,-0.0485540300878 c 0.0,-1.05968660322 0.859045723904,-1.91873232712 1.91873232712,-1.91873232712 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.366220962052,-1.94300934217 l 0.732441924103,0.0 c 1.05968660322,0.0 1.91873232712,0.859045723904 1.91873232712,1.91873232712 l 0.0,0.0485540300878 c 0.0,1.05968660322 -0.859045723904,1.91873232712 -1.91873232712,1.91873232712 l -0.732441924103,0.0 c -1.05968660322,0.0 -1.91873232712,-0.859045723904 -1.91873232712,-1.91873232712 l 0.0,-0.0485540300878 c 0.0,-1.05968660322 0.859045723904,-1.91873232712 1.91873232712,-1.91873232712 Z"
+ android:valueTo="M -0.141334109858,-1.97644431502 l 0.282668219716,0.0 c 1.09156005402,0.0 1.97644431502,0.884884261007 1.97644431502,1.97644431502 l 0.0,0.0 c 0.0,1.09156005402 -0.884884261007,1.97644431502 -1.97644431502,1.97644431502 l -0.282668219716,0.0 c -1.09156005402,0.0 -1.97644431502,-0.884884261007 -1.97644431502,-1.97644431502 l 0.0,0.0 c 0.0,-1.09156005402 0.884884261007,-1.97644431502 1.97644431502,-1.97644431502 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.141334109858,-1.97644431502 l 0.282668219716,0.0 c 1.09156005402,0.0 1.97644431502,0.884884261007 1.97644431502,1.97644431502 l 0.0,0.0 c 0.0,1.09156005402 -0.884884261007,1.97644431502 -1.97644431502,1.97644431502 l -0.282668219716,0.0 c -1.09156005402,0.0 -1.97644431502,-0.884884261007 -1.97644431502,-1.97644431502 l 0.0,0.0 c 0.0,-1.09156005402 0.884884261007,-1.97644431502 1.97644431502,-1.97644431502 Z"
+ android:valueTo="M -0.0331287849506,-1.99447853584 l 0.0662575699012,0.0 c 1.10152007915,0.0 1.99447853584,0.892958456693 1.99447853584,1.99447853584 l 0.0,0.0 c 0.0,1.10152007915 -0.892958456693,1.99447853584 -1.99447853584,1.99447853584 l -0.0662575699012,0.0 c -1.10152007915,0.0 -1.99447853584,-0.892958456693 -1.99447853584,-1.99447853584 l 0.0,0.0 c 0.0,-1.10152007915 0.892958456693,-1.99447853584 1.99447853584,-1.99447853584 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0331287849506,-1.99447853584 l 0.0662575699012,0.0 c 1.10152007915,0.0 1.99447853584,0.892958456693 1.99447853584,1.99447853584 l 0.0,0.0 c 0.0,1.10152007915 -0.892958456693,1.99447853584 -1.99447853584,1.99447853584 l -0.0662575699012,0.0 c -1.10152007915,0.0 -1.99447853584,-0.892958456693 -1.99447853584,-1.99447853584 l 0.0,0.0 c 0.0,-1.10152007915 0.892958456693,-1.99447853584 1.99447853584,-1.99447853584 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/drawable/ft_avd_toarrow.xml b/core/res/res/drawable/ft_avd_toarrow.xml
new file mode 100644
index 0000000..087ea74
--- /dev/null
+++ b/core/res/res/drawable/ft_avd_toarrow.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="ft_avd_toarrow"
+ android:width="24dp"
+ android:viewportWidth="24"
+ android:height="24dp"
+ android:viewportHeight="24" >
+ <group
+ android:name="carrot_3"
+ android:translateX="12"
+ android:translateY="12" >
+ <group
+ android:name="rectangle_4"
+ android:translateY="6" >
+ <group
+ android:name="rectangle_3_pivot_0" >
+ <path
+ android:name="rectangle_path_4"
+ android:fillColor="#FF000000"
+ android:pathData="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z" />
+ </group>
+ </group>
+ <group
+ android:name="rectangle_5" >
+ <group
+ android:name="rectangle_2_pivot_0" >
+ <path
+ android:name="rectangle_path_5"
+ android:fillColor="#FF000000"
+ android:pathData="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z" />
+ </group>
+ </group>
+ <group
+ android:name="rectangle_6"
+ android:translateY="-6" >
+ <group
+ android:name="rectangle_1_pivot_0" >
+ <path
+ android:name="rectangle_path_6"
+ android:fillColor="#FF000000"
+ android:pathData="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z" />
+ </group>
+ </group>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ft_avd_toarrow_animation.xml b/core/res/res/drawable/ft_avd_toarrow_animation.xml
new file mode 100644
index 0000000..e31067d
--- /dev/null
+++ b/core/res/res/drawable/ft_avd_toarrow_animation.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<animated-vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/ft_avd_toarrow" >
+ <target
+ android:name="rectangle_4"
+ android:animation="@anim/ft_avd_toarrow_rectangle_4_animation" />
+ <target
+ android:name="rectangle_3_pivot_0"
+ android:animation="@anim/ft_avd_toarrow_rectangle_3_pivot_0_animation" />
+ <target
+ android:name="rectangle_path_4"
+ android:animation="@anim/ft_avd_toarrow_rectangle_path_4_animation" />
+ <target
+ android:name="rectangle_5"
+ android:animation="@anim/ft_avd_toarrow_rectangle_5_animation" />
+ <target
+ android:name="rectangle_2_pivot_0"
+ android:animation="@anim/ft_avd_toarrow_rectangle_2_pivot_0_animation" />
+ <target
+ android:name="rectangle_path_5"
+ android:animation="@anim/ft_avd_toarrow_rectangle_path_5_animation" />
+ <target
+ android:name="rectangle_6"
+ android:animation="@anim/ft_avd_toarrow_rectangle_6_animation" />
+ <target
+ android:name="rectangle_1_pivot_0"
+ android:animation="@anim/ft_avd_toarrow_rectangle_1_pivot_0_animation" />
+ <target
+ android:name="rectangle_path_6"
+ android:animation="@anim/ft_avd_toarrow_rectangle_path_6_animation" />
+</animated-vector>
diff --git a/core/res/res/drawable/ft_avd_tooverflow.xml b/core/res/res/drawable/ft_avd_tooverflow.xml
new file mode 100644
index 0000000..a2b9cec
--- /dev/null
+++ b/core/res/res/drawable/ft_avd_tooverflow.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="ft_avd_tooverflow"
+ android:width="24dp"
+ android:viewportWidth="24"
+ android:height="24dp"
+ android:viewportHeight="24" >
+ <group
+ android:name="carrot_5"
+ android:translateX="12"
+ android:translateY="12" >
+ <group
+ android:name="rectangle_3"
+ android:translateX="-6.5"
+ android:rotation="45" >
+ <group
+ android:name="rectangle_3_pivot"
+ android:translateX="4.5" >
+ <path
+ android:name="rectangle_path_2"
+ android:fillColor="#FF000000"
+ android:pathData="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
+ </group>
+ </group>
+ <group
+ android:name="rectangle_2"
+ android:translateX="-8" >
+ <group
+ android:name="rectangle_2_pivot"
+ android:translateX="9" >
+ <path
+ android:name="rectangle_path_3"
+ android:fillColor="#FF000000"
+ android:pathData="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
+ </group>
+ </group>
+ <group
+ android:name="rectangle_1"
+ android:translateX="-6.5"
+ android:rotation="-45" >
+ <group
+ android:name="rectangle_1_pivot"
+ android:translateX="4.5" >
+ <path
+ android:name="rectangle_path_1"
+ android:fillColor="#FF000000"
+ android:pathData="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
+ </group>
+ </group>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ft_avd_tooverflow_animation.xml b/core/res/res/drawable/ft_avd_tooverflow_animation.xml
new file mode 100644
index 0000000..ed9975b
--- /dev/null
+++ b/core/res/res/drawable/ft_avd_tooverflow_animation.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<animated-vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/ft_avd_tooverflow" >
+ <target
+ android:name="rectangle_3"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_3_animation" />
+ <target
+ android:name="rectangle_3_pivot"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_3_pivot_animation" />
+ <target
+ android:name="rectangle_path_2"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_path_2_animation" />
+ <target
+ android:name="rectangle_2"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_2_animation" />
+ <target
+ android:name="rectangle_2_pivot"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_2_pivot_animation" />
+ <target
+ android:name="rectangle_path_3"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_path_3_animation" />
+ <target
+ android:name="rectangle_1"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_1_animation" />
+ <target
+ android:name="rectangle_1_pivot"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_1_pivot_animation" />
+ <target
+ android:name="rectangle_path_1"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_path_1_animation" />
+</animated-vector>
diff --git a/core/res/res/drawable/ic_arrow_drop_down.xml b/core/res/res/drawable/ic_arrow_drop_down.xml
new file mode 100644
index 0000000..c8bb411
--- /dev/null
+++ b/core/res/res/drawable/ic_arrow_drop_down.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14.0dp"
+ android:height="14.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M16.600000,8.600000l-4.600000,4.599999 -4.600000,-4.599999 -1.400000,1.400000 6.000000,6.000000 6.000000,-6.000000z"
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/core/res/res/drawable/ic_arrow_up_14dp.xml b/core/res/res/drawable/ic_arrow_up_14dp.xml
new file mode 100644
index 0000000..c4cc0d1
--- /dev/null
+++ b/core/res/res/drawable/ic_arrow_up_14dp.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14.0dp"
+ android:height="14.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M12.000000,8.000000l-6.000000,6.000000 1.400000,1.400000 4.600000,-4.599999 4.600000,4.599999 1.400000,-1.400000z"
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_0.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_0.xml
new file mode 100644
index 0000000..c6db901
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_0.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 l 0.668316831683,0.0 c 0.00003,0.0 0.0663366336634,1.0 0.331683168317,1.0 L 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_1.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_1.xml
new file mode 100644
index 0000000..584385d
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_1.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 c 0.0001,0.0 0.0,1.0 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_2.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_2.xml
new file mode 100644
index 0000000..334dee7
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_2.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 c 0.161290322581,0.0 0.0806451612903,0.909090909091 0.403225806452,0.909090909091 c 0.238709677419,0.0 0.11935483871,0.0909090909091 0.596774193548,0.0909090909091 L 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_3.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_3.xml
new file mode 100644
index 0000000..67891fc
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_3.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 l 0.5,0.0 c 0.2,0.0 0.1,1.0 0.5,1.0 L 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_4.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_4.xml
new file mode 100644
index 0000000..756a9e1
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_4.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 l 0.5,0.0 c 0.00005,0.0 0.1,1.0 0.5,1.0 L 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_5.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_5.xml
new file mode 100644
index 0000000..584385d
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_5.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 c 0.0001,0.0 0.0,1.0 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_6.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_6.xml
new file mode 100644
index 0000000..bed54d4
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_6.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 l 0.582706766917,0.0 c 0.166917293233,0.0 0.0834586466165,1.0 0.417293233083,1.0 L 1.0,1.0" />
diff --git a/core/res/res/layout/floating_popup_container.xml b/core/res/res/layout/floating_popup_container.xml
index 63dae44..dd161e3 100644
--- a/core/res/res/layout/floating_popup_container.xml
+++ b/core/res/res/layout/floating_popup_container.xml
@@ -15,12 +15,11 @@
** limitations under the License.
*/
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
- android:layout_height="@dimen/floating_toolbar_height"
+ android:layout_height="wrap_content"
android:padding="0dp"
- android:layout_margin="0dp"
+ android:layout_margin="20dp"
android:elevation="2dp"
android:focusable="true"
android:focusableInTouchMode="true"
diff --git a/core/res/res/layout/floating_popup_overflow_button.xml b/core/res/res/layout/floating_popup_overflow_button.xml
new file mode 100644
index 0000000..7053f3e
--- /dev/null
+++ b/core/res/res/layout/floating_popup_overflow_button.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/overflow"
+ android:layout_width="@dimen/floating_toolbar_menu_image_button_width"
+ android:layout_height="@dimen/floating_toolbar_height"
+ android:paddingStart="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingTop="@dimen/floating_toolbar_menu_image_button_vertical_padding"
+ android:paddingEnd="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingBottom="@dimen/floating_toolbar_menu_image_button_vertical_padding"
+ android:scaleType="centerInside"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:tint="?attr/floatingToolbarForegroundColor" />
diff --git a/core/res/res/layout/notification_material_action.xml b/core/res/res/layout/notification_material_action.xml
index f4bc918..62602d8 100644
--- a/core/res/res/layout/notification_material_action.xml
+++ b/core/res/res/layout/notification_material_action.xml
@@ -21,7 +21,7 @@
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_gravity="center"
- android:layout_marginStart="8dp"
+ android:layout_marginStart="4dp"
android:textColor="@color/secondary_text_material_light"
android:singleLine="true"
android:ellipsize="end"
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index edaf020..2a89faa 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -17,13 +17,14 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/actions_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
+ android:layout_height="wrap_content"
+ android:layout_marginStart="-16dp"
+ android:layout_marginEnd="-16dp">
<LinearLayout
android:id="@+id/actions"
android:layout_width="match_parent"
android:layout_height="56dp"
- android:paddingEnd="8dp"
+ android:paddingEnd="4dp"
android:orientation="horizontal"
android:visibility="gone"
android:background="#ffeeeeee"
diff --git a/core/res/res/layout/notification_material_media_action.xml b/core/res/res/layout/notification_material_media_action.xml
index 1d52e54..19a6f84 100644
--- a/core/res/res/layout/notification_material_media_action.xml
+++ b/core/res/res/layout/notification_material_media_action.xml
@@ -19,10 +19,12 @@
style="@android:style/Widget.Material.Button.Borderless.Small"
android:id="@+id/action0"
android:layout_width="48dp"
- android:layout_height="match_parent"
- android:layout_marginLeft="2dp"
- android:layout_marginRight="2dp"
- android:layout_weight="1"
+ android:layout_height="48dp"
+ android:paddingBottom="8dp"
+ android:paddingTop="8dp"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:layout_marginEnd="2dp"
android:gravity="center"
android:background="@drawable/notification_material_media_action_background"
/>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
new file mode 100644
index 0000000..aceae9f
--- /dev/null
+++ b/core/res/res/layout/notification_template_header.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<NotificationHeaderView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/notification_header"
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:clipChildren="false"
+ android:layout_gravity="start|top"
+ android:gravity="center_vertical"
+ android:paddingTop="5dp"
+ android:paddingBottom="16dp"
+ android:paddingStart="@dimen/notification_content_margin_start"
+ android:paddingEnd="16dp">
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="18dp"
+ android:layout_height="18dp"
+ android:layout_marginEnd="3dp"
+ />
+ <TextView
+ android:id="@+id/number_of_children"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification"
+ android:layout_marginEnd="3dp"
+ android:layout_marginStart="2dp"
+ android:visibility="gone"
+ android:singleLine="true"
+ />
+ <TextView
+ android:id="@+id/app_name_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="3dp"
+ android:layout_marginEnd="2dp"
+ android:singleLine="true"
+ />
+ <TextView
+ android:id="@+id/sub_text_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@string/notification_header_divider_symbol"
+ android:visibility="gone"/>
+ <TextView
+ android:id="@+id/header_sub_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:visibility="gone"
+ android:singleLine="true"/>
+ <TextView
+ android:id="@+id/content_info_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@string/notification_header_divider_symbol"
+ android:singleLine="true"
+ android:visibility="gone"/>
+ <TextView
+ android:id="@+id/header_content_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:visibility="gone"
+ android:maxWidth="72dp"
+ android:singleLine="true"/>
+ <TextView
+ android:id="@+id/time_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@string/notification_header_divider_symbol"
+ android:singleLine="true"
+ android:visibility="gone"/>
+ <ViewStub
+ android:id="@+id/time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:layout="@layout/notification_template_part_time"
+ android:visibility="gone"
+ />
+ <ViewStub
+ android:id="@+id/chronometer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:layout="@layout/notification_template_part_chronometer"
+ android:visibility="gone"
+ />
+ <ImageView
+ android:id="@+id/expand_button"
+ android:background="@null"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="1dp"
+ android:src="@drawable/ic_arrow_drop_down"
+ android:visibility="gone"
+ />
+</NotificationHeaderView>
+
diff --git a/core/res/res/layout/notification_template_icon_group.xml b/core/res/res/layout/notification_template_icon_group.xml
deleted file mode 100644
index fa66163..0000000
--- a/core/res/res/layout/notification_template_icon_group.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2014 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:internal="http://schemas.android.com/apk/prv/res/android"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- android:id="@+id/icon_group"
- >
- <ImageView android:id="@+id/icon"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="12dp"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="12dp"
- android:scaleType="centerInside"
- />
- <ImageView android:id="@+id/right_icon"
- android:layout_width="16dp"
- android:layout_height="16dp"
- android:padding="3dp"
- android:layout_gravity="end|bottom"
- android:scaleType="centerInside"
- android:visibility="gone"
- android:layout_marginEnd="8dp"
- android:layout_marginBottom="8dp"
- />
-</FrameLayout>
-
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 94bbec8..b69eb24 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -18,24 +18,32 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
- android:layout_height="64dp"
+ android:layout_height="wrap_content"
android:tag="base"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- />
+ <include layout="@layout/notification_template_header" />
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
- android:layout_marginStart="@dimen/notification_large_icon_width"
- android:minHeight="@dimen/notification_large_icon_height"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:minHeight="@dimen/notification_min_content_height"
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
<include layout="@layout/notification_template_part_line3" />
</LinearLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginBottom="11dp"
+ android:layout_marginEnd="@dimen/notification_content_margin_end">
+ <include layout="@layout/notification_template_progress" />
+ </FrameLayout>
+ <include layout="@layout/notification_template_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index 97df978e..8c78b8d 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -14,71 +14,56 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:orientation="vertical"
android:tag="big"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- />
- <LinearLayout
- android:id="@+id/notification_main_column"
+ <FrameLayout
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_min_height"
+ android:layout_gravity="top"
+ android:tag="base"
+ >
+ <include layout="@layout/notification_template_header" />
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="top"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:minHeight="@dimen/notification_min_content_height"
+ android:orientation="vertical"
+ >
+ <include layout="@layout/notification_template_part_line1" />
+ <include layout="@layout/notification_template_part_line3" />
+ </LinearLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginBottom="11dp"
+ android:layout_marginEnd="@dimen/notification_content_margin_end">
+ <include layout="@layout/notification_template_progress" />
+ </FrameLayout>
+ <include layout="@layout/notification_template_right_icon" />
+ </FrameLayout>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:id="@+id/action_divider"
+ android:visibility="gone"
+ android:background="@drawable/notification_template_divider" />
+ <include
+ layout="@layout/notification_material_action_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="top"
- android:layout_marginStart="@dimen/notification_large_icon_width"
- android:minHeight="@dimen/notification_large_icon_height"
- android:orientation="vertical"
- >
- <include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:orientation="horizontal"
- android:gravity="top"
- >
- <TextView android:id="@+id/big_text"
- android:textAppearance="@style/TextAppearance.Material.Notification"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:singleLine="false"
- android:visibility="gone"
- />
- <ImageView android:id="@+id/profile_badge_large_template"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_weight="0"
- android:layout_marginStart="4dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
- </LinearLayout>
- <include
- layout="@layout/notification_template_part_line3"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- />
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_marginTop="10dp"
- android:id="@+id/action_divider"
- android:visibility="gone"
- android:background="@drawable/notification_template_divider" />
- <include
- layout="@layout/notification_material_action_list"
- android:layout_marginStart="-8dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
- </LinearLayout>
-</FrameLayout>
+ />
+</LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index 7fd93de..0427c8a 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -15,44 +15,53 @@
~ limitations under the License
-->
+<!-- Layout for the expanded media notification -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="128dp"
android:background="#00000000"
- android:tag="bigMedia"
+ android:tag="bigMediaNarrow"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- />
+ <include layout="@layout/notification_template_header"
+ android:layout_width="fill_parent"
+ android:layout_height="48dp"
+ android:layout_marginEnd="106dp"/>
<LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_large_icon_width"
- android:minHeight="@dimen/notification_large_icon_height"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginEnd="24dp"
+ android:layout_toStartOf="@id/right_icon"
+ android:minHeight="@dimen/notification_min_content_height"
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
<include layout="@layout/notification_template_part_line3" />
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
- android:layout_width="match_parent"
- android:layout_height="48dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="12dp"
+ android:layout_alignParentStart="true"
+ android:paddingStart="8dp"
+ android:paddingBottom="8dp"
android:orientation="horizontal"
android:layoutDirection="ltr"
>
<!-- media buttons will be added here -->
</LinearLayout>
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_above="@id/media_actions"
- android:id="@+id/action_divider"
- android:background="@drawable/notification_template_divider_media" />
+
+ <ImageView android:id="@+id/right_icon"
+ android:layout_width="96dp"
+ android:layout_height="96dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginTop="16dp"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentTop="true"
+ android:scaleType="centerCrop"
+ />
</RelativeLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media_narrow.xml b/core/res/res/layout/notification_template_material_big_media_narrow.xml
deleted file mode 100644
index 807cfaf..0000000
--- a/core/res/res/layout/notification_template_material_big_media_narrow.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2014 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<!-- Layout to be used with only max 3 actions. It has a much larger picture at the left side-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/status_bar_latest_event_content"
- android:layout_width="match_parent"
- android:layout_height="128dp"
- android:background="#00000000"
- android:tag="bigMediaNarrow"
- >
- <ImageView android:id="@+id/icon"
- android:layout_width="128dp"
- android:layout_height="128dp"
- android:scaleType="centerCrop"
- />
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="12dp"
- android:layout_toEndOf="@id/icon"
- android:minHeight="@dimen/notification_large_icon_height"
- android:orientation="vertical"
- >
- <include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
- <include layout="@layout/notification_template_part_line3" />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/media_actions"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:layout_toEndOf="@id/icon"
- android:layout_alignParentBottom="true"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="12dp"
- android:orientation="horizontal"
- android:layoutDirection="ltr"
- >
- <!-- media buttons will be added here -->
- </LinearLayout>
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_toEndOf="@id/icon"
- android:layout_above="@id/media_actions"
- android:id="@+id/action_divider"
- android:background="@drawable/notification_template_divider_media" />
-</RelativeLayout>
diff --git a/core/res/res/layout/notification_template_material_big_picture.xml b/core/res/res/layout/notification_template_material_big_picture.xml
index f3768b5..74e7775 100644
--- a/core/res/res/layout/notification_template_material_big_picture.xml
+++ b/core/res/res/layout/notification_template_material_big_picture.xml
@@ -21,39 +21,45 @@
android:layout_height="match_parent"
android:tag="bigPicture"
>
- <ImageView
- android:id="@+id/big_picture"
- android:layout_width="match_parent"
- android:layout_height="192dp"
- android:layout_marginTop="64dp"
- android:layout_gravity="bottom"
- android:scaleType="centerCrop"
- />
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="6dp"
- android:layout_marginTop="64dp"
- android:scaleType="fitXY"
- android:src="@drawable/title_bar_shadow"
- />
- <include layout="@layout/notification_template_material_base"
- android:layout_width="match_parent"
- android:layout_height="64dp"
- />
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="208dp"
- android:paddingStart="@dimen/notification_large_icon_width"
- android:layout_gravity="bottom"
- android:background="#CCEEEEEE"
- >
- <include
- layout="@layout/notification_material_action_list"
- android:id="@+id/actions"
- android:layout_gravity="bottom"
+ <include layout="@layout/notification_template_header" />
+ <include layout="@layout/notification_template_right_icon" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="top"
+ android:paddingStart="@dimen/notification_content_margin_start"
+ android:paddingEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:clipToPadding="false"
+ android:orientation="vertical"
+ >
+ <LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- />
- </FrameLayout>
+ android:orientation="vertical">
+ <include layout="@layout/notification_template_part_line1"/>
+ <include layout="@layout/notification_template_progress"/>
+ <include layout="@layout/notification_template_part_line3"/>
+ </LinearLayout>
+ <ImageView
+ android:id="@+id/big_picture"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:adjustViewBounds="true"
+ android:layout_weight="1"
+ android:layout_marginTop="13dp"
+ android:layout_marginBottom="16dp"
+ android:scaleType="centerCrop"
+ />
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginStart="-16dp"
+ android:layout_marginEnd="-16dp"
+ android:id="@+id/action_divider"
+ android:visibility="gone"
+ android:background="@drawable/notification_template_divider" />
+ <include layout="@layout/notification_material_action_list" />
+ </LinearLayout>
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index 7ae29fb..354c0fb 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -21,31 +21,30 @@
android:layout_height="wrap_content"
android:tag="bigText"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- />
+ <include layout="@layout/notification_template_header" />
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
- android:layout_marginStart="@dimen/notification_large_icon_width"
- android:minHeight="@dimen/notification_large_icon_height"
+ android:paddingStart="@dimen/notification_content_margin_start"
+ android:paddingEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:clipToPadding="false"
+ android:minHeight="@dimen/notification_min_content_height"
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
+ <include layout="@layout/notification_template_progress" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
- android:layout_marginBottom="10dp"
+ android:paddingBottom="13dp"
android:orientation="horizontal"
android:gravity="top"
android:layout_weight="1"
>
- <TextView android:id="@+id/big_text"
+ <com.android.internal.widget.ImageFloatingTextView android:id="@+id/big_text"
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -66,27 +65,12 @@
<ImageView
android:layout_width="match_parent"
android:layout_height="1dp"
+ android:layout_marginStart="-16dp"
+ android:layout_marginEnd="-16dp"
android:id="@+id/action_divider"
android:visibility="gone"
android:background="@drawable/notification_template_divider" />
- <include
- layout="@layout/notification_material_action_list"
- android:layout_marginStart="-8dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:id="@+id/overflow_divider"
- android:visibility="visible"
- android:background="@drawable/notification_template_divider" />
- <include
- layout="@layout/notification_template_part_line3"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="10dp" />
+ <include layout="@layout/notification_material_action_list" />
</LinearLayout>
+ <include layout="@layout/notification_template_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml
index 950ae40..4f12d76 100644
--- a/core/res/res/layout/notification_template_material_inbox.xml
+++ b/core/res/res/layout/notification_template_material_inbox.xml
@@ -21,21 +21,26 @@
android:layout_height="wrap_content"
android:tag="inbox"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- />
+ <include layout="@layout/notification_template_header" />
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
- android:layout_marginStart="@dimen/notification_large_icon_width"
- android:minHeight="@dimen/notification_large_icon_height"
+ android:paddingStart="@dimen/notification_content_margin_start"
+ android:paddingEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:minHeight="@dimen/notification_min_content_height"
+ android:clipToPadding="false"
android:orientation="vertical"
>
- <include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
+ <include layout="@layout/notification_template_part_line1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ <include layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="15dp"
+ android:layout_marginTop="4dp"/>
<!-- We can't have another vertical linear layout here with weight != 0 so this forces us to
put the badge on the first line. -->
@@ -43,7 +48,6 @@
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:orientation="horizontal"
>
<TextView android:id="@+id/inbox_text0"
@@ -69,7 +73,6 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
@@ -79,7 +82,6 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
@@ -89,7 +91,6 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
@@ -99,7 +100,6 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
@@ -109,7 +109,6 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
@@ -119,27 +118,15 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
android:layout_weight="1"
/>
- <TextView android:id="@+id/inbox_more"
- android:textAppearance="@style/TextAppearance.Material.Notification"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_marginEnd="8dp"
- android:singleLine="true"
- android:ellipsize="end"
- android:visibility="gone"
- android:layout_weight="1"
- android:text="@android:string/notification_inbox_ellipsis"
- />
<FrameLayout
android:id="@+id/inbox_end_pad"
android:layout_width="match_parent"
- android:layout_height="10dp"
+ android:layout_height="13dp"
android:visibility="gone"
android:layout_weight="0"
/>
@@ -148,27 +135,10 @@
android:layout_height="1dip"
android:id="@+id/action_divider"
android:visibility="gone"
+ android:layout_marginStart="-16dp"
+ android:layout_marginEnd="-16dp"
android:background="@drawable/notification_template_divider" />
- <include
- layout="@layout/notification_material_action_list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="-8dp"
- android:layout_marginRight="-8dp"
- android:layout_weight="0"
- />
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:id="@+id/overflow_divider"
- android:visibility="visible"
- android:background="@drawable/notification_template_divider" />
- <include
- layout="@layout/notification_template_part_line3"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="10dp" />
+ <include layout="@layout/notification_material_action_list" />
</LinearLayout>
+ <include layout="@layout/notification_template_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index f6c22c8..dc4afb8 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -15,40 +15,52 @@
~ limitations under the License
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout
android:id="@+id/status_bar_latest_event_content"
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="64dp"
- android:orientation="horizontal"
- android:background="#00000000"
+ android:layout_height="wrap_content"
android:tag="media"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- android:layout_weight="0"
- />
+ <include layout="@layout/notification_template_header"
+ android:layout_width="fill_parent"
+ android:layout_height="48dp"
+ android:layout_marginEnd="106dp"/>
<LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="fill_vertical"
- android:minHeight="@dimen/notification_large_icon_height"
- android:orientation="vertical"
- >
- <include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
- <include layout="@layout/notification_template_part_line3" />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/media_actions"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginEnd="6dp"
- android:layout_gravity="center_vertical|end"
+ android:layout_height="@dimen/notification_min_content_height"
+ android:background="#00000000"
android:orientation="horizontal"
- android:layoutDirection="ltr"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:layout_marginEnd="72dp"
+ android:tag="media"
>
- <!-- media buttons will be added here -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="fill_vertical"
+ android:layout_weight="1"
+ android:minHeight="@dimen/notification_min_content_height"
+ android:orientation="vertical"
+ >
+ <include layout="@layout/notification_template_part_line1"/>
+ <include layout="@layout/notification_template_progress"/>
+ <include layout="@layout/notification_template_part_line3"/>
+ </LinearLayout>
+ <LinearLayout
+ android:id="@+id/media_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="bottom|end"
+ android:layout_marginStart="10dp"
+ android:layout_marginBottom="8dp"
+ android:layoutDirection="ltr"
+ android:orientation="horizontal"
+ >
+ <!-- media buttons will be added here -->
+ </LinearLayout>
</LinearLayout>
-</LinearLayout>
+ <include layout="@layout/notification_template_right_icon" />
+</FrameLayout>
diff --git a/core/res/res/layout/notification_template_part_chronometer.xml b/core/res/res/layout/notification_template_part_chronometer.xml
index 1f0430e..c5ffbea 100644
--- a/core/res/res/layout/notification_template_part_chronometer.xml
+++ b/core/res/res/layout/notification_template_part_chronometer.xml
@@ -18,9 +18,7 @@
android:textAppearance="@style/TextAppearance.Material.Notification.Time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center"
+ android:layout_marginEnd="4dp"
android:layout_weight="0"
android:singleLine="true"
- android:gravity="center"
- android:paddingStart="8dp"
/>
diff --git a/core/res/res/layout/notification_template_part_line1.xml b/core/res/res/layout/notification_template_part_line1.xml
index 78bc1ed..e7ac408 100644
--- a/core/res/res/layout/notification_template_part_line1.xml
+++ b/core/res/res/layout/notification_template_part_line1.xml
@@ -19,30 +19,25 @@
android:id="@+id/line1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
android:orientation="horizontal"
+ android:layout_marginBottom="1dp"
>
<TextView android:id="@+id/title"
android:textAppearance="@style/TextAppearance.Material.Notification.Title"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
- android:layout_weight="1"
/>
- <ViewStub android:id="@+id/time"
- android:layout_width="wrap_content"
+ <TextView android:id="@+id/text_line_1"
+ android:textAppearance="@style/TextAppearance.Material.Notification"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="0"
- android:visibility="gone"
- android:layout="@layout/notification_template_part_time"
- />
- <ViewStub android:id="@+id/chronometer"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="0"
- android:visibility="gone"
- android:layout="@layout/notification_template_part_chronometer"
+ android:gravity="end|bottom"
+ android:layout_marginStart="16dp"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
/>
</LinearLayout>
diff --git a/core/res/res/layout/notification_template_part_line2.xml b/core/res/res/layout/notification_template_part_line2.xml
deleted file mode 100644
index db43271..0000000
--- a/core/res/res/layout/notification_template_part_line2.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2014 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- >
- <TextView
- android:id="@+id/text2"
- android:textAppearance="@style/TextAppearance.Material.Notification.Line2"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="-1dp"
- android:layout_marginBottom="-1dp"
- android:singleLine="true"
- android:fadingEdge="horizontal"
- android:ellipsize="marquee"
- android:visibility="gone"
- android:layout_weight="1"
- />
- <ImageView android:id="@+id/profile_badge_line2"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_weight="0"
- android:layout_marginStart="4dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
- </LinearLayout>
- <ViewStub
- android:id="@android:id/progress"
- android:layout="@layout/notification_template_progressbar"
- android:layout_width="match_parent"
- android:layout_height="15dp"
- android:layout_marginEnd="8dp"
- android:visibility="gone"
- android:layout_weight="0"
- />
-</merge>
diff --git a/core/res/res/layout/notification_template_part_line3.xml b/core/res/res/layout/notification_template_part_line3.xml
index da3c5c5..76337ac 100644
--- a/core/res/res/layout/notification_template_part_line3.xml
+++ b/core/res/res/layout/notification_template_part_line3.xml
@@ -19,7 +19,6 @@
android:id="@+id/line3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
android:orientation="horizontal"
android:gravity="center_vertical"
>
@@ -33,16 +32,6 @@
android:ellipsize="marquee"
android:fadingEdge="horizontal"
/>
- <TextView android:id="@+id/info"
- android:textAppearance="@style/TextAppearance.Material.Notification.Info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_weight="0"
- android:singleLine="true"
- android:gravity="center"
- android:paddingStart="8dp"
- />
<ImageView android:id="@+id/profile_badge_line3"
android:layout_width="@dimen/notification_badge_size"
android:layout_height="@dimen/notification_badge_size"
diff --git a/core/res/res/layout/notification_template_part_time.xml b/core/res/res/layout/notification_template_part_time.xml
index 37c7ebe..442ff8c 100644
--- a/core/res/res/layout/notification_template_part_time.xml
+++ b/core/res/res/layout/notification_template_part_time.xml
@@ -19,8 +19,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
+ android:layout_marginEnd="4dp"
android:layout_weight="0"
android:singleLine="true"
- android:gravity="center"
- android:paddingStart="8dp"
/>
diff --git a/core/res/res/drawable/notification_icon_legacy_bg.xml b/core/res/res/layout/notification_template_progress.xml
similarity index 64%
rename from core/res/res/drawable/notification_icon_legacy_bg.xml
rename to core/res/res/layout/notification_template_progress.xml
index cc5755d..85532ad 100644
--- a/core/res/res/drawable/notification_icon_legacy_bg.xml
+++ b/core/res/res/layout/notification_template_progress.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2014 The Android Open Source Project
+ ~ Copyright (C) 2015 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,9 +14,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <solid
- android:color="@color/notification_icon_bg_color"/>
-</shape>
+<ViewStub
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/progress"
+ android:layout="@layout/notification_template_progressbar"
+ android:layout_width="match_parent"
+ android:layout_height="15dp"
+ android:visibility="gone"
+ />
diff --git a/core/res/res/drawable/notification_icon_legacy_bg.xml b/core/res/res/layout/notification_template_right_icon.xml
similarity index 62%
copy from core/res/res/drawable/notification_icon_legacy_bg.xml
copy to core/res/res/layout/notification_template_right_icon.xml
index cc5755d..3b358ab 100644
--- a/core/res/res/drawable/notification_icon_legacy_bg.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2014 The Android Open Source Project
+ ~ Copyright (C) 2015 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,8 +15,13 @@
~ limitations under the License
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <solid
- android:color="@color/notification_icon_bg_color"/>
-</shape>
+<ImageView android:id="@+id/right_icon" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginTop="32dp"
+ android:layout_gravity="top|end"
+ android:scaleType="centerCrop"
+ />
+
+
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 669eba1..c3f7afc 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Stembystand"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Sluit nou"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Veiligmodus"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-stelsel"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Persoonlik"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index a7af0f1..4de0097 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"የድምጽ እርዳታ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"አሁን ቆልፍ"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"የሚያስተማምን ሁነታ"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android ስርዓት"</string>
<string name="user_owner_label" msgid="2804351898001038951">"የግል"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 4a9b163..0c05bc4 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -226,6 +226,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"المساعد الصوتي"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"قفل الآن"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"الوضع الآمن"</string>
<string name="android_system_label" msgid="6577375335728551336">"نظام Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"شخصي"</string>
@@ -1470,8 +1472,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"المطالبة برقم التعريف الشخصي قبل إزالة التثبيت"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"المطالبة بنقش إلغاء القفل قبل إزالة التثبيت"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"المطالبة بكلمة المرور قبل إزالة التثبيت"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"التطبيق غير قابل لتغيير الحجم، يمكنك تمريره بإصبعين."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"تم تثبيت الحزمة عن طريق المشرف"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"تم التحديث بواسطة المشرف"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"تم حذف الحزمة عن طريق المشرف"</string>
diff --git a/core/res/res/values-az-rAZ/strings.xml b/core/res/res/values-az-rAZ/strings.xml
index 54dbc39e..593400e 100644
--- a/core/res/res/values-az-rAZ/strings.xml
+++ b/core/res/res/values-az-rAZ/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Səs Yardımçısı"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"İndi kilidləyin"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Təhlükəsiz rejim"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android sistemi"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Şəxsi"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 233d709..50f2938 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Гласова помощ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Заключване сега"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Безопасен режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Системно от Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Личен"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Запитване за ПИН код преди освобождаване"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Запитване за фигура за отключване преди освобождаване"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Запитване за парола преди освобождаване"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Приложението не може да се преоразмерява. Превъртете го с два пръста."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Инсталирано от администратора ви"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Актуализирано от администратора ви"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Изтрито от администратора ви"</string>
diff --git a/core/res/res/values-bn-rBD/strings.xml b/core/res/res/values-bn-rBD/strings.xml
index f5fb017..fdd46172 100644
--- a/core/res/res/values-bn-rBD/strings.xml
+++ b/core/res/res/values-bn-rBD/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ভয়েস সহায়তা"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"এখনই লক করুন"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"৯৯৯+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"নিরাপদ মোড"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android সিস্টেম"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ব্যক্তিগত"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 0e731f6..cb24d26 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Assist. per veu"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloqueja ara"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"+999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mode segur"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index fd3ed4b..c2f1364 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Hlas. asistence"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Zamknout"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Nouzový režim"</string>
<string name="android_system_label" msgid="6577375335728551336">"Systém Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Osobní"</string>
@@ -355,7 +357,7 @@
<string name="permlab_vibrate" msgid="7696427026057705834">"ovládání vibrací"</string>
<string name="permdesc_vibrate" msgid="6284989245902300945">"Umožňuje aplikaci ovládat vibrace."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"ovládání kontrolky"</string>
- <string name="permdesc_flashlight" msgid="6522284794568368310">"Umožňuje aplikaci ovládat baterku."</string>
+ <string name="permdesc_flashlight" msgid="6522284794568368310">"Umožňuje aplikaci ovládat svítilnu."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"přímé volání na telefonní čísla"</string>
<string name="permdesc_callPhone" msgid="3740797576113760827">"Umožňuje aplikaci volat na telefonní čísla bez vašeho přičinění. Může mít za následek neočekávané poplatky nebo hovory. Toto oprávnění neumožňuje aplikaci volat na tísňová čísla. Škodlivé aplikace vás mohou připravit o peníze uskutečňováním hovorů bez vašeho svolení."</string>
<string name="permlab_accessImsCallService" msgid="3574943847181793918">"přístup ke službě zasílání rychlých zpráv pro účely hovorů"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 646f734..42cb42b 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Taleassistent"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lås nu"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Sikker tilstand"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personlig"</string>
@@ -1434,8 +1435,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Bed om pinkode inden frigørelse"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Bed om oplåsningsmønster ved deaktivering"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Bed om adgangskode inden frigørelse"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Appens størrelse kan ikke ændres. Gennemgå den ved at rulle med to fingre."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installeret af din administrator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Opdateret af administrator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Slettet af din administrator"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index f8b78c0..d52c728 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Sprachassistent"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Jetzt sperren"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Abgesicherter Modus"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-System"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Privat"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 7a7cce1..f86c2fd 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Φων.υποβοηθ."</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Κλείδωμα τώρα"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Ασφαλής λειτουργία"</string>
<string name="android_system_label" msgid="6577375335728551336">"Σύστημα Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Προσωπικό"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Να γίνεται ερώτηση για το PIN, πριν από το ξεκαρφίτσωμα"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Να γίνεται ερώτηση για το μοτίβο ξεκλειδώματος, πριν από το ξεκαρφίτσωμα"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Να γίνεται ερώτηση για τον κωδικό πρόσβασης, πριν από το ξεκαρφίτσωμα"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Το μέγεθος της εφαρμογής δεν είναι προσαρμόσιμο. Σύρετε προς τα κάτω με δύο δάχτυλα."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Εγκαταστάθηκε από το διαχειριστή σας"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Ενημερώθηκε από το διαχειριστή σας"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Διαγράφηκε από το διαχειριστή σας"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index c66c55a..e23f8d5 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lock now"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index c66c55a..e23f8d5 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lock now"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index c66c55a..e23f8d5 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lock now"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 0168d4f..b442bc1f 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Asistente voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear ahora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Solicitar PIN para quitar fijación"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Solicitar patrón de desbloqueo para quitar fijación"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Solicitar contraseña para quitar fijación"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"No se puede modificar el tamaño de la app, desplázala con dos dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Lo instaló el administrador."</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Actualizado por el administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Lo eliminó el administrador."</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 04cdfe0..acbef23 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Asistente voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear ahora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"> 999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
@@ -243,7 +245,7 @@
<string name="permgrouplab_phone" msgid="5229115638567440675">"Teléfono"</string>
<string name="permgroupdesc_phone" msgid="6234224354060641055">"hacer y administrar llamadas de teléfono"</string>
<string name="permgrouplab_sensors" msgid="416037179223226722">"Sensores corporales"</string>
- <string name="permgroupdesc_sensors" msgid="7147968539346634043">"acceder a datos del sensor sobre tus constantes vitales"</string>
+ <string name="permgroupdesc_sensors" msgid="7147968539346634043">"acceder a datos de sensores de tus constantes vitales"</string>
<string name="capability_title_canRetrieveWindowContent" msgid="3901717936930170320">"Recuperar el contenido de la ventana"</string>
<string name="capability_desc_canRetrieveWindowContent" msgid="3772225008605310672">"Inspecciona el contenido de una ventana con la que estés interactuando."</string>
<string name="capability_title_canRequestTouchExploration" msgid="3108723364676667320">"Activar la exploración táctil"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Solicitar PIN para desactivar"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Solicitar patrón de desbloqueo para desactivar"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Solicitar contraseña para desactivar"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"No se puede cambiar el tamaño de la aplicación: desplázala con dos dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalado por tu administrador"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Actualizado por tu administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Eliminado por tu administrador"</string>
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index 5898ca0..9a96683 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Häälabi"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lukusta kohe"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Turvarežiim"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-süsteem"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Isiklik"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Enne vabastamist küsi PIN-koodi"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Enne vabastamist küsi avamismustrit"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Enne vabastamist küsi parooli"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Rakenduse suurust ei saa muuta. Kerige kahe sõrmega."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installis teie administraator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Värskendas administraator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Kustutas teie administraator"</string>
diff --git a/core/res/res/values-eu-rES/strings.xml b/core/res/res/values-eu-rES/strings.xml
index 0986b70..a30c437 100644
--- a/core/res/res/values-eu-rES/strings.xml
+++ b/core/res/res/values-eu-rES/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ahots-laguntza"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Blokeatu"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modu segurua"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android sistema"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Pertsonalak"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 689e9e0..8672171 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -191,7 +191,7 @@
<string name="reboot_to_update_prepare" msgid="6305853831955310890">"آمادهسازی برای بهروزرسانی…"</string>
<string name="reboot_to_update_package" msgid="3871302324500927291">"در حال پردازش بستهبندی بهروز…"</string>
<string name="reboot_to_update_reboot" msgid="6428441000951565185">"در حال راهاندازی مجدد…"</string>
- <string name="reboot_to_reset_title" msgid="4142355915340627490">"بازنشانی به دادههای کارخانه"</string>
+ <string name="reboot_to_reset_title" msgid="4142355915340627490">"بازنشانی دادههای کارخانه"</string>
<string name="reboot_to_reset_message" msgid="2432077491101416345">"در حال راهاندازی مجدد…"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"در حال خاموش شدن…"</string>
<string name="shutdown_confirm" product="tablet" msgid="3385745179555731470">"رایانهٔ لوحی شما خاموش میشود."</string>
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"دستیار صوتی"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"اکنون قفل شود"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"بیشتر از 999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"حالت ایمن"</string>
<string name="android_system_label" msgid="6577375335728551336">"سیستم Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"شخصی"</string>
@@ -511,9 +513,9 @@
<string name="policylab_forceLock" msgid="2274085384704248431">"قفل کردن صفحه"</string>
<string name="policydesc_forceLock" msgid="1141797588403827138">"نحوه و زمان قفل شدن صفحه را کنترل میکند."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"پاک کردن تمام دادهها"</string>
- <string name="policydesc_wipeData" product="tablet" msgid="4306184096067756876">"با انجام بازنشانی به دادههای کارخانه، دادههای رایانهٔ لوحی بدون هشدار پاک میشود."</string>
- <string name="policydesc_wipeData" product="tv" msgid="5816221315214527028">"دادههای تلویزیون را بدون هشدار با انجام بازنشانی به داده کارخانه پاک کنید."</string>
- <string name="policydesc_wipeData" product="default" msgid="5096895604574188391">"با انجام بازنشانی به دادههای کارخانه، دادههای تلفن بدون هشدار پاک میشود."</string>
+ <string name="policydesc_wipeData" product="tablet" msgid="4306184096067756876">"با انجام بازنشانی دادههای کارخانه، دادههای رایانهٔ لوحی بدون هشدار پاک میشود."</string>
+ <string name="policydesc_wipeData" product="tv" msgid="5816221315214527028">"دادههای تلویزیون را بدون هشدار با انجام بازنشانی دادههای کارخانه پاک کنید."</string>
+ <string name="policydesc_wipeData" product="default" msgid="5096895604574188391">"با انجام بازنشانی دادههای کارخانه، دادههای تلفن بدون هشدار پاک میشود."</string>
<string name="policylab_wipeData_secondaryUser" msgid="8362863289455531813">"پاک کردن دادههای کاربر"</string>
<string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="6336255514635308054">"دادههای این کاربر را در این رایانه لوحی بدون هشدار پاک میکند."</string>
<string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2086473496848351810">"دادههای این کاربر را در این تلویزیون بدون هشدار پاک میکند."</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"درخواست کد پین قبل از برداشتن پین"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"درخواست الگوی باز کردن قفل قبل از برداشتن پین"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"درخواست گذرواژه قبل از برداشتن پین"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"اندازه برنامه قابل تغییر نیست، با دو انگشت آن را پیمایش کنید."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"توسط سرپرستتان نصب شد"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"توسط سرپرست شما بهروزرسانی شد"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"توسط سرپرستتان حذف شد"</string>
@@ -1484,7 +1485,7 @@
<string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"آخر هفته"</string>
<string name="zen_mode_default_events_name" msgid="8158334939013085363">"رویداد"</string>
<string name="muted_by" msgid="6147073845094180001">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> آن را بیصدا کرد"</string>
- <string name="system_error_wipe_data" msgid="6608165524785354962">"دستگاهتان یک مشکل داخلی دارد، و ممکن است تا زمانی که بازنشانی به داده کارخانه انجام نگیرد، بیثبات بماند."</string>
+ <string name="system_error_wipe_data" msgid="6608165524785354962">"دستگاهتان یک مشکل داخلی دارد، و ممکن است تا زمانی که بازنشانی دادههای کارخانه انجام نگیرد، بیثبات بماند."</string>
<string name="system_error_manufacturer" msgid="8086872414744210668">"دستگاهتان یک مشکل داخلی دارد. برای جزئیات آن با سازندهتان تماس بگیرید."</string>
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"درخواست USSD به درخواست DIAL اصلاح میشود."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"درخواست USSD به درخواست SS اصلاح میشود."</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 8af6e56..8100305 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ääniapuri"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lukitse nyt"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Suojattu tila"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-järjestelmä"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Henkilökoht."</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pyydä PIN ennen irrotusta"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pyydä lukituksenpoistokuvio ennen irrotusta"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pyydä salasana ennen irrotusta"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Sovelluksen koko ei muutu. Vieritä näkymää kahdella sormella."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Järjestelmänvalvoja on asentanut paketin."</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Järjestelmänvalvojasi on päivittänyt paketin."</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Järjestelmänvalvoja on poistanut paketin."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index e2daae6..b273980 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Assist. vocale"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Verrouiller"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">">999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mode sécurisé"</string>
<string name="android_system_label" msgid="6577375335728551336">"Système Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personnel"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Demander le NIP avant d\'annuler l\'épinglage"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Demander le schéma de déverrouillage avant d\'annuler l\'épinglage"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Demander le mot de passe avant d\'annuler l\'épinglage"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Impossible de redimensionner l\'application. Faites-la défiler avec deux doigts."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installé par votre administrateur"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Mis à jour par votre administrateur"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Supprimé par votre administrateur"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 5de0971..850c18a 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Assistance vocale"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Verrouiller"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">">999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mode sécurisé"</string>
<string name="android_system_label" msgid="6577375335728551336">"Système Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personnel"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Demander le code PIN avant d\'annuler l\'épinglage"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Demander le schéma de déverrouillage avant d\'annuler l\'épinglage"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Demander le mot de passe avant d\'annuler l\'épinglage"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Il est impossible de redimensionner l\'application. Faites-la défiler avec deux doigts."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installé par votre administrateur"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Mis à jour par votre administrateur"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Supprimé par votre administrateur"</string>
diff --git a/core/res/res/values-gl-rES/strings.xml b/core/res/res/values-gl-rES/strings.xml
index ba25f89..998f494 100644
--- a/core/res/res/values-gl-rES/strings.xml
+++ b/core/res/res/values-gl-rES/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Asistente voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear agora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">">999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Persoal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Solicitar un PIN antes de soltar a pantalla"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Solicitar un padrón de desbloqueo antes de soltar a pantalla"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Solicitar un contrasinal antes de soltar a pantalla"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Non se pode cambiar o tamaño da aplicación. Desprázate por ela con dous dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalado polo administrador"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Actualizado polo administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Eliminado polo administrador"</string>
diff --git a/core/res/res/values-gu-rIN/strings.xml b/core/res/res/values-gu-rIN/strings.xml
index ce05ef5..7cfd200 100644
--- a/core/res/res/values-gu-rIN/strings.xml
+++ b/core/res/res/values-gu-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"વૉઇસ સહાય"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"હવે લૉક કરો"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"સુરક્ષિત મોડ"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android સિસ્ટમ"</string>
<string name="user_owner_label" msgid="2804351898001038951">"વ્યક્તિગત"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"અનપિન કરતાં પહેલાં PIN માટે પૂછો"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"અનપિન કરતા પહેલાં અનલૉક પેટર્ન માટે પૂછો"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"અનપિન કરતાં પહેલાં પાસવર્ડ માટે પૂછો"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"ઍપ્લિકેશનનું કદ બદલવા યોગ્ય નથી, બે આંગળીઓ વડે તેને સ્ક્રોલ કરો."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"તમારા વ્યવસ્થાપક દ્વારા ઇન્સ્ટોલ કરેલ"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"તમારા વ્યવસ્થાપક દ્વારા અપડેટ થયેલ"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"તમારા વ્યવસ્થાપક દ્વારા કાઢી નાખેલ"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 47fdd28..a001211 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"वॉइस सहायक"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"अभी लॉक करें"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"सुरक्षित मोड"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android सिस्टम"</string>
<string name="user_owner_label" msgid="2804351898001038951">"व्यक्तिगत"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 9bc0267..55399af 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -223,6 +223,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Glasovna pomoć"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Zaključaj sada"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Siguran način rada"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sustav Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Osobno"</string>
@@ -1443,8 +1445,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Traži PIN radi otkvačivanja"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Traži uzorak za otključavanje radi otkvačivanja"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Traži zaporku radi otkvačivanja"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Veličina aplikacije ne može se mijenjati, pomičite je s dva prsta."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalirao administrator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Ažurira vaš administrator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Izbrisao administrator"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 1c0257f..dc87943 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Hangsegéd"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Zárolás most"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Biztonsági üzemmód"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android rendszer"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Személyes"</string>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index 02c6bed..f1d32d8 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ձայնային օգնութ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Կողպել հիմա"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Անվտանգ ռեժիմ"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android համակարգ"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Անձնական"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Ապաամրացնելուց առաջ հարցնել PIN-կոդը"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Ապաամրացնելուց առաջ հարցնել ապակողպող նախշը"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Ապաամրացնելուց առաջ հարցնել գաղտնաբառը"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Հավելվածի չափը հնարավոր չէ փոխել, ոլորեք այն երկու մատի օգնությամբ:"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Ադմինիստրատորը տեղադրել է այն"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Ադմինիստրատորը թարմացրել է այն"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Ադմինիստրատորը ջնջել է այն"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 7346d31..8e18dae 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Bantuan Suara"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Kunci sekarang"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mode aman"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistem Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Pribadi"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Meminta PIN sebelum melepas sematan"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Meminta pola pembukaan kunci sebelum melepas sematan"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Meminta sandi sebelum melepas sematan"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Aplikasi tidak dapat diubah ukurannya, gulir dengan dua jari."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Dipasang oleh administrator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Diperbarui oleh administrator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Dihapus oleh administrator"</string>
diff --git a/core/res/res/values-is-rIS/strings.xml b/core/res/res/values-is-rIS/strings.xml
index c6ec5b1..b2c98f0 100644
--- a/core/res/res/values-is-rIS/strings.xml
+++ b/core/res/res/values-is-rIS/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Raddaðstoð"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Læsa núna"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Örugg stilling"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android kerfið"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Persónulegt"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Biðja um PIN-númer til að losa"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Biðja um opnunarmynstur til að losa"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Biðja um aðgangsorð til að losa"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Ekki er hægt að breyta stærð forritsins, flettu upp og niður með tveimur fingrum."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Uppsett af kerfisstjóra"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Uppfært af kerfisstjóranum"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Eytt af kerfisstjóra"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index c4ecbad..b36e2e5 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Blocca ora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Modalità provvisoria"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personale"</string>
@@ -1434,8 +1435,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Richiedi il PIN per lo sblocco"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Richiedi sequenza di sblocco prima di sbloccare"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Richiedi password prima di sbloccare"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Non è posssibile ridimensionare l\'app: scorri con due dita."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installato dall\'amministratore"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Aggiornato dall\'amministratore"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Eliminato dall\'amministratore"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 55fa332..cf4ca52 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"נעל עכשיו"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"מצב בטוח"</string>
<string name="android_system_label" msgid="6577375335728551336">"מערכת Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"אישי"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"בקש PIN לפני ביטול הצמדה"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"בקש קו ביטול נעילה לפני ביטול הצמדה"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"בקש סיסמה לפני ביטול הצמדה"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"אין אפשרות לשנות את גודל האפליקציה, גלול אותה בשתי אצבעות."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"הותקנה על ידי מנהל המערכת שלך"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"עודכן על ידי מנהל המערכת שלך"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"נמחקה על ידי מנהל המערכת שלך"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index a74d5b7..1ebf699 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"音声アシスト"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"今すぐロック"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g> 件)"</string>
<string name="safeMode" msgid="2788228061547930246">"セーフモード"</string>
<string name="android_system_label" msgid="6577375335728551336">"Androidシステム"</string>
<string name="user_owner_label" msgid="2804351898001038951">"個人用"</string>
@@ -1434,8 +1435,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"オフライン再生を解除する前にPINの入力を求める"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"画面固定を解除する前にロック解除パターンの入力を求める"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"オフライン再生を解除する前にパスワードの入力を求める"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"アプリのサイズは変更できません。2 本の指でスクロールしてください。"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"管理者によってインストールされました"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"管理者によって更新されています"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"管理者によって削除されました"</string>
diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index d37f6099..604323a 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ხმოვანი ასისტ."</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ახლა ჩაკეტვა"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"უსაფრთხო რეჟიმი"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-ის სისტემა"</string>
<string name="user_owner_label" msgid="2804351898001038951">"პირადი"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"ფიქსაციის მოხსნამდე PIN-ის მოთხოვნა"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"ფიქსაციის მოხსნამდე განბლოკვის ნიმუშის მოთხოვნა"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"ფიქსაციის მოხსნამდე პაროლის მოთხოვნა"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"აპის ზომა ვერ შეიცვლება. გადაადგილდით მასში ორი თითის მეშვეობით."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"თქვენი ადმინისტრატორის მიერ დაყენებული"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"განახლებულია თქვენი ადმინისტრატორის მიერ"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"თქვენი ადმინისტრატორის მიერ წაშლილი"</string>
diff --git a/core/res/res/values-kk-rKZ/strings.xml b/core/res/res/values-kk-rKZ/strings.xml
index 0a79c3b..323be5d 100644
--- a/core/res/res/values-kk-rKZ/strings.xml
+++ b/core/res/res/values-kk-rKZ/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Дауыс көмекшісі"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Қазір бекіту"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Қауіпсіз режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android жүйесі"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Жеке"</string>
@@ -1305,7 +1307,7 @@
<string name="enable_accessibility_canceled" msgid="3833923257966635673">"Қол жетімділік өшірілді."</string>
<string name="user_switched" msgid="3768006783166984410">"Ағымдағы пайдаланушы <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="2871009331809089783">"<xliff:g id="NAME">%1$s</xliff:g> ауысу орындалуда…"</string>
- <string name="user_logging_out_message" msgid="8939524935808875155">"<xliff:g id="NAME">%1$s</xliff:g> ішінен шығу орындалуда…"</string>
+ <string name="user_logging_out_message" msgid="8939524935808875155">"<xliff:g id="NAME">%1$s</xliff:g> ішінен шығу…"</string>
<string name="owner_name" msgid="2716755460376028154">"Пайдаланушы"</string>
<string name="error_message_title" msgid="4510373083082500195">"Қателік"</string>
<string name="error_message_change_not_allowed" msgid="1347282344200417578">"Бұл өзгертуге әкімші рұқсат етпеген"</string>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index 21b1dc6..c00bc14 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ជំនួយសម្លេង"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ចាក់សោឥឡូវនេះ"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"របៀបសុវត្ថិភាព"</string>
<string name="android_system_label" msgid="6577375335728551336">"ប្រព័ន្ធ Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ផ្ទាល់ខ្លួន"</string>
@@ -1436,8 +1438,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"សួររកកូដ PIN មុនពេលផ្ដាច់"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"សួររកលំនាំដោះសោមុនពេលផ្ដាច់"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"សួររកពាក្យសម្ងាត់មុនពេលផ្ដាច់"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"កម្មវិធីមិនអាចផ្លាស់ប្តូរទំហំបានទេ សូមរមូរវាដោយប្រើម្រាមដៃពីរ។"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"បានដំឡើងដោយអ្នកគ្រប់គ្រងរបស់អ្នក"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"បានធ្វើបច្ចុប្បន្នភាពដោយអ្នកគ្រប់គ្រងរបស់អ្នក"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"បានលុបដោយអ្នកគ្រប់គ្រងរបស់អ្នក"</string>
diff --git a/core/res/res/values-kn-rIN/strings.xml b/core/res/res/values-kn-rIN/strings.xml
index e828ce0..1aba0bb 100644
--- a/core/res/res/values-kn-rIN/strings.xml
+++ b/core/res/res/values-kn-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ಧ್ವನಿ ಸಹಾಯಕ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ಈಗ ಲಾಕ್ ಮಾಡಿ"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"ಸುರಕ್ಷಿತ ಮೋಡ್"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android ಸಿಸ್ಟಂ"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ವೈಯಕ್ತಿಕ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 8ed4a6f..3384f09 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"음성 지원"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"지금 잠그기"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"안전 모드"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android 시스템"</string>
<string name="user_owner_label" msgid="2804351898001038951">"개인"</string>
@@ -229,7 +231,7 @@
<string name="permgrouplab_contacts" msgid="3657758145679177612">"주소록"</string>
<string name="permgroupdesc_contacts" msgid="6951499528303668046">"주소록 액세스"</string>
<string name="permgrouplab_location" msgid="7275582855722310164">"위치"</string>
- <string name="permgroupdesc_location" msgid="1346617465127855033">"이 기기의 위치에 액세스하기"</string>
+ <string name="permgroupdesc_location" msgid="1346617465127855033">"이 기기의 위치에 액세스"</string>
<string name="permgrouplab_calendar" msgid="5863508437783683902">"캘린더"</string>
<string name="permgroupdesc_calendar" msgid="3889615280211184106">"캘린더 액세스"</string>
<string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
@@ -239,7 +241,7 @@
<string name="permgrouplab_microphone" msgid="171539900250043464">"마이크"</string>
<string name="permgroupdesc_microphone" msgid="4988812113943554584">"오디오 녹음"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"카메라"</string>
- <string name="permgroupdesc_camera" msgid="3250611594678347720">"사진 찍기 및 동영상 녹화"</string>
+ <string name="permgroupdesc_camera" msgid="3250611594678347720">"사진 및 동영상 촬영"</string>
<string name="permgrouplab_phone" msgid="5229115638567440675">"전화"</string>
<string name="permgroupdesc_phone" msgid="6234224354060641055">"전화 걸기 및 관리"</string>
<string name="permgrouplab_sensors" msgid="416037179223226722">"신체 센서"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"고정 해제 이전에 PIN 요청"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"고정 해제 이전에 잠금해제 패턴 요청"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"고정 해제 이전에 비밀번호 요청"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"앱에서 크기 조절이 불가능합니다. 두 손가락을 사용해 스크롤하세요."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"관리자가 설치함"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"관리자에 의해 업데이트됨"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"관리자가 삭제함"</string>
diff --git a/core/res/res/values-ky-rKG/strings.xml b/core/res/res/values-ky-rKG/strings.xml
index 8381c32..870c858 100644
--- a/core/res/res/values-ky-rKG/strings.xml
+++ b/core/res/res/values-ky-rKG/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Үн жардамчысы"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Азыр кулпулоо"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Коопсуз режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android Тутуму"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Жеке"</string>
@@ -1435,8 +1437,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Бошотуудан мурун PIN суралсын"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Бошотуудан мурун кулпуну ачкан үлгү суралсын"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Бошотуудан мурун сырсөз суралсын"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Колдонмонун көлөмүн өзгөртүүгө болбойт, андыктан эки манжаңыз менен сыдырып караңыз."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Администраторуңуз тарабынан орнотулган"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Администраторуңуз жаңырткан"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Администраторуңуз тарабынан жок кылынган"</string>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index 9884bf1..d5484d3 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ຊ່ວຍເຫຼືອທາງສຽງ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ລັອກດຽວນີ້"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
<string name="android_system_label" msgid="6577375335728551336">"ລະບົບ Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ສ່ວນໂຕ"</string>
@@ -1305,7 +1307,7 @@
<string name="enable_accessibility_canceled" msgid="3833923257966635673">"ຍົກເລີກໂຕຊ່ວຍການເຂົ້າເຖິງແລ້ວ."</string>
<string name="user_switched" msgid="3768006783166984410">"ຜູ່ໃຊ້ປັດຈຸບັນ <xliff:g id="NAME">%1$s</xliff:g> ."</string>
<string name="user_switching_message" msgid="2871009331809089783">"ກຳລັງສະລັບໄປຫາ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
- <string name="user_logging_out_message" msgid="8939524935808875155">"ກຳລັງອອກຈາກລະບົບ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
+ <string name="user_logging_out_message" msgid="8939524935808875155">"ກຳລັງອອກຈາກລະບົບ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="2716755460376028154">"ເຈົ້າຂອງ"</string>
<string name="error_message_title" msgid="4510373083082500195">"ຜິດພາດ"</string>
<string name="error_message_change_not_allowed" msgid="1347282344200417578">"ຜູ່ເບິ່ງແຍງລະບົບຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ປ່ຽນແປງສິ່ງນີ້"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"ຖາມຫາ PIN ກ່ອນຍົກເລີກການປັກໝຸດ"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"ຖາມຫາຮູບແບບປົດລັອກກ່ອນຍົກເລີກການປັກໝຸດ"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"ຖາມຫາລະຫັດຜ່ານກ່ອນຍົກເລີກການປັກໝຸດ"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"ບໍ່ສາມາດປັບຂະໜາດແອັບຯໄດ້, ກະລຸນາເລື່ອນມັນໂດຍໃຊ້ນິ້ວສອງນິ້ວແທນ."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"ຜູ້ຄວບຄຸມຂອງທ່ານຕິດຕັ້ງໃສ່ແລ້ວ"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"ອັບເດດໂດຍຜູ້ຄວບຄຸມຂອງທ່ານແລ້ວ"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"ຖືກຜູ້ຄວບຄຸມຂອງທ່ານລຶບໄປແລ້ວ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 1c326fe..6ee24cf 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Užrakinti dabar"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Saugos režimas"</string>
<string name="android_system_label" msgid="6577375335728551336">"„Android“ sistema"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Asmeninė"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Prašyti PIN kodo prieš atsegant"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Prašyti atrakinimo piešinio prieš atsegant"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Prašyti slaptažodžio prieš atsegant"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Programos dydis nekeičiamas, slinkite dviem pirštais."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Įdiegė administratorius"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Atnaujino administratorius"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Ištrynė administratorius"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 23be85a..7785965 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -223,6 +223,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Balss palīgs"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloķēt tūlīt"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"Pārsniedz"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Drošais režīms"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android sistēma"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personisks"</string>
diff --git a/core/res/res/values-mk-rMK/strings.xml b/core/res/res/values-mk-rMK/strings.xml
index cd2efc4a..eb90369 100644
--- a/core/res/res/values-mk-rMK/strings.xml
+++ b/core/res/res/values-mk-rMK/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Гласовна помош"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Заклучи сега"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Безбеден режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Систем Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Лични"</string>
@@ -1436,8 +1437,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Прашај за ПИН пред откачување"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Прашај за шема за отклучување пред откачување"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Прашај за лозинка пред откачување"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Не може да се промени големината на апликацијата. Движете ја со два прста."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Инсталирано од администраторот"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Ажурирано од администраторот"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Избришано од администраторот"</string>
diff --git a/core/res/res/values-ml-rIN/strings.xml b/core/res/res/values-ml-rIN/strings.xml
index c8eae7e..a83928e 100644
--- a/core/res/res/values-ml-rIN/strings.xml
+++ b/core/res/res/values-ml-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"വോയ്സ് സഹായം"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ഇപ്പോൾ ലോക്കുചെയ്യുക"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"സുരക്ഷിത മോഡ്"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android സിസ്റ്റം"</string>
<string name="user_owner_label" msgid="2804351898001038951">"വ്യക്തിഗതം"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"ചെയ്യുംമുമ്പ് പിൻ ചോദിക്കൂ"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"അൺപിൻ ചെയ്യുന്നതിനുമുമ്പ് അൺലോക്ക് പാറ്റേൺ ആവശ്യപ്പെടുക"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"അൺപിൻ ചെയ്യുന്നതിനുമുമ്പ് പാസ്വേഡ് ആവശ്യപ്പെടുക"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"ആപ്പിന്റെ വലുപ്പം ക്രമീകരിക്കാൻ കഴിയില്ല, രണ്ട് വിരലുകൾ ഉപയോഗിച്ച് അത് സ്ക്രോൾ ചെയ്യുക."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"നിങ്ങളുടെ അഡ്മിനിസ്ട്രേറ്റർ ഇൻസ്റ്റാളുചെയ്തു"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"നിങ്ങളുടെ അഡ്മിനിസ്ട്രേറ്റർ അപ്ഡേറ്റുചെയ്തു"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"നിങ്ങളുടെ അഡ്മിനിസ്ട്രേറ്റർ ഇല്ലാതാക്കി"</string>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index 7b7d4b1..a9fb8593 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Дуут туслах"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Одоо түгжих"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Аюулгүй горим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Андройд систем"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Хувийн"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Тогтоосныг суллахаас өмнө PIN асуух"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Тогтоосныг суллахаас өмнө түгжээ тайлах хээ асуух"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Тогтоосныг суллахаас өмнө нууц үг асуух"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Апп-н хэмжээ нь өөрчлөгддөггүй. Үүнийг 2 хуруугаараа гүйлгэнэ үү."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Таны админ суулгасан байна"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Танай админ шинэчилсэн"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Таны админ устгасан байна"</string>
diff --git a/core/res/res/values-mr-rIN/strings.xml b/core/res/res/values-mr-rIN/strings.xml
index 6ae6e82..a8896c3 100644
--- a/core/res/res/values-mr-rIN/strings.xml
+++ b/core/res/res/values-mr-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"व्हॉइस सहाय्य"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"आता लॉक करा"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"सुरक्षित मोड"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android सिस्टम"</string>
<string name="user_owner_label" msgid="2804351898001038951">"वैयक्तिक"</string>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index f354ea0..cababdd 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Bantuan Suara"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Kunci sekarang"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mod selamat"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistem Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Peribadi"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Minta PIN sebelum menyahsemat"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Minta corak buka kunci sebelum menyahsemat"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Minta kata laluan sebelum menyahsemat"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Apl tidak boleh tukar saiznya, tatal apl itu menggunakan dua jari."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Dipasang oleh pentadbir anda"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Dikemas kini oleh pentadbir anda"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Dipadamkan oleh pentadbir anda"</string>
diff --git a/core/res/res/values-my-rMM/strings.xml b/core/res/res/values-my-rMM/strings.xml
index 4a0a227..8e61f41 100644
--- a/core/res/res/values-my-rMM/strings.xml
+++ b/core/res/res/values-my-rMM/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"အသံ အကူအညီ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ယခု သော့ပိတ်ရန်"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"၉၉၉+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"အန္တရာယ်ကင်းမှု စနစ်(Safe mode)"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android စနစ်"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ကိုယ်ရေး"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 4fc7078..f29a903 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Talehjelp"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lås nå"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Sikkermodus"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personlig"</string>
@@ -235,7 +237,7 @@
<string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
<string name="permgroupdesc_sms" msgid="4656988620100940350">"sende og lese SMS-meldinger"</string>
<string name="permgrouplab_storage" msgid="1971118770546336966">"Lagring"</string>
- <string name="permgroupdesc_storage" msgid="637758554581589203">"åpne bilder, media og filer på enheten din"</string>
+ <string name="permgroupdesc_storage" msgid="637758554581589203">"åpne bilder, medier og filer på enheten din"</string>
<string name="permgrouplab_microphone" msgid="171539900250043464">"Mikrofon"</string>
<string name="permgroupdesc_microphone" msgid="4988812113943554584">"spill inn lyd"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"Kamera"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"PIN-kode for å løsne apper"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Krev bruk av opplåsningsmønster for å løsne apper"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Krev passord for å løsne apper"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Du kan ikke endre størrselse på appen – rull med to fingre."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installert av administratoren"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Oppdatert av administratoren"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Slettet av administratoren"</string>
diff --git a/core/res/res/values-ne-rNP/strings.xml b/core/res/res/values-ne-rNP/strings.xml
index 17e67f8..a33289f 100644
--- a/core/res/res/values-ne-rNP/strings.xml
+++ b/core/res/res/values-ne-rNP/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"आवाज सहायता"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"अब बन्द गर्नुहोस्"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"९९९+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"सुरक्षित मोड"</string>
<string name="android_system_label" msgid="6577375335728551336">"एन्ड्रोइड प्रणाली"</string>
<string name="user_owner_label" msgid="2804351898001038951">"व्यक्तिगत"</string>
@@ -1440,12 +1442,11 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"पिन निकाल्नुअघि PIN सोध्नुहोस्"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"पिन निकाल्नुअघि खोल्ने रूपरेखा सोध्नुहोस्"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"पिन निकाल्नुअघि पासवर्ड सोध्नुहोस्"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"अनुप्रयोगको आकार सानो-ठुलो बनाउन मिल्दैन, दुई औँलाले यसलाई स्क्रोल गर्नुहोस्।"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"तपाईँको प्रशासकद्वारा स्थापना गरिएको"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"तपाईँको प्रशासकद्वारा अद्यावधिक गरिएको"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"तपाईँको प्रशासकद्वारा हटाइएको"</string>
- <string name="battery_saver_description" msgid="1960431123816253034">"ब्याट्रीको आयु सुधार्न, ब्याट्री रक्षकले तपाईँको यन्त्रको कार्यसम्पादन घटाउँछ र भाइब्रेसन, स्थान सेवा र बहुसंख्यक पृष्ठभूमि डेटा सीमित गर्दछ। इमेल, सन्देश, र अन्य अनुप्रयोगहरू जुन सिङ्कमा भर पर्छन् अद्यावधिक नहुन सक्छन् जबसम्म तपाईँ तिनीहरूलाई खोल्नुहुन्न\n\n ब्याट्री रक्षक स्वत: निस्कृय हुन्छ जब तपाईँको यन्त्र चार्ज हुँदै हुन्छ।"</string>
+ <string name="battery_saver_description" msgid="1960431123816253034">"ब्याट्रीको आयु सुधार्न, ब्याट्री संरक्षकले तपाईँको यन्त्रको कार्यसम्पादन घटाउँछ र भाइब्रेसन, स्थान सेवा र बहुसंख्यक पृष्ठभूमि डेटा सीमित गर्दछ। इमेल, सन्देश, र अन्य अनुप्रयोगहरू जुन सिङ्कमा भर पर्छन् अद्यावधिक नहुन सक्छन् जबसम्म तपाईँ तिनीहरूलाई खोल्नुहुन्न\n\n ब्याट्री संरक्षक स्वत: निस्कृय हुन्छ जब तपाईँको यन्त्र चार्ज हुँदै हुन्छ।"</string>
<plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="4367877408072000848">
<item quantity="other"> %1$d मिनेटको लागि (<xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g> सम्म)</item>
<item quantity="one">एक मिनेटको लागि (<xliff:g id="FORMATTEDTIME_0">%2$s</xliff:g> सम्म)</item>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index d04e10b..53b45b0 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Spraakassistent"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Nu vergrendelen"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999 +"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Veilige modus"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-systeem"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Persoonlijk"</string>
@@ -243,7 +244,7 @@
<string name="permgrouplab_phone" msgid="5229115638567440675">"Telefoon"</string>
<string name="permgroupdesc_phone" msgid="6234224354060641055">"bellen en telefoontjes beheren"</string>
<string name="permgrouplab_sensors" msgid="416037179223226722">"Lichaamssensoren"</string>
- <string name="permgroupdesc_sensors" msgid="7147968539346634043">"toegang tot sensorgegevens over je vitale functies"</string>
+ <string name="permgroupdesc_sensors" msgid="7147968539346634043">"toegang krijgen tot sensorgegevens over je vitale functies"</string>
<string name="capability_title_canRetrieveWindowContent" msgid="3901717936930170320">"Inhoud van vensters ophalen"</string>
<string name="capability_desc_canRetrieveWindowContent" msgid="3772225008605310672">"De inhoud inspecteren van een venster waarmee je interactie hebt."</string>
<string name="capability_title_canRequestTouchExploration" msgid="3108723364676667320">"\'Verkennen via aanraking\' inschakelen"</string>
diff --git a/core/res/res/values-pa-rIN/strings.xml b/core/res/res/values-pa-rIN/strings.xml
index c3df4ac..d450bf9 100644
--- a/core/res/res/values-pa-rIN/strings.xml
+++ b/core/res/res/values-pa-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ਵੌਇਸ ਅਸਿਸਟ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ਹੁਣ ਲੌਕ ਕਰੋ"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"ਸੁਰੱਖਿਅਤ ਮੋਡ"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android System"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ਨਿੱਜੀ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 14e56cb..b7f984d 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Asystent głosowy"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Zablokuj teraz"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">">999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Tryb awaryjny"</string>
<string name="android_system_label" msgid="6577375335728551336">"System Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Osobiste"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Podaj PIN, aby odpiąć"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Aby odpiąć, poproś o wzór odblokowania"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Aby odpiąć, poproś o hasło"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Rozmiaru tej aplikacji nie można zmienić. Przewiń ją dwoma palcami."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Zainstalowany przez administratora"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Zaktualizowane przez administratora"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Usunięty przez administratora"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 8212d13..4464259 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ajuda de voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear agora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">">999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo de segurança"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Pessoal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pedir PIN antes de liberar"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pedir padrão de desbloqueio antes de liberar"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pedir senha antes de liberar"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"O app não é redimensionável. Desloque-o com dois dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalado pelo seu administrador"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Atualizado pelo administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Excluído pelo seu administrador"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 90091d8..af38435 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Assist. de voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear agora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Pessoal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pedir PIN antes de soltar"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pedir sequência de desbloqueio antes de soltar"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pedir palavra-passe antes de soltar"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"A aplicação não é redimensionável. Desloque-a com dois dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalado pelo administrador"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Atualizado pelo administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Eliminado pelo administrador"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 8212d13..4464259 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ajuda de voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear agora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">">999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo de segurança"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Pessoal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pedir PIN antes de liberar"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pedir padrão de desbloqueio antes de liberar"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pedir senha antes de liberar"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"O app não é redimensionável. Desloque-o com dois dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalado pelo seu administrador"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Atualizado pelo administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Excluído pelo seu administrador"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 9536c70..7ac92c3 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -223,6 +223,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Asistent vocal"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Blocați acum"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"˃999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mod sigur"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistemul Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
@@ -1443,8 +1445,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Solicită codul PIN înainte de a anula fixarea"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Solicită modelul pentru deblocare înainte de a anula fixarea"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Solicită parola înainte de a anula fixarea"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Aplicația nu poate fi redimensionată. Derulați în ea cu două degete."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalat de administrator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Actualizat de un administrator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Șters de administrator"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 613c738..b7ba99f 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Аудиоподсказки"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Заблокировать"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">">999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Безопасный режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Система Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Личные данные"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"PIN-код для отключения"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Запрашивать графический ключ для отключения блокировки"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Запрашивать пароль для отключения блокировки"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Размер окна нельзя изменить. Прокрутите страницу двумя пальцами."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Установлено администратором"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Обновлено администратором"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Удалено администратором"</string>
diff --git a/core/res/res/values-si-rLK/strings.xml b/core/res/res/values-si-rLK/strings.xml
index b9ae0e9..4e265177 100644
--- a/core/res/res/values-si-rLK/strings.xml
+++ b/core/res/res/values-si-rLK/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"හඬ සහායක"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"දැන් අගුළු දමන්න"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"ආරක්ෂිත ආකාරය"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android පද්ධතිය"</string>
<string name="user_owner_label" msgid="2804351898001038951">"පෞද්ගලික"</string>
@@ -1436,8 +1438,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"ගැලවීමට පෙර PIN විමසන්න"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"ගැලවීමට පෙර අගුළු අරින රටාව සඳහා අසන්න"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"ගැලවීමට පෙර මුරපදය විමසන්න"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"යෙදුම ප්රතිප්රමාණ කළ හැකි නොවේ, එය ඇඟිලි දෙකකින් අනුචලනය කරන්න."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"ඔබගේ පරිපාලක විසින් ස්ථාපනය කරන ලද"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"ඔබගේ පරිපාලක විසින් යාවත්කාලීන කරන ලදී"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"ඔබගේ පරිපාලක විසින් මකන ලද"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 856f8f7..ba5465e 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Hlasový asistent"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Uzamknúť"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Núdzový režim"</string>
<string name="android_system_label" msgid="6577375335728551336">"Systém Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Osobné"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pred uvoľnením požiadať o číslo PIN"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pred uvoľnením požiadať o bezpečnostný vzor"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pred uvoľnením požiadať o heslo"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Veľkosť aplikácie nie je možné zmeniť. Zobrazenie môžete posúvať dvoma prstami."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Inštalovaný správcom"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Aktualizované správcom"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Odstránený správcom"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index eff1574..04b7fc7 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Glas. pomočnik"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Zakleni zdaj"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999 +"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Varni način"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistem Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Osebno"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Zahtevaj PIN pred odpenjanjem"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pred odpenjanjem vprašaj za vzorec za odklepanje"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pred odpenjanjem vprašaj za geslo"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Velikosti aplikacije ni mogoče spremeniti. Po njej se pomikajte z dvema prstoma."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Namestil skrbnik"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Posodobil skrbnik"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Izbrisal skrbnik"</string>
diff --git a/core/res/res/values-sq-rAL/strings.xml b/core/res/res/values-sq-rAL/strings.xml
index a29a960..b8ca565 100644
--- a/core/res/res/values-sq-rAL/strings.xml
+++ b/core/res/res/values-sq-rAL/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ndihma zanore"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Kyç tani"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Modaliteti i sigurisë"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistemi \"android\""</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
@@ -1434,8 +1435,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Zhgozhdimi kërkon PIN-in"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Kërko model shkyçjeje para heqjes së gozhdimit"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Kërko fjalëkalim para heqjes nga gozhdimi."</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Përmasa e apl. nuk mund të ndryshohet, lëvize atë me të dy gishtat."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"U instalua nga administratori yt"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Përditësuar nga administratori"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"U fshi nga administratori yt"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 9d12292..4118c63 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -223,6 +223,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Гласовна помоћ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Закључај одмах"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Безбедни режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android систем"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Лично"</string>
@@ -1443,8 +1445,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Тражи PIN пре откачињања"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Тражи шаблон за откључавање пре откачињања"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Тражи лозинку пре откачињања"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Величина апликације не може да се мења, померајте је помоћу два прста."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Инсталирао је ваш администратор"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Ажурирао је администратор"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Избрисао је ваш адмиистратор"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 57edbcb..40141de 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lås nu"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Säkert läge"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personligt"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Be om pinkod innan skärmen slutar fästas"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Be om upplåsningsmönster innan skärmen slutar fästas"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Be om lösenord innan skärmen slutar fästas"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Det går inte att ändra appens storlek. Rulla med två fingrar."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Paketet har installerats av administratören"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Uppdaterat av administratören"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Paketet har raderats av administratören"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 3de8eeb..196d5dc 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Usaidizi wa Sauti"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Funga sasa"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mtindo salama"</string>
<string name="android_system_label" msgid="6577375335728551336">"Mfumo wa Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Binafsi"</string>
diff --git a/core/res/res/values-ta-rIN/strings.xml b/core/res/res/values-ta-rIN/strings.xml
index 30b326a..1113776 100644
--- a/core/res/res/values-ta-rIN/strings.xml
+++ b/core/res/res/values-ta-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"குரல் உதவி"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"இப்போது பூட்டு"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"பாதுகாப்பு பயன்முறை"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android அமைப்பு"</string>
<string name="user_owner_label" msgid="2804351898001038951">"தனிப்பட்ட"</string>
diff --git a/core/res/res/values-te-rIN/strings.xml b/core/res/res/values-te-rIN/strings.xml
index 3659080..c563e7c 100644
--- a/core/res/res/values-te-rIN/strings.xml
+++ b/core/res/res/values-te-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"వాయిస్ సహాయకం"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ఇప్పుడు లాక్ చేయండి"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"సురక్షిత మోడ్"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android సిస్టమ్"</string>
<string name="user_owner_label" msgid="2804351898001038951">"వ్యక్తిగతం"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index f168d6c..0080f51 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ตัวช่วยเสียง"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ล็อกเลย"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"โหมดปลอดภัย"</string>
<string name="android_system_label" msgid="6577375335728551336">"ระบบ Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ส่วนตัว"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"ขอ PIN ก่อนเลิกตรึง"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"ขอรูปแบบการปลดล็อกก่อนเลิกตรึง"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"ขอรหัสผ่านก่อนเลิกตรึง"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"แอปไม่สามารถปรับขนาดได้ เลื่อนแอปด้วยนิ้ว 2 นิ้ว"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"ติดตั้งโดยผู้ดูแลระบบของคุณ"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"อัปเดตโดยผู้ดูแลระบบ"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"ลบโดยผู้ดูแลระบบของคุณ"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index ab30e30..4f907c1 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"I-lock ngayon"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android System"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Humingi ng PIN bago mag-unpin"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Humingi ng pattern sa pag-unlock bago mag-unpin"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Humingi ng password bago mag-unpin"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Hindi nare-resize ang app, mag-scroll dito gamit ang dalawang daliri."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Na-install ng iyong administrator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Na-update ng iyong administrator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Na-delete ng iyong administrator"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 8cf8935..06f8c75 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Sesli Yardım"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Şimdi kilitle"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Güvenli mod"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android Sistemi"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Kişisel"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Sabitlemeyi kaldırmadan önce PIN\'i sor"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Sabitlemeyi kaldırmadan önce kilit açma desenini sor"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Sabitlemeyi kaldırmadan önce şifre sor"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Uygulama yeniden boyutlandırılamaz. İki parmağınızla kaydırın."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Yöneticiniz tarafından yüklendi"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Yöneticiniz tarafından güncellendi"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Yöneticiniz tarafından silindi"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 7522e4e..721f24d 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -224,6 +224,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Голос. підказки"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Блокувати зараз"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Безп. режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Система Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Особисті дані"</string>
diff --git a/core/res/res/values-ur-rPK/strings.xml b/core/res/res/values-ur-rPK/strings.xml
index c4d2466..e31fa57 100644
--- a/core/res/res/values-ur-rPK/strings.xml
+++ b/core/res/res/values-ur-rPK/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ابھی مقفل کریں"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"حفاظتی وضع"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android سسٹم"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ذاتی"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"پن ہٹانے سے پہلے PIN طلب کریں"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"پن ہٹانے سے پہلے غیر مقفل کرنے کا پیٹرن طلب کریں"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"پن ہٹانے سے پہلے پاس ورڈ طلب کریں"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"ایپ ری سائز ایبل نہیں ہے، اسے دو انگلیوں کے ساتھ سکرول کریں۔"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"آپ کے منتظم کی جانب سے انسٹال کر دیا گیا"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"آپ کے منتظم نے اپ ڈيٹ کر دیا"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"آپ کے منتظم کی جانب سے حذف کر دیا گیا"</string>
diff --git a/core/res/res/values-uz-rUZ/strings.xml b/core/res/res/values-uz-rUZ/strings.xml
index 596bbed..0489638 100644
--- a/core/res/res/values-uz-rUZ/strings.xml
+++ b/core/res/res/values-uz-rUZ/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ovozli yordam"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Qulflash"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Xavfsiz usul"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android tizimi"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Shaxsiy"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Yechishda PIN-kod so‘ralsin"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Bo‘shatishdan oldin chizmali parol so‘ralsin"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Bo‘shatishdan oldin parol so‘ralsin"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Oyna o‘lchamini o‘zgartirib bo‘lmaydi. Sahifani ikkita barmoq bilan aylantiring."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Administratoringiz tomonidan o‘rnatilgan"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Administratoringiz tomonidan yangilandi"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Administratoringiz tomonidan o‘chirilgan"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index c5aa37e..ccd1345 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Trợ lý thoại"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Khóa ngay"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Chế độ an toàn"</string>
<string name="android_system_label" msgid="6577375335728551336">"Hệ thống Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Cá nhân"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Hỏi mã PIN trước khi bỏ ghim"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Hỏi hình mở khóa trước khi bỏ ghim"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Hỏi mật khẩu trước khi bỏ ghim"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Ứng dụng không đổi kích thước được, hãy cuộn ứng dụng bằng hai ngón tay."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Được cài đặt bởi quản trị viên của bạn"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Được cập nhật bởi quản trị viên của bạn"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Đã bị xóa bởi quản trị viên của bạn"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 311fe55..1afede0 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"语音助理"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"立即锁定"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"安全模式"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android 系统"</string>
<string name="user_owner_label" msgid="2804351898001038951">"个人"</string>
@@ -1305,8 +1307,7 @@
<string name="enable_accessibility_canceled" msgid="3833923257966635673">"已取消无障碍功能。"</string>
<string name="user_switched" msgid="3768006783166984410">"当前用户是<xliff:g id="NAME">%1$s</xliff:g>。"</string>
<string name="user_switching_message" msgid="2871009331809089783">"正在切换为<xliff:g id="NAME">%1$s</xliff:g>…"</string>
- <!-- no translation found for user_logging_out_message (8939524935808875155) -->
- <skip />
+ <string name="user_logging_out_message" msgid="8939524935808875155">"正在将<xliff:g id="NAME">%1$s</xliff:g>退出帐号…"</string>
<string name="owner_name" msgid="2716755460376028154">"机主"</string>
<string name="error_message_title" msgid="4510373083082500195">"错误"</string>
<string name="error_message_change_not_allowed" msgid="1347282344200417578">"您的管理员不允许进行此更改"</string>
@@ -1435,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"取消时要求输入PIN码"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"取消时要求绘制解锁图案"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"取消时要求输入密码"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"无法调整这个应用的大小,请用双指滚动应用。"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"已由管理员安装"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"由您单位的管理员更新"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"已被管理员删除"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index efab307..84c85bf 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"語音助手"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"立即鎖定"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"安全模式"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android 系統"</string>
<string name="user_owner_label" msgid="2804351898001038951">"個人"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 4d23d76..b45fe75 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"語音小幫手"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"立即鎖定"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"超過 999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"安全模式"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android 系統"</string>
<string name="user_owner_label" msgid="2804351898001038951">"個人"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"取消固定時必須輸入 PIN"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"取消固定時必須畫出解鎖圖形"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"取消固定時必須輸入密碼"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"無法調整這個應用程式的大小,請用雙指捲動該應用程式。"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"已由管理員安裝"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"由您的管理員更新"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"已遭管理員刪除"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index bd566cd..c2159d7 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Isisekeli sezwi"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Khiya manje"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Imodi ephephile"</string>
<string name="android_system_label" msgid="6577375335728551336">"Uhlelo lwe-Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Okomuntu siqu"</string>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 7f8c460..dad68ba 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -130,12 +130,9 @@
<drawable name="notification_template_divider">#29000000</drawable>
<drawable name="notification_template_divider_media">#29ffffff</drawable>
- <color name="notification_icon_bg_color">#ff9e9e9e</color>
+ <color name="notification_icon_default_color">#ff616161</color>
<color name="notification_action_color_filter">@color/secondary_text_material_light</color>
- <color name="notification_media_primary_color">@color/primary_text_material_dark</color>
- <color name="notification_media_secondary_color">@color/secondary_text_material_dark</color>
-
<color name="notification_progress_background_color">@color/secondary_text_material_light</color>
<!-- Keyguard colors -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 01daf26..7b4becc 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -130,11 +130,38 @@
<!-- Default padding for dialogs. -->
<dimen name="dialog_padding">16dp</dimen>
+ <!-- The margin on the start of the content view -->
+ <dimen name="notification_content_margin_start">16dp</dimen>
+
+ <!-- The margin on the end of the content view -->
+ <dimen name="notification_content_margin_end">16dp</dimen>
+
+ <!-- The margin on the end of the content view with a picture.-->
+ <dimen name="notification_content_picture_margin">56dp</dimen>
+
+ <!-- height of the content margin to accomodate for the header -->
+ <dimen name="notification_content_margin_top">30dp</dimen>
+
+ <!-- height of notification header view if present -->
+ <dimen name="notification_header_height">32dp</dimen>
+
+ <!-- Height of a small notification in the status bar -->
+ <dimen name="notification_min_height">84dp</dimen>
+
<!-- The width of the big icons in notifications. -->
<dimen name="notification_large_icon_width">64dp</dimen>
<!-- The width of the big icons in notifications. -->
<dimen name="notification_large_icon_height">64dp</dimen>
+ <!-- Min height of the notification content. -->
+ <dimen name="notification_min_content_height">54dp</dimen>
+
+ <!-- The minimum width of the app name in the header if it shrinks -->
+ <dimen name="notification_header_shrink_min_width">72dp</dimen>
+
+ <!-- The minimum height of the content if there is a picture present with big picture -->
+ <dimen name="notification_big_picture_content_min_height_with_picture">41dp</dimen>
+
<!-- Minimum width of the search view text entry area. -->
<dimen name="search_view_text_min_width">160dip</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8a00294..d6dd842 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -531,6 +531,12 @@
[CHAR LIMIT=4] -->
<string name="status_bar_notification_info_overflow">999+</string>
+ <!-- The number of notifications in the notification header. An example would be (2) or (12) -->
+ <string name="notification_children_count_bracketed">(<xliff:g id="notificationCount" example="1">%d</xliff:g>)</string>
+
+ <!-- The divider symbol between different parts of the notification header. not translatable [CHAR LIMIT=1] -->
+ <string name="notification_header_divider_symbol" translatable="false">•</string>
+
<!-- Displayed to the user to tell them that they have started up the phone in "safe mode" -->
<string name="safeMode">Safe mode</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8cf1134..3860f68 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -197,11 +197,8 @@
<java-symbol type="id" name="inbox_text4" />
<java-symbol type="id" name="inbox_text5" />
<java-symbol type="id" name="inbox_text6" />
- <java-symbol type="id" name="inbox_more" />
- <java-symbol type="id" name="inbox_end_pad" />
<java-symbol type="id" name="status_bar_latest_event_content" />
<java-symbol type="id" name="action_divider" />
- <java-symbol type="id" name="overflow_divider" />
<java-symbol type="id" name="notification_main_column" />
<java-symbol type="id" name="sms_short_code_confirm_message" />
<java-symbol type="id" name="sms_short_code_detail_layout" />
@@ -218,7 +215,6 @@
<java-symbol type="id" name="pin_error_message" />
<java-symbol type="id" name="timePickerLayout" />
<java-symbol type="id" name="profile_badge_large_template" />
- <java-symbol type="id" name="profile_badge_line2" />
<java-symbol type="id" name="profile_badge_line3" />
<java-symbol type="id" name="transitionPosition" />
<java-symbol type="id" name="selection_start_handle" />
@@ -1877,15 +1873,11 @@
<java-symbol type="layout" name="notification_template_material_inbox" />
<java-symbol type="layout" name="notification_template_material_media" />
<java-symbol type="layout" name="notification_template_material_big_media" />
- <java-symbol type="layout" name="notification_template_material_big_media_narrow" />
<java-symbol type="layout" name="notification_template_material_big_text" />
- <java-symbol type="layout" name="notification_template_icon_group" />
+ <java-symbol type="layout" name="notification_template_header" />
<java-symbol type="layout" name="notification_material_media_action" />
<java-symbol type="color" name="notification_action_color_filter" />
- <java-symbol type="color" name="notification_icon_bg_color" />
- <java-symbol type="drawable" name="notification_icon_legacy_bg" />
- <java-symbol type="color" name="notification_media_primary_color" />
- <java-symbol type="color" name="notification_media_secondary_color" />
+ <java-symbol type="color" name="notification_icon_default_color" />
<java-symbol type="color" name="notification_progress_background_color" />
<java-symbol type="id" name="media_actions" />
@@ -2276,6 +2268,7 @@
<java-symbol type="layout" name="floating_popup_menu_image_button" />
<java-symbol type="layout" name="floating_popup_overflow_list_item" />
<java-symbol type="layout" name="floating_popup_overflow_image_list_item" />
+ <java-symbol type="layout" name="floating_popup_overflow_button" />
<java-symbol type="dimen" name="floating_toolbar_height" />
<java-symbol type="dimen" name="floating_toolbar_menu_button_side_padding" />
<java-symbol type="dimen" name="floating_toolbar_overflow_side_padding" />
@@ -2287,6 +2280,10 @@
<java-symbol type="dimen" name="floating_toolbar_horizontal_margin" />
<java-symbol type="dimen" name="floating_toolbar_vertical_margin" />
<java-symbol type="dimen" name="content_rect_bottom_clip_allowance" />
+ <java-symbol type="drawable" name="ft_avd_tooverflow" />
+ <java-symbol type="drawable" name="ft_avd_toarrow" />
+ <java-symbol type="drawable" name="ft_avd_toarrow_animation" />
+ <java-symbol type="drawable" name="ft_avd_tooverflow_animation" />
<java-symbol type="string" name="date_picker_prev_month_button" />
<java-symbol type="string" name="date_picker_next_month_button" />
@@ -2352,4 +2349,24 @@
<java-symbol type="id" name="suggestionContainer" />
<java-symbol type="id" name="addToDictionaryButton" />
<java-symbol type="id" name="deleteButton" />
+
+ <java-symbol type="string" name="notification_children_count_bracketed" />
+ <java-symbol type="id" name="app_name_text" />
+ <java-symbol type="id" name="number_of_children" />
+ <java-symbol type="id" name="header_sub_text" />
+ <java-symbol type="id" name="expand_button" />
+ <java-symbol type="id" name="notification_header" />
+ <java-symbol type="id" name="header_content_info" />
+ <java-symbol type="id" name="time_divider" />
+ <java-symbol type="id" name="sub_text_divider" />
+ <java-symbol type="id" name="content_info_divider" />
+ <java-symbol type="id" name="text_line_1" />
+ <java-symbol type="drawable" name="ic_arrow_up_14dp" />
+ <java-symbol type="dimen" name="notification_header_height" />
+ <java-symbol type="dimen" name="notification_big_picture_content_min_height_with_picture" />
+ <java-symbol type="dimen" name="notification_header_shrink_min_width" />
+ <java-symbol type="dimen" name="notification_content_margin_start" />
+ <java-symbol type="dimen" name="notification_content_margin_end" />
+ <java-symbol type="dimen" name="notification_content_picture_margin" />
+ <java-symbol type="dimen" name="notification_content_margin_top" />
</resources>
diff --git a/core/tests/coretests/apks/install_complete_package_info/Android.mk b/core/tests/coretests/apks/install_complete_package_info/Android.mk
index 1edccb4..19bf356 100644
--- a/core/tests/coretests/apks/install_complete_package_info/Android.mk
+++ b/core/tests/coretests/apks/install_complete_package_info/Android.mk
@@ -5,7 +5,9 @@
LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_complete_package_info
+LOCAL_PACKAGE_NAME := install_complete_package_info
+#LOCAL_MANIFEST_FILE := api_test/AndroidManifest.xml
-include $(BUILD_PACKAGE)
+include $(FrameworkCoreTests_BUILD_PACKAGE)
+#include $(BUILD_PACKAGE)
diff --git a/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml b/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml
index 4c7e968..2897bd5 100644
--- a/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml
+++ b/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml
@@ -17,4 +17,11 @@
package="com.android.frameworks.coretests.keysets_api">
<application android:hasCode="false">
</application>
+ <key-sets>
+ <key-set android:name="A" >
+ <public-key android:name="keyA"
+ android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ=="/>
+ </key-set>
+ </key-sets>
+
</manifest>
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 9498f4c..b5f0617 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -73,7 +73,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-@Suppress // Failing.
public class PackageManagerTests extends AndroidTestCase {
private static final boolean localLOGV = true;
@@ -434,14 +433,6 @@
SECURE_CONTAINERS_PREFIX, publicSrcPath);
assertStartsWith("The native library path should point to the ASEC",
SECURE_CONTAINERS_PREFIX, info.nativeLibraryDir);
- try {
- String compatLib = new File(info.dataDir + "/lib").getCanonicalPath();
- assertEquals("The compatibility lib directory should be a symbolic link to "
- + info.nativeLibraryDir,
- info.nativeLibraryDir, compatLib);
- } catch (IOException e) {
- fail("compat check: Can't read " + info.dataDir + "/lib");
- }
} else {
assertFalse(
(info.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0);
@@ -1014,7 +1005,8 @@
private static void assertUninstalled(ApplicationInfo info) throws Exception {
File nativeLibraryFile = new File(info.nativeLibraryDir);
- assertFalse("Native library directory should be erased", nativeLibraryFile.exists());
+ assertFalse("Native library directory " + info.nativeLibraryDir
+ + " should be erased", nativeLibraryFile.exists());
}
public void deleteFromRawResource(int iFlags, int dFlags) throws Exception {
@@ -1650,15 +1642,10 @@
(info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
assertStartsWith("Native library dir should point to ASEC",
SECURE_CONTAINERS_PREFIX, info.nativeLibraryDir);
- final File nativeLibSymLink = new File(info.dataDir, "lib");
- assertStartsWith("The data directory should have a 'lib' symlink that points to the ASEC container",
- SECURE_CONTAINERS_PREFIX, nativeLibSymLink.getCanonicalPath());
}
}
} catch (NameNotFoundException e) {
failStr("Pkg hasnt been installed correctly");
- } catch (Exception e) {
- failStr("Failed with exception : " + e);
} finally {
if (ip != null) {
cleanUpInstall(ip);
@@ -1689,6 +1676,7 @@
sampleMoveFromRawResource(installFlags, moveFlags, fail, result);
}
+ @Suppress
@LargeTest
public void testMoveAppInternalToInternal() throws Exception {
int installFlags = PackageManager.INSTALL_INTERNAL;
@@ -2157,6 +2145,7 @@
-1);
}
+ @Suppress
@LargeTest
public void testFlagFExistingI() throws Exception {
int iFlags = PackageManager.INSTALL_INTERNAL;
@@ -3272,14 +3261,15 @@
assertTrue(false); // should have thrown
} catch (IllegalArgumentException e) {
}
- installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+ final InstallParams ip = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
try {
ks = pm.getSigningKeySet(otherPkgName);
assertTrue(false); // should have thrown
} catch (SecurityException e) {
+ } finally {
+ cleanUpInstall(ip);
}
- cleanUpInstall(otherPkgName);
ks = pm.getSigningKeySet(mContext.getPackageName());
assertNotNull(ks);
}
@@ -3318,16 +3308,20 @@
assertTrue(false); // should have thrown
} catch(IllegalArgumentException e) {
}
- installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+
+ // make sure we can get a KeySet from our pkg
+ ks = pm.getKeySetByAlias(mPkgName, "A");
+ assertNotNull(ks);
+
+ // and another
+ final InstallParams ip = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
try {
ks = pm.getKeySetByAlias(otherPkgName, "A");
- assertTrue(false); // should have thrown
- } catch (SecurityException e) {
+ assertNotNull(ks);
+ } finally {
+ cleanUpInstall(ip);
}
- cleanUpInstall(otherPkgName);
- ks = pm.getKeySetByAlias(mPkgName, "A");
- assertNotNull(ks);
}
public void testIsSignedBy() throws Exception {
@@ -3360,17 +3354,23 @@
assertFalse(pm.isSignedBy(mPkgName, new KeySet(new Binder())));
assertTrue(pm.isSignedBy(mPkgName, mSigningKS));
- installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+ final InstallParams ip1 = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
- assertFalse(pm.isSignedBy(otherPkgName, mDefinedKS));
- assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
- cleanUpInstall(otherPkgName);
+ try {
+ assertFalse(pm.isSignedBy(otherPkgName, mDefinedKS));
+ assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
+ } finally {
+ cleanUpInstall(ip1);
+ }
- installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
+ final InstallParams ip2 = installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
- assertTrue(pm.isSignedBy(otherPkgName, mDefinedKS));
- assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
- cleanUpInstall(otherPkgName);
+ try {
+ assertTrue(pm.isSignedBy(otherPkgName, mDefinedKS));
+ assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
+ } finally {
+ cleanUpInstall(ip2);
+ }
}
public void testIsSignedByExactly() throws Exception {
@@ -3402,17 +3402,23 @@
assertFalse(pm.isSignedByExactly(mPkgName, new KeySet(new Binder())));
assertTrue(pm.isSignedByExactly(mPkgName, mSigningKS));
- installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+ final InstallParams ip1 = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
- assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
- assertTrue(pm.isSignedByExactly(otherPkgName, mSigningKS));
- cleanUpInstall(otherPkgName);
+ try {
+ assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
+ assertTrue(pm.isSignedByExactly(otherPkgName, mSigningKS));
+ } finally {
+ cleanUpInstall(ip1);
+ }
- installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
+ final InstallParams ip2 = installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
- assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
- assertFalse(pm.isSignedByExactly(otherPkgName, mSigningKS));
- cleanUpInstall(otherPkgName);
+ try {
+ assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
+ assertFalse(pm.isSignedByExactly(otherPkgName, mSigningKS));
+ } finally {
+ cleanUpInstall(ip2);
+ }
}
@@ -3465,11 +3471,10 @@
int apk2 = APP2_CERT1_CERT2;
String apk1Name = "install1.apk";
String apk2Name = "install2.apk";
- InstallParams ip1 = null;
+ final InstallParams ip = installFromRawResource(apk1Name, apk1, 0, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
try {
- ip1 = installFromRawResource(apk1Name, apk1, 0, false,
- false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
PackageManager pm = mContext.getPackageManager();
// Delete app2
File filesDir = mContext.getFilesDir();
@@ -3480,12 +3485,10 @@
getPm().deletePackage(pkg.packageName, null, PackageManager.DELETE_ALL_USERS);
// Check signatures now
int match = mContext.getPackageManager().checkSignatures(
- ip1.pkg.packageName, pkg.packageName);
+ ip.pkg.packageName, pkg.packageName);
assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, match);
} finally {
- if (ip1 != null) {
- cleanUpInstall(ip1);
- }
+ cleanUpInstall(ip);
}
}
@@ -3493,14 +3496,10 @@
public void testInstallNoCertificates() throws Exception {
int apk1 = APP1_UNSIGNED;
String apk1Name = "install1.apk";
- InstallParams ip1 = null;
- try {
- installFromRawResource(apk1Name, apk1, 0, false,
- true, PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES,
- PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
- } finally {
- }
+ installFromRawResource(apk1Name, apk1, 0, false,
+ true, PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
}
/*
@@ -3766,35 +3765,43 @@
* Test that getInstalledPackages returns all the data specified in flags.
*/
public void testGetInstalledPackagesAll() throws Exception {
- int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
+ final int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
| PackageManager.GET_CONFIGURATIONS | PackageManager.GET_INSTRUMENTATION
| PackageManager.GET_PERMISSIONS | PackageManager.GET_PROVIDERS
| PackageManager.GET_RECEIVERS | PackageManager.GET_SERVICES
| PackageManager.GET_SIGNATURES | PackageManager.GET_UNINSTALLED_PACKAGES;
- List<PackageInfo> packages = getPm().getInstalledPackages(flags);
- assertNotNull("installed packages cannot be null", packages);
- assertTrue("installed packages cannot be empty", packages.size() > 0);
+ final InstallParams ip =
+ installFromRawResource("install.apk", R.raw.install_complete_package_info,
+ 0 /*flags*/, false /*cleanUp*/, false /*fail*/, -1 /*result*/,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ try {
+ final List<PackageInfo> packages = getPm().getInstalledPackages(flags);
+ assertNotNull("installed packages cannot be null", packages);
+ assertTrue("installed packages cannot be empty", packages.size() > 0);
- PackageInfo packageInfo = null;
+ PackageInfo packageInfo = null;
- // Find the package with all components specified in the AndroidManifest
- // to ensure no null values
- for (PackageInfo pi : packages) {
- if ("com.android.frameworks.coretests.install_complete_package_info"
- .equals(pi.packageName)) {
- packageInfo = pi;
- break;
+ // Find the package with all components specified in the AndroidManifest
+ // to ensure no null values
+ for (PackageInfo pi : packages) {
+ if ("com.android.frameworks.coretests.install_complete_package_info"
+ .equals(pi.packageName)) {
+ packageInfo = pi;
+ break;
+ }
}
+ assertNotNull("activities should not be null", packageInfo.activities);
+ assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
+ assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
+ assertNotNull("permissions should not be null", packageInfo.permissions);
+ assertNotNull("providers should not be null", packageInfo.providers);
+ assertNotNull("receivers should not be null", packageInfo.receivers);
+ assertNotNull("services should not be null", packageInfo.services);
+ assertNotNull("signatures should not be null", packageInfo.signatures);
+ } finally {
+ cleanUpInstall(ip);
}
- assertNotNull("activities should not be null", packageInfo.activities);
- assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
- assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
- assertNotNull("permissions should not be null", packageInfo.permissions);
- assertNotNull("providers should not be null", packageInfo.providers);
- assertNotNull("receivers should not be null", packageInfo.receivers);
- assertNotNull("services should not be null", packageInfo.services);
- assertNotNull("signatures should not be null", packageInfo.signatures);
}
/**
@@ -3802,38 +3809,52 @@
* flags when the GET_UNINSTALLED_PACKAGES flag is set.
*/
public void testGetUnInstalledPackagesAll() throws Exception {
- int flags = PackageManager.GET_UNINSTALLED_PACKAGES
+ final int flags = PackageManager.GET_UNINSTALLED_PACKAGES
| PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
| PackageManager.GET_CONFIGURATIONS | PackageManager.GET_INSTRUMENTATION
| PackageManager.GET_PERMISSIONS | PackageManager.GET_PROVIDERS
| PackageManager.GET_RECEIVERS | PackageManager.GET_SERVICES
| PackageManager.GET_SIGNATURES | PackageManager.GET_UNINSTALLED_PACKAGES;
- List<PackageInfo> packages = getPm().getInstalledPackages(flags);
- assertNotNull("installed packages cannot be null", packages);
- assertTrue("installed packages cannot be empty", packages.size() > 0);
+ // first, install the package
+ final InstallParams ip =
+ installFromRawResource("install.apk", R.raw.install_complete_package_info,
+ 0 /*flags*/, false /*cleanUp*/, false /*fail*/, -1 /*result*/,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ try {
+ // then, remove it, keeping it's data around
+ final GenericReceiver receiver = new DeleteReceiver(ip.pkg.packageName);
+ invokeDeletePackage(ip.pkg.packageName, PackageManager.DELETE_KEEP_DATA, receiver);
- PackageInfo packageInfo = null;
+ final List<PackageInfo> packages = getPm().getInstalledPackages(flags);
+ assertNotNull("installed packages cannot be null", packages);
+ assertTrue("installed packages cannot be empty", packages.size() > 0);
- // Find the package with all components specified in the AndroidManifest
- // to ensure no null values
- for (PackageInfo pi : packages) {
- if ("com.android.frameworks.coretests.install_complete_package_info"
- .equals(pi.packageName)) {
- packageInfo = pi;
- break;
+ PackageInfo packageInfo = null;
+
+ // Find the package with all components specified in the AndroidManifest
+ // to ensure no null values
+ for (PackageInfo pi : packages) {
+ if ("com.android.frameworks.coretests.install_complete_package_info"
+ .equals(pi.packageName)) {
+ packageInfo = pi;
+ break;
+ }
}
+ assertNotNull("activities should not be null", packageInfo.activities);
+ assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
+ assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
+ assertNotNull("permissions should not be null", packageInfo.permissions);
+ assertNotNull("providers should not be null", packageInfo.providers);
+ assertNotNull("receivers should not be null", packageInfo.receivers);
+ assertNotNull("services should not be null", packageInfo.services);
+ assertNotNull("signatures should not be null", packageInfo.signatures);
+ } finally {
+ cleanUpInstall(ip);
}
- assertNotNull("activities should not be null", packageInfo.activities);
- assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
- assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
- assertNotNull("permissions should not be null", packageInfo.permissions);
- assertNotNull("providers should not be null", packageInfo.providers);
- assertNotNull("receivers should not be null", packageInfo.receivers);
- assertNotNull("services should not be null", packageInfo.services);
- assertNotNull("signatures should not be null", packageInfo.signatures);
}
+ @Suppress
public void testInstall_BadDex_CleanUp() throws Exception {
int retCode = PackageManager.INSTALL_FAILED_DEXOPT;
installFromRawResource("install.apk", R.raw.install_bad_dex, 0, true, true, retCode,
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index c5e2ae6..ddbdc87 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -16,7 +16,11 @@
package android.widget;
+import static android.widget.espresso.TextViewActions.mouseDoubleClickOnTextAtIndex;
+import static android.widget.espresso.TextViewActions.mouseLongClickOnTextAtIndex;
+import static android.widget.espresso.TextViewActions.mouseDoubleClickAndDragOnText;
import static android.widget.espresso.TextViewActions.mouseDragOnText;
+import static android.widget.espresso.TextViewActions.mouseLongClickAndDragOnText;
import static android.widget.espresso.TextViewAssertions.hasSelection;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
@@ -62,4 +66,104 @@
onView(withId(R.id.textview)).check(hasSelection("llo wor"));
}
+
+ @SmallTest
+ public void testSelectTextByLongClick() throws Exception {
+ getActivity();
+
+ final String helloWorld = "Hello world!";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+
+ onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(0));
+ onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+ onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(
+ helloWorld.indexOf("world")));
+ onView(withId(R.id.textview)).check(hasSelection("world"));
+
+ onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(
+ helloWorld.indexOf("llo")));
+ onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+ onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(
+ helloWorld.indexOf("rld")));
+ onView(withId(R.id.textview)).check(hasSelection("world"));
+ }
+
+ @SmallTest
+ public void testSelectTextByDoubleClick() throws Exception {
+ getActivity();
+
+ final String helloWorld = "Hello world!";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+
+ onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(0));
+ onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+ onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(
+ helloWorld.indexOf("world")));
+ onView(withId(R.id.textview)).check(hasSelection("world"));
+
+ onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(
+ helloWorld.indexOf("llo")));
+ onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+ onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(
+ helloWorld.indexOf("rld")));
+ onView(withId(R.id.textview)).check(hasSelection("world"));
+ }
+
+ @SmallTest
+ public void testSelectTextByDoubleClickAndDrag() throws Exception {
+ getActivity();
+
+ final String text = "abcd efg hijk lmn";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ onView(withId(R.id.textview)).perform(
+ mouseDoubleClickAndDragOnText(text.indexOf("f"), text.indexOf("j")));
+ onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+ }
+
+ @SmallTest
+ public void testSelectTextByDoubleClickAndDrag_reverse() throws Exception {
+ getActivity();
+
+ final String text = "abcd efg hijk lmn";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ onView(withId(R.id.textview)).perform(
+ mouseDoubleClickAndDragOnText(text.indexOf("j"), text.indexOf("f")));
+ onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+ }
+
+ @SmallTest
+ public void testSelectTextByLongPressAndDrag() throws Exception {
+ getActivity();
+
+ final String text = "abcd efg hijk lmn";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ onView(withId(R.id.textview)).perform(
+ mouseLongClickAndDragOnText(text.indexOf("f"), text.indexOf("j")));
+ onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+ }
+
+ @SmallTest
+ public void testSelectTextByLongPressAndDrag_reverse() throws Exception {
+ getActivity();
+
+ final String text = "abcd efg hijk lmn";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ onView(withId(R.id.textview)).perform(
+ mouseLongClickAndDragOnText(text.indexOf("j"), text.indexOf("f")));
+ onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+ }
}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 78a0a59..4614505 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -34,12 +34,15 @@
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import com.android.frameworks.coretests.R;
+import android.support.test.espresso.NoMatchingRootException;
+import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.ViewInteraction;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
@@ -165,18 +168,21 @@
assertFloatingToolbarIsDisplayed(getActivity());
}
- private static ViewInteraction onHandleView(int id) {
- return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class)))
- .inRoot(withDecorView(hasDescendant(withId(id))));
- }
-
@SmallTest
public void testSelectionHandles() throws Exception {
final String text = "abcd efg hijk lmn";
onView(withId(R.id.textview)).perform(click());
onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ assertNoSelectionHandles();
+
onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('f')));
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .check(matches(isDisplayed()));
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .check(matches(isDisplayed()));
+
final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
onHandleView(com.android.internal.R.id.selection_start_handle)
.perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
@@ -336,4 +342,25 @@
.perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('i')));
onView(withId(R.id.textview)).check(hasSelection("hijk"));
}
+
+ private static void assertNoSelectionHandles() {
+ try {
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .check(matches(isDisplayed()));
+ } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
+ try {
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .check(matches(isDisplayed()));
+ } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e1) {
+ return;
+ }
+ }
+ throw new AssertionError("Selection handle found");
+ }
+
+ private static ViewInteraction onHandleView(int id)
+ throws NoMatchingRootException, NoMatchingViewException, AssertionError {
+ return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class)))
+ .inRoot(withDecorView(hasDescendant(withId(id))));
+ }
}
diff --git a/core/tests/coretests/src/android/widget/espresso/DragAction.java b/core/tests/coretests/src/android/widget/espresso/DragAction.java
index 1132ce0..ce97568 100644
--- a/core/tests/coretests/src/android/widget/espresso/DragAction.java
+++ b/core/tests/coretests/src/android/widget/espresso/DragAction.java
@@ -91,6 +91,72 @@
},
/**
+ * Starts a drag with a mouse double click.
+ */
+ MOUSE_DOUBLE_CLICK {
+ private DownMotionPerformer downMotion = new DownMotionPerformer() {
+ @Override
+ @Nullable
+ public MotionEvent perform(
+ UiController uiController, float[] coordinates, float[] precision) {
+ return performDoubleTap(uiController, coordinates, precision);
+ }
+ };
+
+ @Override
+ public Status sendSwipe(
+ UiController uiController,
+ float[] startCoordinates, float[] endCoordinates, float[] precision) {
+ return sendLinearDrag(
+ uiController, downMotion, startCoordinates, endCoordinates, precision);
+ }
+
+ @Override
+ public String toString() {
+ return "mouse double click and drag to select";
+ }
+
+ @Override
+ public UiController wrapUiController(UiController uiController) {
+ return new MouseUiController(uiController);
+ }
+ },
+
+ /**
+ * Starts a drag with a mouse long click.
+ */
+ MOUSE_LONG_CLICK {
+ private DownMotionPerformer downMotion = new DownMotionPerformer() {
+ @Override
+ public MotionEvent perform(
+ UiController uiController, float[] coordinates, float[] precision) {
+ MotionEvent downEvent = MotionEvents.sendDown(
+ uiController, coordinates, precision)
+ .down;
+ return performLongPress(uiController, coordinates, precision);
+ }
+ };
+
+ @Override
+ public Status sendSwipe(
+ UiController uiController,
+ float[] startCoordinates, float[] endCoordinates, float[] precision) {
+ return sendLinearDrag(
+ uiController, downMotion, startCoordinates, endCoordinates, precision);
+ }
+
+ @Override
+ public String toString() {
+ return "mouse long click and drag to select";
+ }
+
+ @Override
+ public UiController wrapUiController(UiController uiController) {
+ return new MouseUiController(uiController);
+ }
+ },
+
+ /**
* Starts a drag with a tap.
*/
TAP {
@@ -127,15 +193,7 @@
@Override
public MotionEvent perform(
UiController uiController, float[] coordinates, float[] precision) {
- MotionEvent downEvent = MotionEvents.sendDown(
- uiController, coordinates, precision)
- .down;
- // Duration before a press turns into a long press.
- // Factor 1.5 is needed, otherwise a long press is not safely detected.
- // See android.test.TouchUtils longClickView
- long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
- uiController.loopMainThreadForAtLeast(longPressTimeout);
- return downEvent;
+ return performLongPress(uiController, coordinates, precision);
}
};
@@ -162,25 +220,7 @@
@Nullable
public MotionEvent perform(
UiController uiController, float[] coordinates, float[] precision) {
- MotionEvent downEvent = MotionEvents.sendDown(
- uiController, coordinates, precision)
- .down;
- try {
- if (!MotionEvents.sendUp(uiController, downEvent)) {
- String logMessage = "Injection of up event as part of the double tap " +
- "failed. Sending cancel event.";
- Log.d(TAG, logMessage);
- MotionEvents.sendCancel(uiController, downEvent);
- return null;
- }
-
- long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime();
- uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout);
-
- return MotionEvents.sendDown(uiController, coordinates, precision).down;
- } finally {
- downEvent.recycle();
- }
+ return performDoubleTap(uiController, coordinates, precision);
}
};
@@ -267,6 +307,43 @@
return res;
}
+ private static MotionEvent performLongPress(
+ UiController uiController, float[] coordinates, float[] precision) {
+ MotionEvent downEvent = MotionEvents.sendDown(
+ uiController, coordinates, precision)
+ .down;
+ // Duration before a press turns into a long press.
+ // Factor 1.5 is needed, otherwise a long press is not safely detected.
+ // See android.test.TouchUtils longClickView
+ long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
+ uiController.loopMainThreadForAtLeast(longPressTimeout);
+ return downEvent;
+ }
+
+ @Nullable
+ private static MotionEvent performDoubleTap(
+ UiController uiController, float[] coordinates, float[] precision) {
+ MotionEvent downEvent = MotionEvents.sendDown(
+ uiController, coordinates, precision)
+ .down;
+ try {
+ if (!MotionEvents.sendUp(uiController, downEvent)) {
+ String logMessage = "Injection of up event as part of the double tap " +
+ "failed. Sending cancel event.";
+ Log.d(TAG, logMessage);
+ MotionEvents.sendCancel(uiController, downEvent);
+ return null;
+ }
+
+ long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime();
+ uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout);
+
+ return MotionEvents.sendDown(uiController, coordinates, precision).down;
+ } finally {
+ downEvent.recycle();
+ }
+ }
+
@Override
public UiController wrapUiController(UiController uiController) {
return uiController;
diff --git a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
new file mode 100644
index 0000000..de640ca
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.widget.espresso;
+
+import org.hamcrest.Matcher;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.CoordinatesProvider;
+import android.support.test.espresso.action.GeneralClickAction;
+import android.support.test.espresso.action.PrecisionDescriber;
+import android.support.test.espresso.action.Tapper;
+import android.view.View;
+
+/**
+ * ViewAction for performing an click on View by a mouse.
+ */
+public final class MouseClickAction implements ViewAction {
+ private final GeneralClickAction mGeneralClickAction;
+
+ public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider,
+ PrecisionDescriber precisionDescriber) {
+ mGeneralClickAction = new GeneralClickAction(tapper, coordinatesProvider,
+ precisionDescriber);
+ }
+
+ @Override
+ public Matcher<View> getConstraints() {
+ return mGeneralClickAction.getConstraints();
+ }
+
+ @Override
+ public String getDescription() {
+ return mGeneralClickAction.getDescription();
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ mGeneralClickAction.perform(new MouseUiController(uiController), view);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 32abc86..63e310e 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -53,6 +53,21 @@
}
/**
+ * Returns an action that clicks by mouse on text at an index on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param index The index of the TextView's text to click on.
+ */
+ public static ViewAction mouseClickOnTextAtIndex(int index) {
+ return actionWithAssertions(
+ new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), Press.PINPOINT));
+ }
+
+ /**
* Returns an action that double-clicks on text at an index on the TextView.<br>
* <br>
* View constraints:
@@ -68,6 +83,21 @@
}
/**
+ * Returns an action that double-clicks by mouse on text at an index on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param index The index of the TextView's text to double-click on.
+ */
+ public static ViewAction mouseDoubleClickOnTextAtIndex(int index) {
+ return actionWithAssertions(
+ new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.PINPOINT));
+ }
+
+ /**
* Returns an action that long presses on text at an index on the TextView.<br>
* <br>
* View constraints:
@@ -83,6 +113,21 @@
}
/**
+ * Returns an action that long click by mouse on text at an index on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param index The index of the TextView's text to long click on.
+ */
+ public static ViewAction mouseLongClickOnTextAtIndex(int index) {
+ return actionWithAssertions(
+ new MouseClickAction(Tap.LONG, new TextCoordinates(index), Press.PINPOINT));
+ }
+
+ /**
* Returns an action that long presses then drags on text from startIndex to endIndex on the
* TextView.<br>
* <br>
@@ -148,6 +193,50 @@
TextView.class));
}
+ /**
+ * Returns an action that double click then drags by mouse on text from startIndex to endIndex
+ * on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param startIndex The index of the TextView's text to start a drag from
+ * @param endIndex The index of the TextView's text to end the drag at
+ */
+ public static ViewAction mouseDoubleClickAndDragOnText(int startIndex, int endIndex) {
+ return actionWithAssertions(
+ new DragAction(
+ DragAction.Drag.MOUSE_DOUBLE_CLICK,
+ new TextCoordinates(startIndex),
+ new TextCoordinates(endIndex),
+ Press.PINPOINT,
+ TextView.class));
+ }
+
+ /**
+ * Returns an action that long click then drags by mouse on text from startIndex to endIndex
+ * on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param startIndex The index of the TextView's text to start a drag from
+ * @param endIndex The index of the TextView's text to end the drag at
+ */
+ public static ViewAction mouseLongClickAndDragOnText(int startIndex, int endIndex) {
+ return actionWithAssertions(
+ new DragAction(
+ DragAction.Drag.MOUSE_LONG_CLICK,
+ new TextCoordinates(startIndex),
+ new TextCoordinates(endIndex),
+ Press.PINPOINT,
+ TextView.class));
+ }
+
public enum Handle {
SELECTION_START,
SELECTION_END,
diff --git a/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk
index f6d8ee9..edfd380 100644
--- a/data/sounds/AllAudio.mk
+++ b/data/sounds/AllAudio.mk
@@ -228,6 +228,7 @@
$(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
$(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:system/media/audio/ui/Trusted.ogg \
$(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \
$(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
diff --git a/data/sounds/AudioPackage10.mk b/data/sounds/AudioPackage10.mk
index 5a5eea6..c5222af 100644
--- a/data/sounds/AudioPackage10.mk
+++ b/data/sounds/AudioPackage10.mk
@@ -1,9 +1,9 @@
#
# Audio Package 10 - Mako
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -23,6 +23,7 @@
$(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage11.mk b/data/sounds/AudioPackage11.mk
index 0f85b33..43c83b9 100644
--- a/data/sounds/AudioPackage11.mk
+++ b/data/sounds/AudioPackage11.mk
@@ -1,9 +1,9 @@
#
# Audio Package 11 - Razor
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -23,6 +23,7 @@
$(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage12.mk b/data/sounds/AudioPackage12.mk
index 4251332..cd4d35b 100644
--- a/data/sounds/AudioPackage12.mk
+++ b/data/sounds/AudioPackage12.mk
@@ -13,7 +13,7 @@
RINGTONE_FILES := Callisto Dione Ganymede Luna Oberon Phobos Sedna Titania Triton Umbriel
EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
camera_focus Dock Undock Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted
+MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted VideoStop
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
$(LOCAL_PATH)/alarms/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage12_48.mk b/data/sounds/AudioPackage12_48.mk
index 70e68d3..80758f4 100644
--- a/data/sounds/AudioPackage12_48.mk
+++ b/data/sounds/AudioPackage12_48.mk
@@ -13,7 +13,7 @@
RINGTONE_FILES := Callisto Dione Ganymede Luna Oberon Phobos Sedna Titania Triton Umbriel
EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted
+MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted VideoStop
# Alarms not yet available in 48 kHz
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
diff --git a/data/sounds/AudioPackage13.mk b/data/sounds/AudioPackage13.mk
index cec7280..d33a4af 100644
--- a/data/sounds/AudioPackage13.mk
+++ b/data/sounds/AudioPackage13.mk
@@ -14,7 +14,7 @@
Umbriel
EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
camera_focus Dock Undock Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery
+MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
$(LOCAL_PATH)/alarms/material/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage13_48.mk b/data/sounds/AudioPackage13_48.mk
index d1b17c8..9c320ae 100644
--- a/data/sounds/AudioPackage13_48.mk
+++ b/data/sounds/AudioPackage13_48.mk
@@ -14,7 +14,7 @@
Umbriel
EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery
+MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
$(LOCAL_PATH)/alarms/material/ogg/$(fn)_48k.ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage2.mk b/data/sounds/AudioPackage2.mk
index ba9d7e2..40319c4 100644
--- a/data/sounds/AudioPackage2.mk
+++ b/data/sounds/AudioPackage2.mk
@@ -1,11 +1,11 @@
#
# Audio Package 2
-#
+#
# Include this file in a product makefile to include these audio files
#
# This is a larger package of sounds than the 1.0 release for devices
# that have larger internal flash.
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -34,6 +34,7 @@
$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage3.mk b/data/sounds/AudioPackage3.mk
index 5bfeb42..a05de72 100644
--- a/data/sounds/AudioPackage3.mk
+++ b/data/sounds/AudioPackage3.mk
@@ -1,11 +1,11 @@
#
# Audio Package 3
-#
+#
# Include this file in a product makefile to include these audio files
#
# This is a larger package of sounds than the 1.0 release for devices
# that have larger internal flash.
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -34,6 +34,7 @@
$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage4.mk b/data/sounds/AudioPackage4.mk
index 43dbe20..d376a2d 100644
--- a/data/sounds/AudioPackage4.mk
+++ b/data/sounds/AudioPackage4.mk
@@ -1,11 +1,11 @@
#
# Audio Package 4
-#
+#
# Include this file in a product makefile to include these audio files
#
# This is a larger package of sounds than the 1.0 release for devices
# that have larger internal flash.
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -39,6 +39,7 @@
$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage5.mk b/data/sounds/AudioPackage5.mk
index fbbb16a..72384c8 100644
--- a/data/sounds/AudioPackage5.mk
+++ b/data/sounds/AudioPackage5.mk
@@ -1,9 +1,9 @@
#
# Audio Package 5 - Crespo/Soju
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -20,6 +20,7 @@
$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage6.mk b/data/sounds/AudioPackage6.mk
index c843fdc..5413704 100644
--- a/data/sounds/AudioPackage6.mk
+++ b/data/sounds/AudioPackage6.mk
@@ -1,9 +1,9 @@
#
# Audio Package 6 - Trygon/Stingray
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -19,6 +19,7 @@
$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage7.mk b/data/sounds/AudioPackage7.mk
index ce82651..e4763be 100644
--- a/data/sounds/AudioPackage7.mk
+++ b/data/sounds/AudioPackage7.mk
@@ -1,9 +1,9 @@
#
# Audio Package 7 - Tuna
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -21,6 +21,7 @@
$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage7alt.mk b/data/sounds/AudioPackage7alt.mk
index db468f3..30e6173 100644
--- a/data/sounds/AudioPackage7alt.mk
+++ b/data/sounds/AudioPackage7alt.mk
@@ -21,6 +21,7 @@
$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
$(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage8.mk b/data/sounds/AudioPackage8.mk
index 4112c18..b38e62d 100644
--- a/data/sounds/AudioPackage8.mk
+++ b/data/sounds/AudioPackage8.mk
@@ -1,9 +1,9 @@
#
# Audio Package 7 - Tuna
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -23,6 +23,7 @@
$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage9.mk b/data/sounds/AudioPackage9.mk
index 1b430c0..dbe1350 100644
--- a/data/sounds/AudioPackage9.mk
+++ b/data/sounds/AudioPackage9.mk
@@ -23,6 +23,7 @@
$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/OriginalAudio.mk b/data/sounds/OriginalAudio.mk
index e1ca24b..f683752 100644
--- a/data/sounds/OriginalAudio.mk
+++ b/data/sounds/OriginalAudio.mk
@@ -1,8 +1,8 @@
#
# Original audio package that shipped on G1
-#
+#
# This file is included from core.mk so that all devices will have these sounds
-#
+#
# TODO: Clean up for future releases
#
@@ -32,6 +32,7 @@
$(LOCAL_PATH)/effects/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \
$(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:system/media/audio/notifications/CaffeineSnake.ogg
diff --git a/data/sounds/effects/VideoStop.ogg b/data/sounds/effects/VideoStop.ogg
new file mode 100644
index 0000000..1450522
--- /dev/null
+++ b/data/sounds/effects/VideoStop.ogg
Binary files differ
diff --git a/data/sounds/effects/VideoStop.wav b/data/sounds/effects/VideoStop.wav
new file mode 100644
index 0000000..5809d93
--- /dev/null
+++ b/data/sounds/effects/VideoStop.wav
Binary files differ
diff --git a/data/sounds/effects/material/ogg/VideoStop.ogg b/data/sounds/effects/material/ogg/VideoStop.ogg
new file mode 100644
index 0000000..e98fabc0
--- /dev/null
+++ b/data/sounds/effects/material/ogg/VideoStop.ogg
Binary files differ
diff --git a/data/sounds/effects/material/ogg/VideoStop_48k.ogg b/data/sounds/effects/material/ogg/VideoStop_48k.ogg
new file mode 100644
index 0000000..b1eb780
--- /dev/null
+++ b/data/sounds/effects/material/ogg/VideoStop_48k.ogg
Binary files differ
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 95ae72e..5acc1a3 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -484,7 +484,7 @@
*
* @param bounds The maximum size the offscreen bitmap needs to be
* (in local coordinates)
- * @param alpha The alpha to apply to the offscreen when when it is
+ * @param alpha The alpha to apply to the offscreen when it is
drawn during restore()
* @param saveFlags see _SAVE_FLAG constants, generally {@link #ALL_SAVE_FLAG} is recommended
* for performance reasons.
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index f9474ef..d313aa5 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -139,6 +139,7 @@
exit.setInterpolator(LINEAR_INTERPOLATOR);
exit.setDuration(OPACITY_EXIT_DURATION);
exit.setStartDelay(fastEnterDuration);
+ exit.setStartValue(targetAlpha);
set.add(exit);
// Linear "fast" enter based on current opacity.
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 1c2c940..f5b7a2e 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -36,6 +36,7 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.OsConstants;
@@ -1260,6 +1261,13 @@
*/
public void setWakeMode(Context context, int mode) {
boolean washeld = false;
+
+ /* Disable persistant wakelocks in media player based on property */
+ if (SystemProperties.getBoolean("audio.offload.ignore_setawake", false) == true) {
+ Log.w(TAG, "IGNORING setWakeMode " + mode);
+ return;
+ }
+
if (mWakeLock != null) {
if (mWakeLock.isHeld()) {
washeld = true;
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 41b8ab2..be9fb47 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -240,7 +240,7 @@
/**
* Gets the root id.
* <p>
- * Note that the root id may become invalid or change when when the
+ * Note that the root id may become invalid or change when the
* browser is disconnected.
* </p>
*
@@ -270,7 +270,7 @@
/**
* Gets the media session token associated with the media browser.
* <p>
- * Note that the session token may become invalid or change when when the
+ * Note that the session token may become invalid or change when the
* browser is disconnected.
* </p>
*
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 595928a..60f5d60 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -39,7 +39,7 @@
</activity>
<activity
- android:name=".ManageRootActivity"
+ android:name=".DownloadsActivity"
android:theme="@style/DocumentsFullScreenTheme"
android:icon="@drawable/ic_doc_text">
<intent-filter>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 55e2f44..b99c806 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -39,13 +39,15 @@
import android.os.SystemClock;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
import android.support.design.widget.Snackbar;
import android.text.format.DateUtils;
import android.util.Log;
-import android.widget.Toast;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.model.RootInfo;
import libcore.io.IoUtils;
@@ -72,6 +74,12 @@
// TODO: Move it to a shared file when more operations are implemented.
public static final int FAILURE_COPY = 1;
+ // Parameters of the copy job. Requests to an IntentService are serialized so this code only
+ // needs to deal with one job at a time.
+ // NOTE: This must be declared by concrete type as the concrete type
+ // is required by putParcelableArrayListExtra.
+ private final ArrayList<DocumentInfo> mFailedFiles = new ArrayList<>();
+
private PowerManager mPowerManager;
private NotificationManager mNotificationManager;
@@ -80,9 +88,6 @@
// Jobs are serialized but a job ID is used, to avoid mixing up cancellation requests.
private String mJobId;
private volatile boolean mIsCancelled;
- // Parameters of the copy job. Requests to an IntentService are serialized so this code only
- // needs to deal with one job at a time.
- private final ArrayList<DocumentInfo> mFailedFiles;
private long mBatchSize;
private long mBytesCopied;
private long mStartTime;
@@ -97,10 +102,11 @@
private ContentProviderClient mSrcClient;
private ContentProviderClient mDstClient;
+ // For testing only.
+ @Nullable private TestOnlyListener mJobFinishedListener;
+
public CopyService() {
super("CopyService");
-
- mFailedFiles = new ArrayList<DocumentInfo>();
}
/**
@@ -115,7 +121,11 @@
final Resources res = activity.getResources();
final Intent copyIntent = new Intent(activity, CopyService.class);
copyIntent.putParcelableArrayListExtra(
- EXTRA_SRC_LIST, new ArrayList<DocumentInfo>(srcDocs));
+ EXTRA_SRC_LIST,
+ // Don't create a copy unless absolutely necessary :)
+ srcDocs instanceof ArrayList
+ ? (ArrayList<DocumentInfo>) srcDocs
+ : new ArrayList<DocumentInfo>(srcDocs));
copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) dstStack);
copyIntent.putExtra(EXTRA_TRANSFER_MODE, mode);
@@ -198,6 +208,11 @@
.setAutoCancel(true);
mNotificationManager.notify(mJobId, 0, errorBuilder.build());
}
+
+ if (mJobFinishedListener != null) {
+ mJobFinishedListener.onFinished(mFailedFiles);
+ }
+
if (DEBUG) Log.d(TAG, "Done cleaning up");
}
}
@@ -269,6 +284,26 @@
}
/**
+ * Sets a callback to be run when the next run job is finished.
+ * This is test ONLY instrumentation. The alternative is for us to add
+ * broadcast intents SOLELY for the purpose of testing.
+ * @param listener
+ */
+ @VisibleForTesting
+ void addFinishedListener(TestOnlyListener listener) {
+ this.mJobFinishedListener = listener;
+
+ }
+
+ /**
+ * Only used for testing. Is that obvious enough?
+ */
+ @VisibleForTesting
+ interface TestOnlyListener {
+ void onFinished(List<DocumentInfo> failed);
+ }
+
+ /**
* Calculates the cumulative size of all the documents in the list. Directories are recursed
* into and totaled up.
*
@@ -279,7 +314,7 @@
private long calculateFileSizes(List<DocumentInfo> srcs) throws RemoteException {
long result = 0;
for (DocumentInfo src : srcs) {
- if (Document.MIME_TYPE_DIR.equals(src.mimeType)) {
+ if (src.isDirectory()) {
// Directories need to be recursed into.
result += calculateFileSizesHelper(src.derivedUri);
} else {
@@ -412,8 +447,21 @@
*/
private void copy(DocumentInfo srcInfo, DocumentInfo dstDirInfo, int mode)
throws RemoteException {
- if (DEBUG) Log.d(TAG, "Copying " + srcInfo.displayName + " (" + srcInfo.derivedUri + ")" +
- " to " + dstDirInfo.displayName + " (" + dstDirInfo.derivedUri + ")");
+
+ String opDesc = mode == TRANSFER_MODE_COPY ? "copy" : "move";
+
+ // Guard unsupported recursive operation.
+ if (dstDirInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstDirInfo)) {
+ if (DEBUG) Log.d(TAG,
+ "Skipping recursive " + opDesc + " of directory " + dstDirInfo.derivedUri);
+ mFailedFiles.add(srcInfo);
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG,
+ "Performing " + opDesc + " of " + srcInfo.displayName
+ + " (" + srcInfo.derivedUri + ")" + " to " + dstDirInfo.displayName
+ + " (" + dstDirInfo.derivedUri + ")");
// When copying within the same provider, try to use optimized copying and moving.
// If not supported, then fallback to byte-by-byte copy/move.
@@ -450,7 +498,7 @@
return;
}
- if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
+ if (srcInfo.isDirectory()) {
copyDirectoryHelper(srcInfo.derivedUri, dstUri, mode);
} else {
copyFileHelper(srcInfo.derivedUri, dstUri, mode);
@@ -458,6 +506,17 @@
}
/**
+ * Returns true if {@code doc} is a descendant of {@code parentDoc}.
+ */
+ boolean isDescendentOf(DocumentInfo doc, DocumentInfo parentDoc) throws RemoteException {
+ if (parentDoc.isDirectory() && doc.authority.equals(parentDoc.authority)) {
+ return DocumentsContract.isChildDocument(
+ mDstClient, doc.derivedUri, parentDoc.derivedUri);
+ }
+ return false;
+ }
+
+ /**
* Handles recursion into a directory and copying its contents. Note that in linux terms, this
* does the equivalent of "cp src/* dst", not "cp -r src dst".
*
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index c6425a6..f3c3f2f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -32,6 +32,7 @@
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
+import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.util.Log;
import android.view.KeyEvent;
@@ -83,8 +84,10 @@
editText.setOnEditorActionListener(
new OnEditorActionListener() {
@Override
- public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
+ public boolean onEditorAction(
+ TextView view, int actionId, @Nullable KeyEvent event) {
+ if (event != null
+ && event.getKeyCode() == KeyEvent.KEYCODE_ENTER
&& event.hasNoModifiers()) {
createDirectory(editText.getText().toString());
dialog.dismiss();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
similarity index 95%
rename from packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
rename to packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
index 3045fa8..f224343 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
@@ -50,8 +50,11 @@
import java.util.Arrays;
import java.util.List;
-public class ManageRootActivity extends BaseActivity {
- private static final String TAG = "ManageRootsActivity";
+// 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";
private Toolbar mToolbar;
private Spinner mToolbarStack;
@@ -59,7 +62,7 @@
private ItemSelectedListener mStackListener;
private BaseAdapter mStackAdapter;
- public ManageRootActivity() {
+ public DownloadsActivity() {
super(R.layout.manage_roots_activity, TAG);
}
@@ -250,7 +253,7 @@
finish();
}
- public static ManageRootActivity get(Fragment fragment) {
- return (ManageRootActivity) fragment.getActivity();
+ public static DownloadsActivity get(Fragment fragment) {
+ return (DownloadsActivity) fragment.getActivity();
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
index 120f610..23074f0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
@@ -37,7 +37,6 @@
implements DialogInterface.OnClickListener {
private static final String TAG = "FailureDialogFragment";
- private int mFailure;
private int mTransferMode;
private ArrayList<DocumentInfo> mFailedSrcList;
@@ -75,7 +74,6 @@
public Dialog onCreateDialog(Bundle inState) {
super.onCreate(inState);
- mFailure = getArguments().getInt(CopyService.EXTRA_FAILURE);
mTransferMode = getArguments().getInt(CopyService.EXTRA_TRANSFER_MODE);
mFailedSrcList = getArguments().getParcelableArrayList(CopyService.EXTRA_SRC_LIST);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index 38d3805..ef6d2c9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -176,7 +176,6 @@
final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
open.setVisible(false);
- share.setVisible(false);
delete.setVisible(canDelete);
copyTo.setVisible(true);
copyTo.setEnabled(true);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index cc981e1e..dfdc705 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -25,6 +25,7 @@
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsProvider;
+import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import com.android.documentsui.DocumentsApplication;
@@ -204,13 +205,18 @@
}
}
- private void deriveFields() {
+ @VisibleForTesting
+ void deriveFields() {
derivedUri = DocumentsContract.buildDocumentUri(authority, documentId);
}
@Override
public String toString() {
- return "Document{docId=" + documentId + ", name=" + displayName + "}";
+ return "Document{"
+ + "docId=" + documentId
+ + ", name=" + displayName
+ + ", isDirectory=" + isDirectory()
+ + "}";
}
public boolean isCreateSupported() {
@@ -237,6 +243,22 @@
return (flags & Document.FLAG_DIR_HIDE_GRID_TITLES) != 0;
}
+ public int hashCode() {
+ return derivedUri.hashCode() + mimeType.hashCode();
+ }
+
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (!(other instanceof DocumentInfo)) {
+ return false;
+ }
+
+ DocumentInfo that = (DocumentInfo) other;
+ // Uri + mime type should be totally unique.
+ return derivedUri.equals(that.derivedUri) && mimeType.equals(that.mimeType);
+ }
+
public static String getCursorString(Cursor cursor, String columnName) {
final int index = cursor.getColumnIndex(columnName);
return (index != -1) ? cursor.getString(index) : null;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
similarity index 85%
rename from packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
rename to packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
index 369ab7d..079d599 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
@@ -28,12 +28,10 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Document;
import android.test.MoreAsserts;
import android.test.ServiceTestCase;
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import com.android.documentsui.model.DocumentInfo;
@@ -48,6 +46,7 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -55,9 +54,9 @@
import java.util.concurrent.TimeoutException;
@MediumTest
-public class CopyTest extends ServiceTestCase<CopyService> {
+public class CopyServiceTest extends ServiceTestCase<CopyService> {
- public CopyTest() {
+ public CopyServiceTest() {
super(CopyService.class);
}
@@ -72,11 +71,13 @@
private DocumentsProviderHelper mDocHelper;
private StubProvider mStorage;
private Context mSystemContext;
+ private CopyJobListener mListener;
@Override
protected void setUp() throws Exception {
super.setUp();
+ mListener = new CopyJobListener();
setupTestContext();
mClient = mResolver.acquireContentProviderClient(AUTHORITY);
@@ -84,6 +85,8 @@
mStorage.clearCacheAndBuildRoots();
mDocHelper = new DocumentsProviderHelper(AUTHORITY, mClient);
+
+ assertDestFileCount(0);
}
@Override
@@ -97,15 +100,13 @@
Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
"The five boxing wizards jump quickly".getBytes());
- assertDstFileCountEquals(0);
-
startService(createCopyIntent(Lists.newArrayList(testFile)));
// 2 operations: file creation, then writing data.
mResolver.waitForChanges(2);
// Verify that one file was copied; check file contents.
- assertDstFileCountEquals(1);
+ assertDestFileCount(1);
assertCopied(srcPath);
}
@@ -114,8 +115,6 @@
String testContent = "The five boxing wizards jump quickly";
Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", testContent.getBytes());
- assertDstFileCountEquals(0);
-
Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
startService(moveIntent);
@@ -124,7 +123,7 @@
mResolver.waitForChanges(3);
// Verify that one file was moved; check file contents.
- assertDstFileCountEquals(1);
+ assertDestFileCount(1);
assertDoesNotExist(SRC_ROOT, srcPath);
byte[] dstContent = readFile(DST_ROOT, srcPath);
@@ -147,15 +146,13 @@
mStorage.createFile(SRC_ROOT, srcPaths[1], "text/plain", testContent[1].getBytes()),
mStorage.createFile(SRC_ROOT, srcPaths[2], "text/plain", testContent[2].getBytes()));
- assertDstFileCountEquals(0);
-
// Copy all the test files.
startService(createCopyIntent(testFiles));
// 3 file creations, 3 file writes.
mResolver.waitForChanges(6);
- assertDstFileCountEquals(3);
+ assertDestFileCount(3);
for (String path : srcPaths) {
assertCopied(path);
}
@@ -163,29 +160,54 @@
public void testCopyEmptyDir() throws Exception {
String srcPath = "/emptyDir";
- Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
-
- assertDstFileCountEquals(0);
+ Uri testDir = createTestDirectory(srcPath);
startService(createCopyIntent(Lists.newArrayList(testDir)));
// Just 1 operation: Directory creation.
mResolver.waitForChanges(1);
- assertDstFileCountEquals(1);
+ assertDestFileCount(1);
// Verify that the dst exists and is a directory.
File dst = mStorage.getFile(DST_ROOT, srcPath);
assertTrue(dst.isDirectory());
}
+ public void testNoCopyDirToSelf() throws Exception {
+ Uri testDir = createTestDirectory("/someDir");
+
+ Intent intent = createCopyIntent(Lists.newArrayList(testDir), testDir);
+ startService(intent);
+
+ getService().addFinishedListener(mListener);
+
+ mListener.waitForFinished();
+ mListener.assertFailedCount(1);
+ mListener.assertFileFailed("someDir");
+
+ assertDestFileCount(0);
+ }
+
+ public void testNoCopyDirToDescendent() throws Exception {
+ Uri testDir = createTestDirectory("/someDir");
+ Uri descDir = createTestDirectory("/someDir/theDescendent");
+
+ Intent intent = createCopyIntent(Lists.newArrayList(testDir), descDir);
+ startService(intent);
+
+ getService().addFinishedListener(mListener);
+
+ mListener.waitForFinished();
+ mListener.assertFailedCount(1);
+ mListener.assertFileFailed("someDir");
+
+ assertDestFileCount(0);
+ }
+
public void testMoveEmptyDir() throws Exception {
String srcPath = "/emptyDir";
- Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
-
- assertDstFileCountEquals(0);
+ Uri testDir = createTestDirectory(srcPath);
Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
@@ -194,7 +216,7 @@
// 2 operations: Directory creation, and removal of the original.
mResolver.waitForChanges(2);
- assertDstFileCountEquals(1);
+ assertDestFileCount(1);
// Verify that the dst exists and is a directory.
File dst = mStorage.getFile(DST_ROOT, srcPath);
@@ -217,8 +239,7 @@
srcDir + "/test2.txt"
};
// Create test dir; put some files in it.
- Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
+ Uri testDir = createTestDirectory(srcDir);
mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
mStorage.createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
@@ -252,8 +273,6 @@
Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
"The five boxing wizards jump quickly".getBytes());
- assertDstFileCountEquals(0);
-
mStorage.simulateReadErrorsForFile(testFile);
startService(createCopyIntent(Lists.newArrayList(testFile)));
@@ -262,7 +281,7 @@
mResolver.waitForChanges(3);
// Verify that the failed copy was cleaned up.
- assertDstFileCountEquals(0);
+ assertDestFileCount(0);
}
public void testMoveFileWithReadErrors() throws Exception {
@@ -270,8 +289,6 @@
Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
"The five boxing wizards jump quickly".getBytes());
- assertDstFileCountEquals(0);
-
mStorage.simulateReadErrorsForFile(testFile);
Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
@@ -288,7 +305,7 @@
return;
} finally {
// Verify that the failed copy was cleaned up, and the src file wasn't removed.
- assertDstFileCountEquals(0);
+ assertDestFileCount(0);
assertExists(SRC_ROOT, srcPath);
}
// The asserts above didn't fail, but the CopyService did something unexpected.
@@ -308,8 +325,7 @@
srcDir + "/test2.txt"
};
// Create test dir; put some files in it.
- Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
+ Uri testDir = createTestDirectory(srcDir);
mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
Uri errFile = mStorage
.createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
@@ -346,33 +362,37 @@
assertExists(SRC_ROOT, srcFiles[1]);
}
- /**
- * Copies the given files to a pre-determined destination.
- *
- * @throws FileNotFoundException
- */
+ private Uri createTestDirectory(String dir) throws IOException {
+ return mStorage.createFile(
+ SRC_ROOT, dir, DocumentsContract.Document.MIME_TYPE_DIR, null);
+ }
+
private Intent createCopyIntent(List<Uri> srcs) throws Exception {
+ RootInfo root = mDocHelper.getRoot(DST_ROOT);
+ final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, root.documentId);
+
+ return createCopyIntent(srcs, dst);
+ }
+
+ private Intent createCopyIntent(List<Uri> srcs, Uri dst) throws Exception {
final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList();
for (Uri src : srcs) {
srcDocs.add(DocumentInfo.fromUri(mResolver, src));
}
- RootInfo root = mDocHelper.getRoot(DST_ROOT);
- final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, root.documentId);
DocumentStack stack = new DocumentStack();
stack.push(DocumentInfo.fromUri(mResolver, dst));
final Intent copyIntent = new Intent(mContext, CopyService.class);
copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
- // startService(copyIntent);
return copyIntent;
}
/**
* Returns a count of the files in the given directory.
*/
- private void assertDstFileCountEquals(int expected) throws RemoteException {
+ private void assertDestFileCount(int expected) throws RemoteException {
RootInfo dest = mDocHelper.getRoot(DST_ROOT);
final Uri queryUri = DocumentsContract.buildChildDocumentsUri(AUTHORITY,
dest.documentId);
@@ -449,6 +469,34 @@
mResolver.addProvider(AUTHORITY, mStorage);
}
+ private final class CopyJobListener implements CopyService.TestOnlyListener {
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final List<DocumentInfo> failedDocs = new ArrayList<>();
+ @Override
+ public void onFinished(List<DocumentInfo> failed) {
+ failedDocs.addAll(failed);
+ latch.countDown();
+ }
+
+ public void assertFileFailed(String expectedName) {
+ for (DocumentInfo failed : failedDocs) {
+ if (expectedName.equals(failed.displayName)) {
+ return;
+ }
+ }
+ fail("Couldn't find failed file: " + expectedName);
+ }
+
+ public void waitForFinished() throws InterruptedException {
+ latch.await(500, TimeUnit.MILLISECONDS);
+ }
+
+ public void assertFailedCount(int expected) {
+ assertEquals(expected, failedDocs.size());
+ }
+ }
+
/**
* A test resolver that enables this test suite to listen for notifications that mark when copy
* operations are done.
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 2d42ddc..d23cdeb 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -531,6 +531,16 @@
this.rootInfo = rootInfo;
mStorage.put(this.documentId, this);
}
+ @Override
+ public String toString() {
+ return "StubDocument{"
+ + "path:" + file.getPath()
+ + ", mimeType:" + mimeType
+ + ", rootInfo:" + rootInfo
+ + ", documentId:" + documentId
+ + ", parentId:" + parentId
+ + "}";
+ }
}
private static String getDocumentIdForFile(File file) {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
new file mode 100644
index 0000000..a6aba7b
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.model;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class DocumentInfoTest extends AndroidTestCase {
+
+ public void testEquals() throws Exception {
+ DocumentInfo doc = createDocInfo("authority.a", "doc.1", "text/plain");
+ assertEquals(doc, doc);
+ }
+
+ public void testNotEquals_differentAuthority() throws Exception {
+ DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
+ DocumentInfo docB = createDocInfo("authority.b", "doc.1", "text/plain");
+ assertFalse(docA.equals(docB));
+ }
+
+ public void testNotEquals_differentDocId() throws Exception {
+ DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
+ DocumentInfo docB = createDocInfo("authority.a", "doc.2", "text/plain");
+ assertFalse(docA.equals(docB));
+ }
+
+ public void testNotEquals_differentMimetype() throws Exception {
+ DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
+ DocumentInfo docB = createDocInfo("authority.a", "doc.1", "image/png");
+ assertFalse(docA.equals(docB));
+ }
+
+ private DocumentInfo createDocInfo(String authority, String docId, String mimeType) {
+ DocumentInfo doc = new DocumentInfo();
+ doc.authority = authority;
+ doc.documentId = docId;
+ doc.mimeType = mimeType;
+ doc.deriveFields();
+ return doc;
+ }
+}
diff --git a/packages/ExternalStorageProvider/res/values-uz-rUZ/strings.xml b/packages/ExternalStorageProvider/res/values-uz-rUZ/strings.xml
index d1a956bf..069e137 100644
--- a/packages/ExternalStorageProvider/res/values-uz-rUZ/strings.xml
+++ b/packages/ExternalStorageProvider/res/values-uz-rUZ/strings.xml
@@ -18,5 +18,5 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="7123375275748530234">"Tashqi xotira"</string>
<string name="root_internal_storage" msgid="827844243068584127">"Ichki xotira"</string>
- <string name="root_home" msgid="7931555396767513359">"Shaxsiy"</string>
+ <string name="root_home" msgid="7931555396767513359">"Mening fayllarim"</string>
</resources>
diff --git a/packages/Keyguard/res/values/config.xml b/packages/Keyguard/res/values/config.xml
index b398ab2..bde6ed5 100644
--- a/packages/Keyguard/res/values/config.xml
+++ b/packages/Keyguard/res/values/config.xml
@@ -23,9 +23,9 @@
<!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] -->
<bool name="config_disableMenuKeyInLockScreen">false</bool>
- <!-- Threshold in micro amperes below which a charger is rated as "slow" -->
- <integer name="config_chargingSlowlyThreshold">1000000</integer>
+ <!-- Threshold in micro watts below which a charger is rated as "slow"; 1A @ 5V -->
+ <integer name="config_chargingSlowlyThreshold">5000000</integer>
- <!-- Threshold in micro amperes above which a charger is rated as "fast" -->
- <integer name="config_chargingFastThreshold">1500000</integer>
+ <!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V -->
+ <integer name="config_chargingFastThreshold">7500000</integer>
</resources>
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3d78028..8102c34 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -75,6 +75,7 @@
import static android.os.BatteryManager.EXTRA_HEALTH;
import static android.os.BatteryManager.EXTRA_LEVEL;
import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
+import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
import static android.os.BatteryManager.EXTRA_PLUGGED;
import static android.os.BatteryManager.EXTRA_STATUS;
@@ -155,6 +156,8 @@
*/
private static final int FINGERPRINT_STATE_CANCELLING_RESTARTING = 3;
+ private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
+
private static KeyguardUpdateMonitor sInstance;
private final Context mContext;
@@ -617,10 +620,25 @@
final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0);
final int level = intent.getIntExtra(EXTRA_LEVEL, 0);
final int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
- final int maxChargingCurrent = intent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+
+ final int maxChargingMicroAmp = intent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+ int maxChargingMicroVolt = intent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+ final int maxChargingMicroWatt;
+
+ if (maxChargingMicroVolt <= 0) {
+ maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
+ }
+ if (maxChargingMicroAmp > 0) {
+ // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor
+ // to maintain precision equally on both factors.
+ maxChargingMicroWatt = (maxChargingMicroAmp / 1000)
+ * (maxChargingMicroVolt / 1000);
+ } else {
+ maxChargingMicroWatt = -1;
+ }
final Message msg = mHandler.obtainMessage(
MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health,
- maxChargingCurrent));
+ maxChargingMicroWatt));
mHandler.sendMessage(msg);
} else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
SimData args = SimData.fromIntent(intent);
@@ -806,13 +824,14 @@
public final int level;
public final int plugged;
public final int health;
- public final int maxChargingCurrent;
- public BatteryStatus(int status, int level, int plugged, int health, int maxChargingCurrent) {
+ public final int maxChargingWattage;
+ public BatteryStatus(int status, int level, int plugged, int health,
+ int maxChargingWattage) {
this.status = status;
this.level = level;
this.plugged = plugged;
this.health = health;
- this.maxChargingCurrent = maxChargingCurrent;
+ this.maxChargingWattage = maxChargingWattage;
}
/**
@@ -844,9 +863,9 @@
}
public final int getChargingSpeed(int slowThreshold, int fastThreshold) {
- return maxChargingCurrent <= 0 ? CHARGING_UNKNOWN :
- maxChargingCurrent < slowThreshold ? CHARGING_SLOWLY :
- maxChargingCurrent > fastThreshold ? CHARGING_FAST :
+ return maxChargingWattage <= 0 ? CHARGING_UNKNOWN :
+ maxChargingWattage < slowThreshold ? CHARGING_SLOWLY :
+ maxChargingWattage > fastThreshold ? CHARGING_FAST :
CHARGING_REGULAR;
}
}
@@ -1422,7 +1441,7 @@
}
// change in charging current while plugged in
- if (nowPluggedIn && current.maxChargingCurrent != old.maxChargingCurrent) {
+ if (nowPluggedIn && current.maxChargingWattage != old.maxChargingWattage) {
return true;
}
diff --git a/packages/MtpDocumentsProvider/res/drawable-hdpi/ic_root_mtp.png b/packages/MtpDocumentsProvider/res/drawable-hdpi/ic_root_mtp.png
new file mode 100644
index 0000000..7691433
--- /dev/null
+++ b/packages/MtpDocumentsProvider/res/drawable-hdpi/ic_root_mtp.png
Binary files differ
diff --git a/packages/MtpDocumentsProvider/res/drawable-mdpi/ic_root_mtp.png b/packages/MtpDocumentsProvider/res/drawable-mdpi/ic_root_mtp.png
new file mode 100644
index 0000000..1cf7b3a
--- /dev/null
+++ b/packages/MtpDocumentsProvider/res/drawable-mdpi/ic_root_mtp.png
Binary files differ
diff --git a/packages/MtpDocumentsProvider/res/drawable-xhdpi/ic_root_mtp.png b/packages/MtpDocumentsProvider/res/drawable-xhdpi/ic_root_mtp.png
new file mode 100644
index 0000000..27e3542
--- /dev/null
+++ b/packages/MtpDocumentsProvider/res/drawable-xhdpi/ic_root_mtp.png
Binary files differ
diff --git a/packages/MtpDocumentsProvider/res/drawable-xxhdpi/ic_root_mtp.png b/packages/MtpDocumentsProvider/res/drawable-xxhdpi/ic_root_mtp.png
new file mode 100644
index 0000000..3df2578b
--- /dev/null
+++ b/packages/MtpDocumentsProvider/res/drawable-xxhdpi/ic_root_mtp.png
Binary files differ
diff --git a/packages/MtpDocumentsProvider/res/drawable-xxxhdpi/ic_root_mtp.png b/packages/MtpDocumentsProvider/res/drawable-xxxhdpi/ic_root_mtp.png
new file mode 100644
index 0000000..fd2b795
--- /dev/null
+++ b/packages/MtpDocumentsProvider/res/drawable-xxxhdpi/ic_root_mtp.png
Binary files differ
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/CursorHelper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/CursorHelper.java
deleted file mode 100644
index 844b216..0000000
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/CursorHelper.java
+++ /dev/null
@@ -1,91 +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.mtp;
-
-import android.content.res.Resources;
-import android.database.MatrixCursor;
-import android.media.MediaFile;
-import android.mtp.MtpConstants;
-import android.mtp.MtpObjectInfo;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Document;
-
-/**
- * TODO Remove this class after we switch to use MtpDatabase.
- */
-final class CursorHelper {
- static final int DUMMY_HANDLE_FOR_ROOT = 0;
-
- private CursorHelper() {
- }
-
- static void addToCursor(Resources resources, MtpRoot root, MatrixCursor.RowBuilder builder) {
- final Identifier identifier = new Identifier(
- root.mDeviceId, root.mStorageId, DUMMY_HANDLE_FOR_ROOT);
- builder.add(Document.COLUMN_DOCUMENT_ID, identifier.toDocumentId());
- builder.add(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
- builder.add(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
- builder.add(Document.COLUMN_LAST_MODIFIED, null);
- builder.add(Document.COLUMN_FLAGS, 0);
- builder.add(Document.COLUMN_SIZE,
- (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
- }
-
- static void addToCursor(MtpObjectInfo objectInfo, Identifier rootIdentifier,
- MatrixCursor.RowBuilder builder) {
- final Identifier identifier = new Identifier(
- rootIdentifier.mDeviceId, rootIdentifier.mStorageId, objectInfo.getObjectHandle());
- final String mimeType = formatTypeToMimeType(objectInfo.getFormat());
-
- int flag = 0;
- if (objectInfo.getProtectionStatus() == 0) {
- flag |= DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
- DocumentsContract.Document.FLAG_SUPPORTS_WRITE;
- if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
- flag |= DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE;
- }
- }
- if (objectInfo.getThumbCompressedSize() > 0) {
- flag |= DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL;
- }
-
- builder.add(Document.COLUMN_DOCUMENT_ID, identifier.toDocumentId());
- builder.add(Document.COLUMN_DISPLAY_NAME, objectInfo.getName());
- builder.add(Document.COLUMN_MIME_TYPE, mimeType);
- builder.add(
- Document.COLUMN_LAST_MODIFIED,
- objectInfo.getDateModified() != 0 ? objectInfo.getDateModified() : null);
- builder.add(Document.COLUMN_FLAGS, flag);
- builder.add(Document.COLUMN_SIZE, objectInfo.getCompressedSize());
- }
-
- static String formatTypeToMimeType(int format) {
- if (format == MtpConstants.FORMAT_ASSOCIATION) {
- return DocumentsContract.Document.MIME_TYPE_DIR;
- } else {
- return MediaFile.getMimeTypeForFormatCode(format);
- }
- }
-
- static int mimeTypeToFormatType(String fileName, String mimeType) {
- if (Document.MIME_TYPE_DIR.equals(mimeType)) {
- return MtpConstants.FORMAT_ASSOCIATION;
- } else {
- return MediaFile.getFormatCode(fileName, mimeType);
- }
- }
-}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index 1c96906..6fa0df2 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -24,7 +24,6 @@
import android.os.Bundle;
import android.os.Process;
import android.provider.DocumentsContract;
-import android.util.Log;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -75,9 +74,15 @@
int parentHandle = parent.mObjectHandle;
// Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
// getObjectHandles if we would like to obtain children under the root.
- if (parentHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
+ if (parentHandle == Identifier.DUMMY_HANDLE_FOR_ROOT) {
parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
}
+ // TODO: Handle nit race around here.
+ // 1. getObjectHandles.
+ // 2. putNewDocument.
+ // 3. startAddingChildDocuemnts.
+ // 4. stopAddingChildDocuments - It removes the new document added at the step 2,
+ // because it is not updated between start/stopAddingChildDocuments.
task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles(
parent.mDeviceId, parent.mStorageId, parentHandle));
task.fillDocuments(loadDocuments(
@@ -215,8 +220,8 @@
throw new IOException(mError);
}
- final Cursor cursor = mDatabase.queryChildDocuments(
- columnNames, mIdentifier.mDocumentId, /* use old ID format */ true);
+ final Cursor cursor =
+ mDatabase.queryChildDocuments(columnNames, mIdentifier.mDocumentId);
cursor.setNotificationUri(resolver, createUri());
cursor.respond(extras);
@@ -250,10 +255,10 @@
return;
}
if (mNumLoaded == 0) {
- mDatabase.startAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().startAddingChildDocuments(mIdentifier.mDocumentId);
}
try {
- mDatabase.putChildDocuments(
+ mDatabase.getMapper().putChildDocuments(
mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList);
mNumLoaded += objectInfoList.length;
} catch (SQLiteException exp) {
@@ -261,7 +266,7 @@
mNumLoaded = 0;
}
if (getState() != STATE_LOADING) {
- mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
}
}
@@ -270,13 +275,13 @@
mError = message;
mNumLoaded = 0;
if (lastState == STATE_LOADING) {
- mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
}
}
private Uri createUri() {
return DocumentsContract.buildChildDocumentsUri(
- MtpDocumentsProvider.AUTHORITY, mIdentifier.toDocumentId());
+ MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId);
}
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
index 4238721e..20b3bf5 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
@@ -16,39 +16,19 @@
package com.android.mtp;
+import java.util.Objects;
+
/**
* Static utilities for ID.
*/
class Identifier {
+ final static int DUMMY_HANDLE_FOR_ROOT = 0;
+
final int mDeviceId;
final int mStorageId;
final int mObjectHandle;
final String mDocumentId;
- static Identifier createFromRootId(String rootId) {
- final String[] components = rootId.split("_");
- return new Identifier(
- Integer.parseInt(components[0]),
- Integer.parseInt(components[1]));
- }
-
- static Identifier createFromDocumentId(String documentId) {
- final String[] components = documentId.split("_");
- return new Identifier(
- Integer.parseInt(components[0]),
- Integer.parseInt(components[1]),
- Integer.parseInt(components[2]));
- }
-
-
- Identifier(int deviceId, int storageId) {
- this(deviceId, storageId, CursorHelper.DUMMY_HANDLE_FOR_ROOT);
- }
-
- Identifier(int deviceId, int storageId, int objectHandle) {
- this(deviceId, storageId, objectHandle, null);
- }
-
Identifier(int deviceId, int storageId, int objectHandle, String documentId) {
mDeviceId = deviceId;
mStorageId = storageId;
@@ -56,16 +36,6 @@
mDocumentId = documentId;
}
- // TODO: Make the ID persistent.
- String toRootId() {
- return String.format("%d_%d", mDeviceId, mStorageId);
- }
-
- // TODO: Make the ID persistent.
- String toDocumentId() {
- return String.format("%d_%d_%d", mDeviceId, mStorageId, mObjectHandle);
- }
-
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Identifier))
@@ -77,6 +47,6 @@
@Override
public int hashCode() {
- return (mDeviceId << 16) ^ (mStorageId << 8) ^ mObjectHandle;
+ return Objects.hash(mDeviceId, mStorageId, mObjectHandle, mDocumentId);
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
new file mode 100644
index 0000000..0d9d60c
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -0,0 +1,513 @@
+/*
+ * 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.mtp;
+
+import static com.android.mtp.MtpDatabaseConstants.*;
+
+import android.content.ContentValues;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.mtp.MtpObjectInfo;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.android.mtp.MtpDatabase.strings;
+
+
+/**
+ * Mapping operations for MtpDatabase.
+ * Also see the comments of {@link MtpDatabase}.
+ */
+class Mapper {
+ private final MtpDatabase mDatabase;
+
+ /**
+ * Mapping mode for roots/documents where we start adding child documents.
+ * Methods operate the state needs to be synchronized.
+ */
+ private final Map<String, Integer> mMappingMode = new HashMap<>();
+
+ Mapper(MtpDatabase database) {
+ mDatabase = database;
+ }
+
+ /**
+ * Invokes {@link #startAddingDocuments} for root documents.
+ * @param deviceId Device ID.
+ */
+ synchronized void startAddingRootDocuments(int deviceId) {
+ final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
+ Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
+ mMappingMode.put(
+ mappingStateKey,
+ startAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
+ }
+
+ /**
+ * Invokes {@link #startAddingDocuments} for child of specific documents.
+ * @param parentDocumentId Document ID for parent document.
+ */
+ @VisibleForTesting
+ synchronized void startAddingChildDocuments(String parentDocumentId) {
+ final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
+ Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
+ mMappingMode.put(
+ mappingStateKey,
+ startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
+ }
+
+ /**
+ * Puts root information to database.
+ * @param deviceId Device ID
+ * @param resources Resources required to localize root name.
+ * @param roots List of root information.
+ * @return If roots are added or removed from the database.
+ */
+ synchronized boolean putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
+ try {
+ final boolean heuristic;
+ final String mapColumn;
+ final String key = getRootDocumentsMappingStateKey(deviceId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ heuristic = false;
+ mapColumn = COLUMN_STORAGE_ID;
+ break;
+ case MAP_BY_NAME:
+ heuristic = true;
+ mapColumn = Document.COLUMN_DISPLAY_NAME;
+ break;
+ default:
+ throw new Error("Unexpected map mode.");
+ }
+ final ContentValues[] valuesList = new ContentValues[roots.length];
+ for (int i = 0; i < roots.length; i++) {
+ if (roots[i].mDeviceId != deviceId) {
+ throw new IllegalArgumentException();
+ }
+ valuesList[i] = new ContentValues();
+ MtpDatabase.getRootDocumentValues(valuesList[i], resources, roots[i]);
+ }
+ final boolean changed = putDocuments(
+ valuesList,
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ heuristic,
+ mapColumn);
+ final ContentValues values = new ContentValues();
+ int i = 0;
+ for (final MtpRoot root : roots) {
+ // Use the same value for the root ID and the corresponding document ID.
+ final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
+ // If it fails to insert/update documents, the document ID will be set with -1.
+ // In this case we don't insert/update root extra information neither.
+ if (documentId == null) {
+ continue;
+ }
+ values.put(Root.COLUMN_ROOT_ID, documentId);
+ values.put(
+ Root.COLUMN_FLAGS,
+ Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
+ values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
+ values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
+ values.put(Root.COLUMN_MIME_TYPES, "");
+ database.replace(TABLE_ROOT_EXTRA, null, values);
+ }
+ database.setTransactionSuccessful();
+ return changed;
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ /**
+ * Puts document information to database.
+ * @param deviceId Device ID
+ * @param parentId Parent document ID.
+ * @param documents List of document information.
+ */
+ synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
+ final boolean heuristic;
+ final String mapColumn;
+ final String key = getChildDocumentsMappingStateKey(parentId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ heuristic = false;
+ mapColumn = COLUMN_OBJECT_HANDLE;
+ break;
+ case MAP_BY_NAME:
+ heuristic = true;
+ mapColumn = Document.COLUMN_DISPLAY_NAME;
+ break;
+ default:
+ throw new Error("Unexpected map mode.");
+ }
+ final ContentValues[] valuesList = new ContentValues[documents.length];
+ for (int i = 0; i < documents.length; i++) {
+ valuesList[i] = new ContentValues();
+ MtpDatabase.getChildDocumentValues(
+ valuesList[i], deviceId, parentId, documents[i]);
+ }
+ putDocuments(
+ valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
+ }
+
+ /**
+ * Stops adding root documents.
+ * @param deviceId Device ID.
+ * @return True if new rows are added/removed.
+ */
+ synchronized boolean stopAddingRootDocuments(int deviceId) {
+ final String key = getRootDocumentsMappingStateKey(deviceId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ mMappingMode.remove(key);
+ return stopAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ COLUMN_STORAGE_ID);
+ case MAP_BY_NAME:
+ mMappingMode.remove(key);
+ return stopAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ Document.COLUMN_DISPLAY_NAME);
+ default:
+ throw new Error("Unexpected mapping state.");
+ }
+ }
+
+ /**
+ * Stops adding documents under the parent.
+ * @param parentId Document ID of the parent.
+ */
+ synchronized void stopAddingChildDocuments(String parentId) {
+ final String key = getChildDocumentsMappingStateKey(parentId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ stopAddingDocuments(
+ SELECTION_CHILD_DOCUMENTS,
+ parentId,
+ COLUMN_OBJECT_HANDLE);
+ break;
+ case MAP_BY_NAME:
+ stopAddingDocuments(
+ SELECTION_CHILD_DOCUMENTS,
+ parentId,
+ Document.COLUMN_DISPLAY_NAME);
+ break;
+ default:
+ throw new Error("Unexpected mapping state.");
+ }
+ mMappingMode.remove(key);
+ }
+
+ @VisibleForTesting
+ void clearMapping() {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
+ try {
+ mDatabase.deleteDocumentsAndRootsRecursively(
+ COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
+ final ContentValues values = new ContentValues();
+ values.putNull(COLUMN_OBJECT_HANDLE);
+ values.putNull(COLUMN_STORAGE_ID);
+ values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
+ database.update(TABLE_DOCUMENTS, values, null, null);
+ database.setTransactionSuccessful();
+ mMappingMode.clear();
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ /**
+ * Starts adding new documents.
+ * The methods decides mapping mode depends on if all documents under the given parent have MTP
+ * identifier or not. If all the documents have MTP identifier, it uses the identifier to find
+ * a corresponding existing row. Otherwise it does heuristic.
+ *
+ * @param selection Query matches valid documents.
+ * @param arg Argument for selection.
+ * @return Mapping mode.
+ */
+ private int startAddingDocuments(String selection, String arg) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
+ try {
+ // Delete all pending rows.
+ mDatabase.deleteDocumentsAndRootsRecursively(
+ selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING));
+
+ // Set all documents as invalidated.
+ final ContentValues values = new ContentValues();
+ values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
+ database.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
+
+ // If we have rows that does not have MTP identifier, do heuristic mapping by name.
+ final boolean useNameForResolving = DatabaseUtils.queryNumEntries(
+ database,
+ TABLE_DOCUMENTS,
+ selection + " AND " + COLUMN_STORAGE_ID + " IS NULL",
+ new String[] { arg }) > 0;
+ database.setTransactionSuccessful();
+ return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER;
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ /**
+ * Puts the documents into the database.
+ * If the mapping mode is not heuristic, it just adds the rows to the database or updates the
+ * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
+ * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
+ * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
+ * rows. If the methods adds rows to database, it updates valueList with correct document ID.
+ *
+ * @param valuesList Values for documents to be stored in the database.
+ * @param selection SQL where closure to select rows that shares the same parent.
+ * @param arg Argument for selection SQL.
+ * @param heuristic Whether the mapping mode is heuristic.
+ * @return Whether the method adds new rows.
+ */
+ private boolean putDocuments(
+ ContentValues[] valuesList,
+ String selection,
+ String arg,
+ boolean heuristic,
+ String mappingKey) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ boolean added = false;
+ database.beginTransaction();
+ try {
+ for (final ContentValues values : valuesList) {
+ final Cursor candidateCursor = database.query(
+ TABLE_DOCUMENTS,
+ strings(Document.COLUMN_DOCUMENT_ID),
+ selection + " AND " +
+ COLUMN_ROW_STATE + "=? AND " +
+ mappingKey + "=?",
+ strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)),
+ null,
+ null,
+ null,
+ "1");
+ try {
+ final long rowId;
+ if (candidateCursor.getCount() == 0) {
+ rowId = database.insert(TABLE_DOCUMENTS, null, values);
+ if (rowId == -1) {
+ throw new SQLiteException("Failed to put a document into database.");
+ }
+ added = true;
+ } else if (!heuristic) {
+ candidateCursor.moveToNext();
+ final String documentId = candidateCursor.getString(0);
+ rowId = database.update(
+ TABLE_DOCUMENTS,
+ values,
+ SELECTION_DOCUMENT_ID,
+ strings(documentId));
+ } else {
+ values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
+ rowId = database.insert(TABLE_DOCUMENTS, null, values);
+ }
+ // Document ID is a primary integer key of the table. So the returned row
+ // IDs should be same with the document ID.
+ values.put(Document.COLUMN_DOCUMENT_ID, rowId);
+ } finally {
+ candidateCursor.close();
+ }
+ }
+
+ database.setTransactionSuccessful();
+ return added;
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ /**
+ * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
+ * If the database does not find corresponding 'invalidated' document, it just removes
+ * 'invalidated' document from the database.
+ * @param selection Query to select rows for resolving.
+ * @param arg Argument for selection SQL.
+ * @param groupKey Column name used to find corresponding rows.
+ * @return Whether the methods adds or removed visible rows.
+ */
+ private boolean stopAddingDocuments(String selection, String arg, String groupKey) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
+ try {
+ // Get 1-to-1 mapping of invalidated document and pending document.
+ final String invalidatedIdQuery = createStateFilter(
+ ROW_STATE_INVALIDATED, Document.COLUMN_DOCUMENT_ID);
+ final String pendingIdQuery = createStateFilter(
+ ROW_STATE_PENDING, Document.COLUMN_DOCUMENT_ID);
+ // SQL should be like:
+ // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
+ // group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
+ // WHERE device_id = ? AND parent_document_id IS NULL
+ // GROUP BY display_name
+ // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
+ // count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
+ final Cursor mergingCursor = database.query(
+ TABLE_DOCUMENTS,
+ new String[] {
+ "group_concat(" + invalidatedIdQuery + ")",
+ "group_concat(" + pendingIdQuery + ")"
+ },
+ selection,
+ strings(arg),
+ groupKey,
+ "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
+ null);
+
+ final ContentValues values = new ContentValues();
+ while (mergingCursor.moveToNext()) {
+ final String invalidatedId = mergingCursor.getString(0);
+ final String pendingId = mergingCursor.getString(1);
+
+ // Obtain the new values including the latest object handle from mapping row.
+ getFirstRow(
+ TABLE_DOCUMENTS,
+ SELECTION_DOCUMENT_ID,
+ new String[] { pendingId },
+ values);
+ values.remove(Document.COLUMN_DOCUMENT_ID);
+ values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+ database.update(
+ TABLE_DOCUMENTS,
+ values,
+ SELECTION_DOCUMENT_ID,
+ new String[] { invalidatedId });
+
+ getFirstRow(
+ TABLE_ROOT_EXTRA,
+ SELECTION_ROOT_ID,
+ new String[] { pendingId },
+ values);
+ if (values.size() > 0) {
+ values.remove(Root.COLUMN_ROOT_ID);
+ database.update(
+ TABLE_ROOT_EXTRA,
+ values,
+ SELECTION_ROOT_ID,
+ new String[] { invalidatedId });
+ }
+
+ // Delete 'pending' row.
+ mDatabase.deleteDocumentsAndRootsRecursively(
+ SELECTION_DOCUMENT_ID, new String[] { pendingId });
+ }
+ mergingCursor.close();
+
+ boolean changed = false;
+
+ // Delete all invalidated rows that cannot be mapped.
+ if (mDatabase.deleteDocumentsAndRootsRecursively(
+ COLUMN_ROW_STATE + " = ? AND " + selection,
+ strings(ROW_STATE_INVALIDATED, arg))) {
+ changed = true;
+ }
+
+ // The database cannot find old document ID for the pending rows.
+ // Turn the all pending rows into valid state, which means the rows become to be
+ // valid with new document ID.
+ values.clear();
+ values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+ if (database.update(
+ TABLE_DOCUMENTS,
+ values,
+ COLUMN_ROW_STATE + " = ? AND " + selection,
+ strings(ROW_STATE_PENDING, arg)) != 0) {
+ changed = true;
+ }
+ database.setTransactionSuccessful();
+ return changed;
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ /**
+ * Obtains values of the first row for the query.
+ * @param values ContentValues that the values are stored to.
+ * @param table Target table.
+ * @param selection Query to select rows.
+ * @param args Argument for query.
+ */
+ private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ values.clear();
+ final Cursor cursor = database.query(table, null, selection, args, null, null, null, "1");
+ if (cursor.getCount() == 0) {
+ return;
+ }
+ cursor.moveToNext();
+ DatabaseUtils.cursorRowToContentValues(cursor, values);
+ cursor.close();
+ }
+
+ /**
+ * Gets SQL expression that represents the given value or NULL depends on the row state.
+ * You must pass static constants to this methods otherwise you may be suffered from SQL
+ * injections.
+ * @param state Expected row state.
+ * @param a SQL value.
+ * @return Expression that represents a if the row state is expected one, and represents NULL
+ * otherwise.
+ */
+ private static String createStateFilter(int state, String a) {
+ return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
+ " THEN " + a + " ELSE NULL END";
+ }
+
+ /**
+ * @param deviceId Device ID.
+ * @return Key for {@link #mMappingMode}.
+ */
+ private static String getRootDocumentsMappingStateKey(int deviceId) {
+ return "RootDocuments/" + deviceId;
+ }
+
+ /**
+ * @param parentDocumentId Document ID for the parent document.
+ * @return Key for {@link #mMappingMode}.
+ */
+ private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
+ return "ChildDocuments/" + parentDocumentId;
+ }
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 3dc69cc..51c0281 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -22,6 +22,12 @@
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.media.MediaFile;
+import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
@@ -29,8 +35,9 @@
import com.android.internal.annotations.VisibleForTesting;
-import java.util.HashMap;
-import java.util.Map;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Objects;
/**
* Database for MTP objects.
@@ -46,13 +53,13 @@
* by comparing the directory structure and object name.
*
* To start putting documents into the database, the client needs to call
- * {@link #startAddingChildDocuments(String)} with the parent document ID. Also it needs to call
- * {@link #stopAddingChildDocuments(String)} after putting all child documents to the database.
- * (All explanations are same for root documents)
+ * {@link Mapper#startAddingChildDocuments(String)} with the parent document ID. Also it
+ * needs to call {@link Mapper#stopAddingChildDocuments(String)} after putting all child
+ * documents to the database. (All explanations are same for root documents)
*
- * database.startAddingChildDocuments();
- * database.putChildDocuments();
- * database.stopAddingChildDocuments();
+ * database.getMapper().startAddingChildDocuments();
+ * database.getMapper().putChildDocuments();
+ * database.getMapper().stopAddingChildDocuments();
*
* To update the existing documents, the client code can repeat to call the three methods again.
* The newly added rows update corresponding existing rows that have same MTP identifier like
@@ -66,170 +73,232 @@
* the database tries to find corresponding rows by using document's name instead of MTP identifier
* at the next update cycle.
*
- * TODO: Remove @VisibleForTesting annotation when we start to use this class.
* TODO: Improve performance by SQL optimization.
*/
-@VisibleForTesting
class MtpDatabase {
- private final MtpDatabaseInternal mDatabase;
+ private final SQLiteDatabase mDatabase;
+ private final Mapper mMapper;
- /**
- * Mapping mode for roots/documents where we start adding child documents.
- * Methods operate the state needs to be synchronized.
- */
- private final Map<String, Integer> mMappingMode = new HashMap<>();
-
- @VisibleForTesting
- MtpDatabase(Context context, int flags) {
- mDatabase = new MtpDatabaseInternal(context, flags);
+ SQLiteDatabase getSQLiteDatabase() {
+ return mDatabase;
}
- /**
- * Closes the database.
- */
- @VisibleForTesting
+ MtpDatabase(Context context, int flags) {
+ final OpenHelper helper = new OpenHelper(context, flags);
+ mDatabase = helper.getWritableDatabase();
+ mMapper = new Mapper(this);
+ }
+
void close() {
mDatabase.close();
}
/**
- * {@link MtpDatabaseInternal#queryRoots}
+ * Returns operations for mapping.
+ * @return Mapping operations.
*/
- Cursor queryRoots(String[] columnNames) {
- return mDatabase.queryRoots(columnNames);
+ Mapper getMapper() {
+ return mMapper;
}
/**
- * {@link MtpDatabaseInternal#queryRootDocuments}
+ * Queries roots information.
+ * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}.
+ * @return Database cursor.
+ */
+ Cursor queryRoots(String[] columnNames) {
+ return mDatabase.query(
+ VIEW_ROOTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?)",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+ null,
+ null,
+ null);
+ }
+
+ /**
+ * Queries root documents information.
+ * @param columnNames Column names defined in
+ * {@link android.provider.DocumentsContract.Document}.
+ * @return Database cursor.
*/
@VisibleForTesting
Cursor queryRootDocuments(String[] columnNames) {
- return mDatabase.queryRootDocuments(columnNames);
+ return mDatabase.query(
+ TABLE_DOCUMENTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?)",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+ null,
+ null,
+ null);
}
/**
- * {@link MtpDatabaseInternal#queryChildDocuments}
+ * Queries documents information.
+ * @param columnNames Column names defined in
+ * {@link android.provider.DocumentsContract.Document}.
+ * @return Database cursor.
*/
- @VisibleForTesting
Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
- return queryChildDocuments(columnNames, parentDocumentId, false);
- }
-
- @VisibleForTesting
- Cursor queryChildDocuments(String[] columnNames, String parentDocumentId, boolean useOldId) {
- final String[] newColumnNames = new String[columnNames.length];
-
- // TODO: Temporary replace document ID with old format.
- for (int i = 0; i < columnNames.length; i++) {
- if (useOldId && DocumentsContract.Document.COLUMN_DOCUMENT_ID.equals(columnNames[i])) {
- newColumnNames[i] = COLUMN_DEVICE_ID + " || '_' || " + COLUMN_STORAGE_ID +
- " || '_' || IFNULL(" + COLUMN_OBJECT_HANDLE + ",0) AS " +
- DocumentsContract.Document.COLUMN_DOCUMENT_ID;
- } else {
- newColumnNames[i] = columnNames[i];
- }
- }
-
- return mDatabase.queryChildDocuments(newColumnNames, parentDocumentId);
- }
-
- Identifier createIdentifier(String parentDocumentId) {
- return mDatabase.createIdentifier(parentDocumentId);
+ return mDatabase.query(
+ TABLE_DOCUMENTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, parentDocumentId),
+ null,
+ null,
+ null);
}
/**
- * {@link MtpDatabaseInternal#removeDeviceRows}
+ * Queries a single document.
+ * @param documentId
+ * @param projection
+ * @return Database cursor.
*/
- void removeDeviceRows(int deviceId) {
- mDatabase.removeDeviceRows(deviceId);
+ public Cursor queryDocument(String documentId, String[] projection) {
+ return mDatabase.query(
+ TABLE_DOCUMENTS,
+ projection,
+ SELECTION_DOCUMENT_ID,
+ strings(documentId),
+ null,
+ null,
+ null,
+ "1");
}
/**
- * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for root documents.
+ * Remove all rows belong to a device.
* @param deviceId Device ID.
*/
- synchronized void startAddingRootDocuments(int deviceId) {
- final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
- if (mMappingMode.containsKey(mappingStateKey)) {
- throw new Error("Mapping for the root has already started.");
- }
- mMappingMode.put(
- mappingStateKey,
- mDatabase.startAddingDocuments(
- SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
+ void removeDeviceRows(int deviceId) {
+ // Call non-recursive version because it anyway deletes all rows in the devices.
+ deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId));
}
/**
- * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for child of specific documents.
- * @param parentDocumentId Document ID for parent document.
+ * Obtains parent document ID.
+ * @param documentId
+ * @return parent document ID.
+ * @throws FileNotFoundException
*/
- @VisibleForTesting
- synchronized void startAddingChildDocuments(String parentDocumentId) {
- final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
- if (mMappingMode.containsKey(mappingStateKey)) {
- throw new Error("Mapping for the root has already started.");
+ String getParentId(String documentId) throws FileNotFoundException {
+ final Cursor cursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(COLUMN_PARENT_DOCUMENT_ID),
+ SELECTION_DOCUMENT_ID,
+ strings(documentId),
+ null,
+ null,
+ null,
+ "1");
+ try {
+ if (cursor.moveToNext()) {
+ return cursor.getString(0);
+ } else {
+ throw new FileNotFoundException("Cannot find a row having ID=" + documentId);
+ }
+ } finally {
+ cursor.close();
}
- mMappingMode.put(
- mappingStateKey,
- mDatabase.startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
}
/**
- * Puts root information to database.
- * @param deviceId Device ID
- * @param resources Resources required to localize root name.
- * @param roots List of root information.
- * @return If roots are added or removed from the database.
+ * Adds new document under the parent.
+ * The method does not affect invalidated and pending documents because we know the document is
+ * newly added and never mapped with existing ones.
+ * @param parentDocumentId
+ * @param info
+ * @return Document ID of added document.
*/
- synchronized boolean putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
+ String putNewDocument(int deviceId, String parentDocumentId, MtpObjectInfo info) {
+ final ContentValues values = new ContentValues();
+ getChildDocumentValues(values, deviceId, parentDocumentId, info);
mDatabase.beginTransaction();
try {
- final boolean heuristic;
- final String mapColumn;
- switch (mMappingMode.get(getRootDocumentsMappingStateKey(deviceId))) {
- case MAP_BY_MTP_IDENTIFIER:
- heuristic = false;
- mapColumn = COLUMN_STORAGE_ID;
- break;
- case MAP_BY_NAME:
- heuristic = true;
- mapColumn = Document.COLUMN_DISPLAY_NAME;
- break;
- default:
- throw new Error("Unexpected map mode.");
+ final long id = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+ mDatabase.setTransactionSuccessful();
+ return Long.toString(id);
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
+
+ /**
+ * Deletes document and its children.
+ * @param documentId
+ */
+ void deleteDocument(String documentId) {
+ deleteDocumentsAndRootsRecursively(SELECTION_DOCUMENT_ID, strings(documentId));
+ }
+
+ /**
+ * Gets identifier from document ID.
+ * @param documentId Document ID.
+ * @return Identifier.
+ * @throws FileNotFoundException
+ */
+ Identifier createIdentifier(String documentId) throws FileNotFoundException {
+ // Currently documentId is old format.
+ final Cursor cursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(COLUMN_DEVICE_ID, COLUMN_STORAGE_ID, COLUMN_OBJECT_HANDLE),
+ SELECTION_DOCUMENT_ID,
+ strings(documentId),
+ null,
+ null,
+ null,
+ "1");
+ try {
+ if (cursor.getCount() == 0) {
+ throw new FileNotFoundException("ID is not found.");
+ } else {
+ cursor.moveToNext();
+ return new Identifier(
+ cursor.getInt(0),
+ cursor.getInt(1),
+ cursor.isNull(2) ? Identifier.DUMMY_HANDLE_FOR_ROOT : cursor.getInt(2),
+ documentId);
}
- final ContentValues[] valuesList = new ContentValues[roots.length];
- for (int i = 0; i < roots.length; i++) {
- if (roots[i].mDeviceId != deviceId) {
- throw new IllegalArgumentException();
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Deletes a document, and its root information if the document is a root document.
+ * @param selection Query to select documents.
+ * @param args Arguments for selection.
+ * @return Whether the method deletes rows.
+ */
+ boolean deleteDocumentsAndRootsRecursively(String selection, String[] args) {
+ mDatabase.beginTransaction();
+ try {
+ boolean changed = false;
+ final Cursor cursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(Document.COLUMN_DOCUMENT_ID),
+ selection,
+ args,
+ null,
+ null,
+ null);
+ try {
+ while (cursor.moveToNext()) {
+ if (deleteDocumentsAndRootsRecursively(
+ COLUMN_PARENT_DOCUMENT_ID + "=?",
+ strings(cursor.getString(0)))) {
+ changed = true;
+ }
}
- valuesList[i] = new ContentValues();
- getRootDocumentValues(valuesList[i], resources, roots[i]);
+ } finally {
+ cursor.close();
}
- final boolean changed = mDatabase.putDocuments(
- valuesList,
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
- heuristic,
- mapColumn);
- final ContentValues values = new ContentValues();
- int i = 0;
- for (final MtpRoot root : roots) {
- // Use the same value for the root ID and the corresponding document ID.
- final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
- // If it fails to insert/update documents, the document ID will be set with -1.
- // In this case we don't insert/update root extra information neither.
- if (documentId == null) {
- continue;
- }
- values.put(Root.COLUMN_ROOT_ID, documentId);
- values.put(
- Root.COLUMN_FLAGS,
- Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
- values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
- values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
- values.put(Root.COLUMN_MIME_TYPES, "");
- mDatabase.putRootExtra(values);
+ if (deleteDocumentsAndRoots(selection, args)) {
+ changed = true;
}
mDatabase.setTransactionSuccessful();
return changed;
@@ -239,94 +308,80 @@
}
/**
- * Puts document information to database.
- * @param deviceId Device ID
- * @param parentId Parent document ID.
- * @param documents List of document information.
+ * Returns the set of device ID stored in the database.
*/
- @VisibleForTesting
- synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
- final boolean heuristic;
- final String mapColumn;
- switch (mMappingMode.get(getChildDocumentsMappingStateKey(parentId))) {
- case MAP_BY_MTP_IDENTIFIER:
- heuristic = false;
- mapColumn = COLUMN_OBJECT_HANDLE;
- break;
- case MAP_BY_NAME:
- heuristic = true;
- mapColumn = Document.COLUMN_DISPLAY_NAME;
- break;
- default:
- throw new Error("Unexpected map mode.");
- }
- final ContentValues[] valuesList = new ContentValues[documents.length];
- for (int i = 0; i < documents.length; i++) {
- valuesList[i] = new ContentValues();
- getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]);
- }
- mDatabase.putDocuments(
- valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
- }
-
- /**
- * Clears mapping between MTP identifier and document/root ID.
- */
- @VisibleForTesting
- synchronized void clearMapping() {
- mDatabase.clearMapping();
- mMappingMode.clear();
- }
-
- /**
- * Stops adding root documents.
- * @param deviceId Device ID.
- * @return True if new rows are added/removed.
- */
- synchronized boolean stopAddingRootDocuments(int deviceId) {
- final String mappingModeKey = getRootDocumentsMappingStateKey(deviceId);
- switch (mMappingMode.get(mappingModeKey)) {
- case MAP_BY_MTP_IDENTIFIER:
- mMappingMode.remove(mappingModeKey);
- return mDatabase.stopAddingDocuments(
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
- COLUMN_STORAGE_ID);
- case MAP_BY_NAME:
- mMappingMode.remove(mappingModeKey);
- return mDatabase.stopAddingDocuments(
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
- Document.COLUMN_DISPLAY_NAME);
- default:
- throw new Error("Unexpected mapping state.");
+ int[] getDeviceIds() {
+ final Cursor cursor = mDatabase.query(
+ true,
+ TABLE_DOCUMENTS,
+ strings(COLUMN_DEVICE_ID),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null);
+ try {
+ final int[] ids = new int[cursor.getCount()];
+ for (int i = 0; i < ids.length; i++) {
+ cursor.moveToNext();
+ ids[i] = cursor.getInt(0);
+ }
+ return ids;
+ } finally {
+ cursor.close();
}
}
- /**
- * Stops adding documents under the parent.
- * @param parentId Document ID of the parent.
- */
- @VisibleForTesting
- synchronized void stopAddingChildDocuments(String parentId) {
- final String mappingModeKey = getChildDocumentsMappingStateKey(parentId);
- switch (mMappingMode.get(mappingModeKey)) {
- case MAP_BY_MTP_IDENTIFIER:
- mDatabase.stopAddingDocuments(
- SELECTION_CHILD_DOCUMENTS,
- parentId,
- COLUMN_OBJECT_HANDLE);
- break;
- case MAP_BY_NAME:
- mDatabase.stopAddingDocuments(
- SELECTION_CHILD_DOCUMENTS,
- parentId,
- Document.COLUMN_DISPLAY_NAME);
- break;
- default:
- throw new Error("Unexpected mapping state.");
+ private boolean deleteDocumentsAndRoots(String selection, String[] args) {
+ mDatabase.beginTransaction();
+ try {
+ int deleted = 0;
+ deleted += mDatabase.delete(
+ TABLE_ROOT_EXTRA,
+ Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
+ false,
+ TABLE_DOCUMENTS,
+ new String[] { Document.COLUMN_DOCUMENT_ID },
+ selection,
+ null,
+ null,
+ null,
+ null) + ")",
+ args);
+ deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args);
+ mDatabase.setTransactionSuccessful();
+ // TODO Remove mappingState.
+ return deleted != 0;
+ } finally {
+ mDatabase.endTransaction();
}
- mMappingMode.remove(mappingModeKey);
+ }
+
+ private static class OpenHelper extends SQLiteOpenHelper {
+ public OpenHelper(Context context, int flags) {
+ super(context,
+ flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME,
+ null,
+ DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(QUERY_CREATE_DOCUMENTS);
+ db.execSQL(QUERY_CREATE_ROOT_EXTRA);
+ db.execSQL(QUERY_CREATE_VIEW_ROOTS);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @VisibleForTesting
+ static void deleteDatabase(Context context) {
+ context.deleteDatabase(DATABASE_NAME);
}
/**
@@ -335,8 +390,7 @@
* @param resources Resources used to get localized root name.
* @param root Root to be converted {@link ContentValues}.
*/
- private static void getRootDocumentValues(
- ContentValues values, Resources resources, MtpRoot root) {
+ static void getRootDocumentValues(ContentValues values, Resources resources, MtpRoot root) {
values.clear();
values.put(COLUMN_DEVICE_ID, root.mDeviceId);
values.put(COLUMN_STORAGE_ID, root.mStorageId);
@@ -347,7 +401,7 @@
values.put(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
values.putNull(Document.COLUMN_SUMMARY);
values.putNull(Document.COLUMN_LAST_MODIFIED);
- values.putNull(Document.COLUMN_ICON);
+ values.put(Document.COLUMN_ICON, R.drawable.ic_root_mtp);
values.put(Document.COLUMN_FLAGS, 0);
values.put(Document.COLUMN_SIZE,
(int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
@@ -360,10 +414,12 @@
* @param parentId Parent document ID of the object.
* @param info MTP object info.
*/
- private void getChildDocumentValues(
+ static void getChildDocumentValues(
ContentValues values, int deviceId, String parentId, MtpObjectInfo info) {
values.clear();
- final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat());
+ final String mimeType = info.getFormat() == MtpConstants.FORMAT_ASSOCIATION ?
+ DocumentsContract.Document.MIME_TYPE_DIR :
+ MediaFile.getMimeTypeForFormatCode(info.getFormat());
int flag = 0;
if (info.getProtectionStatus() == 0) {
flag |= Document.FLAG_SUPPORTS_DELETE |
@@ -391,19 +447,11 @@
values.put(Document.COLUMN_SIZE, info.getCompressedSize());
}
- /**
- * @param deviceId Device ID.
- * @return Key for {@link #mMappingMode}.
- */
- private static String getRootDocumentsMappingStateKey(int deviceId) {
- return "RootDocuments/" + deviceId;
- }
-
- /**
- * @param parentDocumentId Document ID for the parent document.
- * @return Key for {@link #mMappingMode}.
- */
- private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
- return "ChildDocuments/" + parentDocumentId;
+ static String[] strings(Object... args) {
+ final String[] results = new String[args.length];
+ for (int i = 0; i < args.length; i++) {
+ results[i] = Objects.toString(args[i]);
+ }
+ return results;
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
index 97c1d29..0ead2d5 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -24,7 +24,7 @@
*/
class MtpDatabaseConstants {
static final int DATABASE_VERSION = 1;
- static final String DATABASE_NAME = null;
+ static final String DATABASE_NAME = "database";
static final int FLAG_DATABASE_IN_MEMORY = 1;
static final int FLAG_DATABASE_IN_FILE = 0;
@@ -129,11 +129,7 @@
Root.COLUMN_TITLE + "," +
TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " +
Root.COLUMN_SUMMARY + "," +
- // Temporary replace COLUMN_DOCUMENT_ID with old format.
TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
- Root.COLUMN_DOCUMENT_ID + "_," +
- TABLE_DOCUMENTS + "." + COLUMN_DEVICE_ID + "|| '_' ||" +
- TABLE_DOCUMENTS + "." + COLUMN_STORAGE_ID + "||'_0' AS " +
Root.COLUMN_DOCUMENT_ID + "," +
TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES + "," +
TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES + "," +
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
deleted file mode 100644
index 9c5d6b6..0000000
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
+++ /dev/null
@@ -1,515 +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.mtp;
-
-import static com.android.mtp.MtpDatabaseConstants.*;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.provider.DocumentsContract.Document;
-import android.provider.DocumentsContract.Root;
-
-import java.util.Objects;
-
-/**
- * Class that provides operations processing SQLite database directly.
- */
-class MtpDatabaseInternal {
- private static class OpenHelper extends SQLiteOpenHelper {
- public OpenHelper(Context context, int flags) {
- super(context,
- flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME,
- null,
- DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL(QUERY_CREATE_DOCUMENTS);
- db.execSQL(QUERY_CREATE_ROOT_EXTRA);
- db.execSQL(QUERY_CREATE_VIEW_ROOTS);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- throw new UnsupportedOperationException();
- }
- }
-
- private final SQLiteDatabase mDatabase;
-
- MtpDatabaseInternal(Context context, int flags) {
- final OpenHelper helper = new OpenHelper(context, flags);
- mDatabase = helper.getWritableDatabase();
- }
-
- void close() {
- mDatabase.close();
- }
-
- /**
- * Queries roots information.
- * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}.
- * @return Database cursor.
- */
- Cursor queryRoots(String[] columnNames) {
- return mDatabase.query(
- VIEW_ROOTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?)",
- strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
- null,
- null,
- null);
- }
-
- /**
- * Queries root documents information.
- * @param columnNames Column names defined in
- * {@link android.provider.DocumentsContract.Document}.
- * @return Database cursor.
- */
- Cursor queryRootDocuments(String[] columnNames) {
- return mDatabase.query(
- TABLE_DOCUMENTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?)",
- strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
- null,
- null,
- null);
- }
-
- /**
- * Queries documents information.
- * @param columnNames Column names defined in
- * {@link android.provider.DocumentsContract.Document}.
- * @return Database cursor.
- */
- Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
- return mDatabase.query(
- TABLE_DOCUMENTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
- strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, parentDocumentId),
- null,
- null,
- null);
- }
-
- /**
- * Remove all rows belong to a device.
- * @param deviceId Device ID.
- */
- void removeDeviceRows(int deviceId) {
- deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId));
- }
-
- /**
- * Gets identifier from document ID.
- * @param documentId Document ID.
- * @return Identifier.
- */
- Identifier createIdentifier(String documentId) {
- // Currently documentId is old format.
- final Identifier oldIdentifier = Identifier.createFromDocumentId(documentId);
- final String selection;
- final String[] args;
- if (oldIdentifier.mObjectHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
- selection = COLUMN_DEVICE_ID + "= ? AND " +
- COLUMN_ROW_STATE + " IN (?, ?) AND " +
- COLUMN_STORAGE_ID + "= ? AND " +
- COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
- args = strings(
- oldIdentifier.mDeviceId,
- ROW_STATE_VALID,
- ROW_STATE_INVALIDATED,
- oldIdentifier.mStorageId);
- } else {
- selection = COLUMN_DEVICE_ID + "= ? AND " +
- COLUMN_ROW_STATE + " IN (?, ?) AND " +
- COLUMN_STORAGE_ID + "= ? AND " +
- COLUMN_OBJECT_HANDLE + " = ?";
- args = strings(
- oldIdentifier.mDeviceId,
- ROW_STATE_VALID,
- ROW_STATE_INVALIDATED,
- oldIdentifier.mStorageId,
- oldIdentifier.mObjectHandle);
- }
-
- final Cursor cursor = mDatabase.query(
- TABLE_DOCUMENTS,
- strings(Document.COLUMN_DOCUMENT_ID),
- selection,
- args,
- null,
- null,
- null,
- "1");
- try {
- if (cursor.getCount() == 0) {
- return oldIdentifier;
- } else {
- cursor.moveToNext();
- return new Identifier(
- oldIdentifier.mDeviceId,
- oldIdentifier.mStorageId,
- oldIdentifier.mObjectHandle,
- cursor.getString(0));
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Starts adding new documents.
- * The methods decides mapping mode depends on if all documents under the given parent have MTP
- * identifier or not. If all the documents have MTP identifier, it uses the identifier to find
- * a corresponding existing row. Otherwise it does heuristic.
- *
- * @param selection Query matches valid documents.
- * @param arg Argument for selection.
- * @return Mapping mode.
- */
- int startAddingDocuments(String selection, String arg) {
- mDatabase.beginTransaction();
- try {
- // Delete all pending rows.
- deleteDocumentsAndRoots(
- selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING));
-
- // Set all documents as invalidated.
- final ContentValues values = new ContentValues();
- values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
- mDatabase.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
-
- // If we have rows that does not have MTP identifier, do heuristic mapping by name.
- final boolean useNameForResolving = DatabaseUtils.queryNumEntries(
- mDatabase,
- TABLE_DOCUMENTS,
- selection + " AND " + COLUMN_STORAGE_ID + " IS NULL",
- new String[] { arg }) > 0;
- mDatabase.setTransactionSuccessful();
- return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER;
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * Puts the documents into the database.
- * If the mapping mode is not heuristic, it just adds the rows to the database or updates the
- * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
- * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
- * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
- * rows. If the methods adds rows to database, it updates valueList with correct document ID.
- *
- * @param valuesList Values for documents to be stored in the database.
- * @param selection SQL where closure to select rows that shares the same parent.
- * @param arg Argument for selection SQL.
- * @param heuristic Whether the mapping mode is heuristic.
- * @return Whether the method adds new rows.
- */
- boolean putDocuments(
- ContentValues[] valuesList,
- String selection,
- String arg,
- boolean heuristic,
- String mappingKey) {
- boolean added = false;
- mDatabase.beginTransaction();
- try {
- for (final ContentValues values : valuesList) {
- final Cursor candidateCursor = mDatabase.query(
- TABLE_DOCUMENTS,
- strings(Document.COLUMN_DOCUMENT_ID),
- selection + " AND " +
- COLUMN_ROW_STATE + "=? AND " +
- mappingKey + "=?",
- strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)),
- null,
- null,
- null,
- "1");
- try {
- final long rowId;
- if (candidateCursor.getCount() == 0) {
- rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
- if (rowId == -1) {
- throw new SQLiteException("Failed to put a document into database.");
- }
- added = true;
- } else if (!heuristic) {
- candidateCursor.moveToNext();
- final String documentId = candidateCursor.getString(0);
- rowId = mDatabase.update(
- TABLE_DOCUMENTS,
- values,
- SELECTION_DOCUMENT_ID,
- strings(documentId));
- } else {
- values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
- rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
- }
- // Document ID is a primary integer key of the table. So the returned row
- // IDs should be same with the document ID.
- values.put(Document.COLUMN_DOCUMENT_ID, rowId);
- } finally {
- candidateCursor.close();
- }
- }
-
- mDatabase.setTransactionSuccessful();
- return added;
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * Puts extra information for root documents.
- * @param values Values containing extra information.
- */
- void putRootExtra(ContentValues values) {
- mDatabase.replace(TABLE_ROOT_EXTRA, null, values);
- }
-
- /**
- * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
- * If the database does not find corresponding 'invalidated' document, it just removes
- * 'invalidated' document from the database.
- * @param selection Query to select rows for resolving.
- * @param arg Argument for selection SQL.
- * @param groupKey Column name used to find corresponding rows.
- * @return Whether the methods adds or removed visible rows.
- */
- boolean stopAddingDocuments(String selection, String arg, String groupKey) {
- mDatabase.beginTransaction();
- try {
- // Get 1-to-1 mapping of invalidated document and pending document.
- final String invalidatedIdQuery = createStateFilter(
- ROW_STATE_INVALIDATED, Document.COLUMN_DOCUMENT_ID);
- final String pendingIdQuery = createStateFilter(
- ROW_STATE_PENDING, Document.COLUMN_DOCUMENT_ID);
- // SQL should be like:
- // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
- // group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
- // WHERE device_id = ? AND parent_document_id IS NULL
- // GROUP BY display_name
- // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
- // count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
- final Cursor mergingCursor = mDatabase.query(
- TABLE_DOCUMENTS,
- new String[] {
- "group_concat(" + invalidatedIdQuery + ")",
- "group_concat(" + pendingIdQuery + ")"
- },
- selection,
- strings(arg),
- groupKey,
- "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
- null);
-
- final ContentValues values = new ContentValues();
- while (mergingCursor.moveToNext()) {
- final String invalidatedId = mergingCursor.getString(0);
- final String pendingId = mergingCursor.getString(1);
-
- // Obtain the new values including the latest object handle from mapping row.
- getFirstRow(
- TABLE_DOCUMENTS,
- SELECTION_DOCUMENT_ID,
- new String[] { pendingId },
- values);
- values.remove(Document.COLUMN_DOCUMENT_ID);
- values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
- mDatabase.update(
- TABLE_DOCUMENTS,
- values,
- SELECTION_DOCUMENT_ID,
- new String[] { invalidatedId });
-
- getFirstRow(
- TABLE_ROOT_EXTRA,
- SELECTION_ROOT_ID,
- new String[] { pendingId },
- values);
- if (values.size() > 0) {
- values.remove(Root.COLUMN_ROOT_ID);
- mDatabase.update(
- TABLE_ROOT_EXTRA,
- values,
- SELECTION_ROOT_ID,
- new String[] { invalidatedId });
- }
-
- // Delete 'pending' row.
- deleteDocumentsAndRoots(SELECTION_DOCUMENT_ID, new String[] { pendingId });
- }
- mergingCursor.close();
-
- boolean changed = false;
-
- // Delete all invalidated rows that cannot be mapped.
- if (deleteDocumentsAndRoots(
- COLUMN_ROW_STATE + " = ? AND " + selection,
- strings(ROW_STATE_INVALIDATED, arg))) {
- changed = true;
- }
-
- // The database cannot find old document ID for the pending rows.
- // Turn the all pending rows into valid state, which means the rows become to be
- // valid with new document ID.
- values.clear();
- values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
- if (mDatabase.update(
- TABLE_DOCUMENTS,
- values,
- COLUMN_ROW_STATE + " = ? AND " + selection,
- strings(ROW_STATE_PENDING, arg)) != 0) {
- changed = true;
- }
- mDatabase.setTransactionSuccessful();
- return changed;
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * Clears MTP related identifier.
- * It clears MTP's object handle and storage ID that are not stable over MTP sessions and mark
- * the all documents as 'invalidated'. It also remove 'pending' rows as adding is cancelled
- * now.
- */
- void clearMapping() {
- mDatabase.beginTransaction();
- try {
- deleteDocumentsAndRoots(COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
- final ContentValues values = new ContentValues();
- values.putNull(COLUMN_OBJECT_HANDLE);
- values.putNull(COLUMN_STORAGE_ID);
- values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
- mDatabase.update(TABLE_DOCUMENTS, values, null, null);
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * {@link android.database.sqlite.SQLiteDatabase#beginTransaction()}
- */
- void beginTransaction() {
- mDatabase.beginTransaction();
- }
-
- /**
- * {@link android.database.sqlite.SQLiteDatabase#setTransactionSuccessful()}
- */
- void setTransactionSuccessful() {
- mDatabase.setTransactionSuccessful();
- }
-
- /**
- * {@link android.database.sqlite.SQLiteDatabase#endTransaction()}
- */
- void endTransaction() {
- mDatabase.endTransaction();
- }
-
- /**
- * Deletes a document, and its root information if the document is a root document.
- * @param selection Query to select documents.
- * @param args Arguments for selection.
- * @return Whether the method deletes rows.
- */
- private boolean deleteDocumentsAndRoots(String selection, String[] args) {
- mDatabase.beginTransaction();
- try {
- int deleted = 0;
- deleted += mDatabase.delete(
- TABLE_ROOT_EXTRA,
- Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
- false,
- TABLE_DOCUMENTS,
- new String[] { Document.COLUMN_DOCUMENT_ID },
- selection,
- null,
- null,
- null,
- null) + ")",
- args);
- deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args);
- mDatabase.setTransactionSuccessful();
- return deleted != 0;
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * Obtains values of the first row for the query.
- * @param values ContentValues that the values are stored to.
- * @param table Target table.
- * @param selection Query to select rows.
- * @param args Argument for query.
- */
- private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
- values.clear();
- final Cursor cursor = mDatabase.query(table, null, selection, args, null, null, null, "1");
- if (cursor.getCount() == 0) {
- return;
- }
- cursor.moveToNext();
- DatabaseUtils.cursorRowToContentValues(cursor, values);
- cursor.close();
- }
-
- /**
- * Gets SQL expression that represents the given value or NULL depends on the row state.
- * @param state Expected row state.
- * @param a SQL value.
- * @return Expression that represents a if the row state is expected one, and represents NULL
- * otherwise.
- */
- private static String createStateFilter(int state, String a) {
- return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
- " THEN " + a + " ELSE NULL END";
- }
-
- /**
- * Converts values into string array.
- * @param args Values converted into string array.
- * @return String array.
- */
- private static String[] strings(Object... args) {
- final String[] results = new String[args.length];
- for (int i = 0; i < args.length; i++) {
- results[i] = Objects.toString(args[i]);
- }
- return results;
- }
-}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 7c0676f..9511e15 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -20,8 +20,9 @@
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.Cursor;
-import android.database.MatrixCursor;
import android.graphics.Point;
+import android.media.MediaFile;
+import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
@@ -56,11 +57,13 @@
Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
};
+ private final Object mDeviceListLock = new Object();
+
private static MtpDocumentsProvider sSingleton;
private MtpManager mMtpManager;
private ContentResolver mResolver;
- @GuardedBy("mDeviceToolkits")
+ @GuardedBy("mDeviceListLock")
private Map<Integer, DeviceToolkit> mDeviceToolkits;
private RootScanner mRootScanner;
private Resources mResources;
@@ -82,6 +85,7 @@
mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
+ resume();
return true;
}
@@ -97,6 +101,7 @@
mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
mDatabase = database;
mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
+ resume();
}
@Override
@@ -116,39 +121,7 @@
if (projection == null) {
projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
}
- final Identifier identifier = Identifier.createFromDocumentId(documentId);
-
- if (identifier.mObjectHandle != CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
- MtpObjectInfo objectInfo;
- try {
- objectInfo = mMtpManager.getObjectInfo(
- identifier.mDeviceId, identifier.mObjectHandle);
- } catch (IOException e) {
- throw new FileNotFoundException(e.getMessage());
- }
- final MatrixCursor cursor = new MatrixCursor(projection);
- CursorHelper.addToCursor(
- objectInfo,
- new Identifier(identifier.mDeviceId, identifier.mStorageId),
- cursor.newRow());
- return cursor;
- } else {
- MtpRoot[] roots;
- try {
- roots = mMtpManager.getRoots(identifier.mDeviceId);
- } catch (IOException e) {
- throw new FileNotFoundException(e.getMessage());
- }
- for (final MtpRoot root : roots) {
- if (identifier.mStorageId != root.mStorageId)
- continue;
- final MatrixCursor cursor = new MatrixCursor(projection);
- CursorHelper.addToCursor(mResources, root, cursor.newRow());
- return cursor;
- }
- }
-
- throw new FileNotFoundException();
+ return mDatabase.queryDocument(documentId, projection);
}
@Override
@@ -170,7 +143,7 @@
public ParcelFileDescriptor openDocument(
String documentId, String mode, CancellationSignal signal)
throws FileNotFoundException {
- final Identifier identifier = Identifier.createFromDocumentId(documentId);
+ final Identifier identifier = mDatabase.createIdentifier(documentId);
try {
switch (mode) {
case "r":
@@ -195,7 +168,7 @@
String documentId,
Point sizeHint,
CancellationSignal signal) throws FileNotFoundException {
- final Identifier identifier = Identifier.createFromDocumentId(documentId);
+ final Identifier identifier = mDatabase.createIdentifier(documentId);
try {
return new AssetFileDescriptor(
getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
@@ -209,22 +182,24 @@
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
try {
- final Identifier identifier = Identifier.createFromDocumentId(documentId);
- final int parentHandle =
- mMtpManager.getParent(identifier.mDeviceId, identifier.mObjectHandle);
+ final Identifier identifier = mDatabase.createIdentifier(documentId);
+ final Identifier parentIdentifier =
+ mDatabase.createIdentifier(mDatabase.getParentId(documentId));
mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
- final Identifier parentIdentifier = new Identifier(
- identifier.mDeviceId, identifier.mStorageId, parentHandle);
+ mDatabase.deleteDocument(documentId);
getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
- notifyChildDocumentsChange(parentIdentifier.toDocumentId());
+ notifyChildDocumentsChange(parentIdentifier.mDocumentId);
} catch (IOException error) {
+ for (final StackTraceElement element : error.getStackTrace()) {
+ Log.e("hirono", element.toString());
+ }
throw new FileNotFoundException(error.getMessage());
}
}
@Override
public void onTrimMemory(int level) {
- synchronized (mDeviceToolkits) {
+ synchronized (mDeviceListLock) {
for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
toolkit.mDocumentLoader.clearCompletedTasks();
}
@@ -235,19 +210,23 @@
public String createDocument(String parentDocumentId, String mimeType, String displayName)
throws FileNotFoundException {
try {
- final Identifier parentId = Identifier.createFromDocumentId(parentDocumentId);
+ final Identifier parentId = mDatabase.createIdentifier(parentDocumentId);
final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe();
pipe[0].close(); // 0 bytes for a new document.
- final int objectHandle = mMtpManager.createDocument(
- parentId.mDeviceId,
- new MtpObjectInfo.Builder()
- .setStorageId(parentId.mStorageId)
- .setParent(parentId.mObjectHandle)
- .setFormat(CursorHelper.mimeTypeToFormatType(displayName, mimeType))
- .setName(displayName)
- .build(), pipe[1]);
- final String documentId = new Identifier(parentId.mDeviceId, parentId.mStorageId,
- objectHandle).toDocumentId();
+ final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
+ MtpConstants.FORMAT_ASSOCIATION :
+ MediaFile.getFormatCode(displayName, mimeType);
+ final MtpObjectInfo info = new MtpObjectInfo.Builder()
+ .setStorageId(parentId.mStorageId)
+ .setParent(parentId.mObjectHandle)
+ .setFormat(formatCode)
+ .setName(displayName)
+ .build();
+ final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]);
+ final MtpObjectInfo infoWithHandle =
+ new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
+ final String documentId = mDatabase.putNewDocument(
+ parentId.mDeviceId, parentDocumentId, infoWithHandle);
getDocumentLoader(parentId).clearTask(parentId);
notifyChildDocumentsChange(parentDocumentId);
return documentId;
@@ -258,47 +237,26 @@
}
void openDevice(int deviceId) throws IOException {
- synchronized (mDeviceToolkits) {
+ synchronized (mDeviceListLock) {
mMtpManager.openDevice(deviceId);
- mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
+ mDeviceToolkits.put(
+ deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
}
mRootScanner.resume();
}
void closeDevice(int deviceId) throws IOException, InterruptedException {
- // TODO: Flush the device before closing (if not closed externally).
- synchronized (mDeviceToolkits) {
- getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
- mDeviceToolkits.remove(deviceId);
+ synchronized (mDeviceListLock) {
+ closeDeviceInternal(deviceId);
mDatabase.removeDeviceRows(deviceId);
- mMtpManager.closeDevice(deviceId);
}
mRootScanner.notifyChange();
- if (!hasOpenedDevices()) {
- mRootScanner.pause();
- }
- }
-
- synchronized void closeAllDevices() throws InterruptedException {
- boolean closed = false;
- for (int deviceId : mMtpManager.getOpenedDeviceIds()) {
- try {
- mDatabase.removeDeviceRows(deviceId);
- mMtpManager.closeDevice(deviceId);
- getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
- closed = true;
- } catch (IOException d) {
- Log.d(TAG, "Failed to close the MTP device: " + deviceId);
- }
- }
- if (closed) {
- mRootScanner.notifyChange();
- mRootScanner.pause();
- }
}
boolean hasOpenedDevices() {
- return mMtpManager.getOpenedDeviceIds().length != 0;
+ synchronized (mDeviceListLock) {
+ return mMtpManager.getOpenedDeviceIds().length != 0;
+ }
}
/**
@@ -306,14 +264,18 @@
*/
@Override
public void shutdown() {
- try {
- closeAllDevices();
- } catch (InterruptedException e) {
- // It should fail unit tests by throwing runtime exception.
- throw new RuntimeException(e.getMessage());
- } finally {
- mDatabase.close();
- super.shutdown();
+ synchronized (mDeviceListLock) {
+ try {
+ for (final int id : mMtpManager.getOpenedDeviceIds()) {
+ closeDeviceInternal(id);
+ }
+ } catch (InterruptedException|IOException e) {
+ // It should fail unit tests by throwing runtime exception.
+ throw new RuntimeException(e);
+ } finally {
+ mDatabase.close();
+ super.shutdown();
+ }
}
}
@@ -324,8 +286,35 @@
false);
}
+ /**
+ * Reopens MTP devices based on database state.
+ */
+ private void resume() {
+ synchronized (mDeviceListLock) {
+ mDatabase.getMapper().clearMapping();
+ final int[] ids = mDatabase.getDeviceIds();
+ for (final int id : ids) {
+ try {
+ openDevice(id);
+ } catch (IOException exception) {
+ mDatabase.removeDeviceRows(id);
+ }
+ }
+ }
+ }
+
+ private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
+ // TODO: Flush the device before closing (if not closed externally).
+ getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
+ mDeviceToolkits.remove(deviceId);
+ mMtpManager.closeDevice(deviceId);
+ if (!hasOpenedDevices()) {
+ mRootScanner.pause();
+ }
+ }
+
private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
- synchronized (mDeviceToolkits) {
+ synchronized (mDeviceListLock) {
final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
if (toolkit == null) {
throw new FileNotFoundException();
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
index 723dc14..9b3c20f 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
@@ -55,22 +55,20 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent == null) {
- // If intent is null, the service was restarted.
- // TODO: Recover opened devices here.
- return START_STICKY;
- }
- if (intent.getAction().equals(ACTION_OPEN_DEVICE)) {
- final UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE);
- try {
- final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
- provider.openDevice(device.getDeviceId());
- return START_STICKY;
- } catch (IOException error) {
- Log.e(MtpDocumentsProvider.TAG, error.getMessage());
+ // If intent is null, the service was restarted.
+ if (intent != null) {
+ if (intent.getAction().equals(ACTION_OPEN_DEVICE)) {
+ final UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE);
+ try {
+ final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
+ provider.openDevice(device.getDeviceId());
+ return START_STICKY;
+ } catch (IOException error) {
+ Log.e(MtpDocumentsProvider.TAG, error.getMessage());
+ }
+ } else {
+ Log.e(MtpDocumentsProvider.TAG, "Received unknown intent action.");
}
- } else {
- Log.w(MtpDocumentsProvider.TAG, "Received unknown intent action.");
}
stopSelfIfNeeded();
return Service.START_NOT_STICKY;
@@ -78,14 +76,8 @@
@Override
public void onDestroy() {
- final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
unregisterReceiver(mReceiver);
mReceiver = null;
- try {
- provider.closeAllDevices();
- } catch (InterruptedException e) {
- Log.e(MtpDocumentsProvider.TAG, e.getMessage());
- }
super.onDestroy();
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
index 714936d..cd52f31 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
@@ -32,6 +32,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.ArrayList;
/**
* The model wrapping android.mtp API.
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
index affaebd..16523bc 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
@@ -22,10 +22,8 @@
import android.util.Log;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index b0962dd..df2ab01 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -113,13 +113,14 @@
for (int deviceId : deviceIds) {
try {
final MtpRoot[] roots = mManager.getRoots(deviceId);
- mDatabase.startAddingRootDocuments(deviceId);
+ mDatabase.getMapper().startAddingRootDocuments(deviceId);
try {
- if (mDatabase.putRootDocuments(deviceId, mResources, roots)) {
+ if (mDatabase.getMapper().putRootDocuments(
+ deviceId, mResources, roots)) {
changed = true;
}
} finally {
- if (mDatabase.stopAddingRootDocuments(deviceId)) {
+ if (mDatabase.getMapper().stopAddingRootDocuments(deviceId)) {
changed = true;
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index a80eb51..f0b4343 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -40,11 +40,11 @@
@Override
public void setUp() {
mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, new TestResources(), new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, new TestResources(), new MtpRoot[] {
new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
mManager = new BlockableTestMtpManager(getContext());
mResolver = new TestContentResolver();
mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
@@ -57,7 +57,7 @@
public void testBasic() throws Exception {
final Uri uri = DocumentsContract.buildChildDocumentsUri(
- MtpDocumentsProvider.AUTHORITY, mParentIdentifier.toDocumentId());
+ MtpDocumentsProvider.AUTHORITY, mParentIdentifier.mDocumentId);
setUpDocument(mManager, 40);
mManager.blockDocument(0, 15);
mManager.blockDocument(0, 35);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 25dd1c8..67b0672 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -20,10 +20,15 @@
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import java.io.FileNotFoundException;
+
+import static com.android.mtp.MtpDatabase.strings;
+
@SmallTest
public class MtpDatabaseTest extends AndroidTestCase {
private final String[] COLUMN_NAMES = new String[] {
@@ -55,8 +60,8 @@
}
public void testPutRootDocuments() throws Exception {
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"")
@@ -75,7 +80,7 @@
assertEquals("displayName", "Device Storage", cursor.getString(5));
assertTrue("summary", cursor.isNull(6));
assertTrue("lastModified", cursor.isNull(7));
- assertTrue("icon", cursor.isNull(8));
+ assertEquals("icon", R.drawable.ic_root_mtp, cursor.getInt(8));
assertEquals("flag", 0, cursor.getInt(9));
assertEquals("size", 1000, cursor.getInt(10));
@@ -97,7 +102,7 @@
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
- Root.COLUMN_DOCUMENT_ID + "_",
+ Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES,
Root.COLUMN_CAPACITY_BYTES
});
@@ -106,7 +111,7 @@
cursor.moveToNext();
assertEquals(1, cursor.getInt(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- assertTrue(cursor.isNull(2));
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device Storage", cursor.getString(3));
assertTrue(cursor.isNull(4));
assertEquals(1, cursor.getInt(5));
@@ -116,7 +121,7 @@
cursor.moveToNext();
assertEquals(2, cursor.getInt(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- assertTrue(cursor.isNull(2));
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device Storage", cursor.getString(3));
assertTrue(cursor.isNull(4));
assertEquals(2, cursor.getInt(5));
@@ -126,7 +131,7 @@
cursor.moveToNext();
assertEquals(3, cursor.getInt(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- assertTrue(cursor.isNull(2));
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device /@#%&<>Storage", cursor.getString(3));
assertTrue(cursor.isNull(4));
assertEquals(3, cursor.getInt(5));
@@ -147,8 +152,8 @@
}
public void testPutChildDocuments() throws Exception {
- mDatabase.startAddingChildDocuments("parentId");
- mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
@@ -222,8 +227,8 @@
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
});
@@ -254,7 +259,7 @@
cursor.close();
}
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -282,8 +287,8 @@
cursor.close();
}
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
});
@@ -321,7 +326,7 @@
cursor.close();
}
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -356,13 +361,13 @@
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
- mDatabase.startAddingChildDocuments("parentId");
- mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
{
final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
@@ -386,8 +391,8 @@
cursor.close();
}
- mDatabase.startAddingChildDocuments("parentId");
- mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
});
@@ -404,7 +409,7 @@
cursor.close();
}
- mDatabase.stopAddingChildDocuments("parentId");
+ mDatabase.getMapper().stopAddingChildDocuments("parentId");
{
final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
@@ -433,12 +438,12 @@
Root.COLUMN_ROOT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.startAddingRootDocuments(1);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingRootDocuments(1);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
});
- mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
+ mDatabase.getMapper().putRootDocuments(1, resources, new MtpRoot[] {
new MtpRoot(1, 100, "Device", "Storage", 0, 0, "")
});
@@ -468,18 +473,18 @@
cursor.close();
}
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.startAddingRootDocuments(1);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingRootDocuments(1);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
});
- mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
+ mDatabase.getMapper().putRootDocuments(1, resources, new MtpRoot[] {
new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
});
- mDatabase.stopAddingRootDocuments(0);
- mDatabase.stopAddingRootDocuments(1);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(1);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -514,25 +519,25 @@
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
};
- mDatabase.startAddingChildDocuments("parentId1");
- mDatabase.startAddingChildDocuments("parentId2");
- mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId1");
+ mDatabase.getMapper().startAddingChildDocuments("parentId2");
+ mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+ mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingChildDocuments("parentId1");
- mDatabase.startAddingChildDocuments("parentId2");
- mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId1");
+ mDatabase.getMapper().startAddingChildDocuments("parentId2");
+ mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+ mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.stopAddingChildDocuments("parentId1");
+ mDatabase.getMapper().stopAddingChildDocuments("parentId1");
{
final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
@@ -563,23 +568,23 @@
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -611,18 +616,18 @@
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -653,17 +658,17 @@
public void testReplaceExistingRoots() {
// The client code should be able to replace existing rows with new information.
// Add one.
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
// Replace it.
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@@ -692,26 +697,155 @@
}
}
- public void _testFailToReplaceExisitingUnmappedRoots() {
+ public void testFailToReplaceExisitingUnmappedRoots() {
// The client code should not be able to replace rows before resolving 'unmapped' rows.
// Add one.
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
+ final Cursor oldCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID));
+ assertEquals(1, oldCursor.getCount());
+
// Add one.
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""),
});
// Add one more before resolving unmapped documents.
- try {
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
- });
- fail();
- } catch (Throwable e) {
- assertTrue(e instanceof Error);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""),
+ });
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+
+ // Because the roots shares the same name, the roots should have new IDs.
+ final Cursor newCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID));
+ assertEquals(2, newCursor.getCount());
+ oldCursor.moveToNext();
+ newCursor.moveToNext();
+ assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
+ newCursor.moveToNext();
+ assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
+
+ oldCursor.close();
+ newCursor.close();
+ }
+
+ public void testQueryDocument() {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ });
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+
+ final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME));
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("Device Storage A", cursor.getString(0));
+ cursor.close();
+ }
+
+ public void testGetParentId() throws FileNotFoundException {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ });
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+
+ mDatabase.getMapper().startAddingChildDocuments("1");
+ mDatabase.getMapper().putChildDocuments(
+ 0,
+ "1",
+ new MtpObjectInfo[] {
+ createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
+ });
+ mDatabase.getMapper().stopAddingChildDocuments("1");
+
+ assertEquals("1", mDatabase.getParentId("2"));
+ }
+
+ public void testDeleteDocument() {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ });
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+
+ mDatabase.getMapper().startAddingChildDocuments("1");
+ mDatabase.getMapper().putChildDocuments(
+ 0,
+ "1",
+ new MtpObjectInfo[] {
+ createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024),
+ });
+ mDatabase.getMapper().stopAddingChildDocuments("1");
+
+ mDatabase.getMapper().startAddingChildDocuments("2");
+ mDatabase.getMapper().putChildDocuments(
+ 0,
+ "2",
+ new MtpObjectInfo[] {
+ createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
+ });
+ mDatabase.getMapper().stopAddingChildDocuments("2");
+
+ mDatabase.deleteDocument("2");
+
+ {
+ // Do not query deleted documents.
+ final Cursor cursor =
+ mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ {
+ // Child document should be deleted also.
+ final Cursor cursor =
+ mDatabase.queryDocument("3", strings(Document.COLUMN_DOCUMENT_ID));
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+ }
+
+ public void testPutNewDocument() {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ });
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+
+ assertEquals(
+ "2",
+ mDatabase.putNewDocument(
+ 0, "1", createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024)));
+
+ {
+ final Cursor cursor =
+ mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("2", cursor.getString(0));
+ cursor.close();
+ }
+
+ // The new document should not be mapped with existing invalidated document.
+ mDatabase.getMapper().clearMapping();
+ mDatabase.getMapper().startAddingChildDocuments("1");
+ mDatabase.putNewDocument(
+ 0,
+ "1",
+ createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024));
+ mDatabase.getMapper().stopAddingChildDocuments("1");
+
+ {
+ final Cursor cursor =
+ mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("3", cursor.getString(0));
+ cursor.close();
}
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index cabb08d..dc6f79e 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -23,10 +23,14 @@
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsContract;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import static com.android.mtp.MtpDatabase.strings;
@SmallTest
public class MtpDocumentsProviderTest extends AndroidTestCase {
@@ -42,17 +46,16 @@
public void setUp() throws IOException {
mResolver = new TestContentResolver();
mMtpManager = new TestMtpManager(getContext());
- mProvider = new MtpDocumentsProvider();
- mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
- mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase);
}
@Override
public void tearDown() {
mProvider.shutdown();
+ MtpDatabase.deleteDatabase(getContext());
}
public void testOpenAndCloseDevice() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mMtpManager.setRoots(0, new MtpRoot[] {
new MtpRoot(
@@ -73,6 +76,7 @@
}
public void testOpenAndCloseErrorDevice() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
try {
mProvider.openDevice(1);
fail();
@@ -104,6 +108,7 @@
}
public void testQueryRoots() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mMtpManager.addValidDevice(1);
mMtpManager.setRoots(0, new MtpRoot[] {
@@ -135,10 +140,9 @@
cursor.moveToNext();
assertEquals("1", cursor.getString(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- // TODO: Add storage icon for MTP devices.
- assertTrue(cursor.isNull(2) /* icon */);
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device A Storage A", cursor.getString(3));
- assertEquals("0_1_0", cursor.getString(4));
+ assertEquals("1", cursor.getString(4));
assertEquals(1024, cursor.getInt(5));
}
@@ -151,15 +155,15 @@
cursor.moveToNext();
assertEquals("2", cursor.getString(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- // TODO: Add storage icon for MTP devices.
- assertTrue(cursor.isNull(2) /* icon */);
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device B Storage B", cursor.getString(3));
- assertEquals("1_1_0", cursor.getString(4));
+ assertEquals("2", cursor.getString(4));
assertEquals(2048, cursor.getInt(5));
}
}
public void testQueryRoots_error() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mMtpManager.addValidDevice(1);
// Not set roots for device 0 so that MtpManagerMock#getRoots throws IOException.
@@ -183,32 +187,41 @@
cursor.moveToNext();
assertEquals("1", cursor.getString(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- // TODO: Add storage icon for MTP devices.
- assertTrue(cursor.isNull(2) /* icon */);
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device B Storage B", cursor.getString(3));
- assertEquals("1_1_0", cursor.getString(4));
+ assertEquals("1", cursor.getString(4));
assertEquals(2048, cursor.getInt(5));
}
}
- public void testQueryDocument() throws IOException {
+ public void testQueryDocument() throws IOException, InterruptedException, TimeoutException {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
- .setObjectHandle(2)
- .setStorageId(1)
- .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
- .setName("image.jpg")
- .setDateModified(1422716400000L)
- .setCompressedSize(1024 * 1024 * 5)
- .setThumbCompressedSize(1024 * 50)
- .build());
- final Cursor cursor = mProvider.queryDocument("0_1_2", null);
+
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+ setupDocuments(
+ 0,
+ 0,
+ MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
+ "1",
+ new MtpObjectInfo[] {
+ new MtpObjectInfo.Builder()
+ .setObjectHandle(100)
+ .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
+ .setName("image.jpg")
+ .setDateModified(1422716400000L)
+ .setCompressedSize(1024 * 1024 * 5)
+ .setThumbCompressedSize(50 * 1024)
+ .build()
+ });
+
+ final Cursor cursor = mProvider.queryDocument("2", null);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals("0_1_2", cursor.getString(0));
+ assertEquals("2", cursor.getString(0));
assertEquals("image/jpeg", cursor.getString(1));
assertEquals("image.jpg", cursor.getString(2));
assertEquals(1422716400000L, cursor.getLong(3));
@@ -220,21 +233,33 @@
assertEquals(1024 * 1024 * 5, cursor.getInt(5));
}
- public void testQueryDocument_directory() throws IOException {
+ public void testQueryDocument_directory()
+ throws IOException, InterruptedException, TimeoutException {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
- .setObjectHandle(2)
- .setStorageId(1)
- .setFormat(MtpConstants.FORMAT_ASSOCIATION)
- .setName("directory")
- .setDateModified(1422716400000L)
- .build());
- final Cursor cursor = mProvider.queryDocument("0_1_2", null);
+
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+ setupDocuments(
+ 0,
+ 0,
+ MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
+ "1",
+ new MtpObjectInfo[] {
+ new MtpObjectInfo.Builder()
+ .setObjectHandle(2)
+ .setStorageId(1)
+ .setFormat(MtpConstants.FORMAT_ASSOCIATION)
+ .setName("directory")
+ .setDateModified(1422716400000L)
+ .build()
+ });
+
+ final Cursor cursor = mProvider.queryDocument("2", null);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals("0_1_2", cursor.getString(0));
+ assertEquals("2", cursor.getString(0));
assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
assertEquals("directory", cursor.getString(2));
assertEquals(1422716400000L, cursor.getLong(3));
@@ -246,10 +271,13 @@
assertEquals(0, cursor.getInt(5));
}
- public void testQueryDocument_forRoot() throws IOException {
+ public void testQueryDocument_forRoot()
+ throws IOException, InterruptedException, TimeoutException {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setRoots(0, new MtpRoot[] {
+
+ setupRoots(0, new MtpRoot[] {
new MtpRoot(
0 /* deviceId */,
1 /* storageId */,
@@ -259,11 +287,11 @@
4096 /* total space */,
"" /* no volume identifier */)
});
- final Cursor cursor = mProvider.queryDocument("0_1_0", null);
+ final Cursor cursor = mProvider.queryDocument("1", null);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals("0_1_0", cursor.getString(0));
+ assertEquals("1", cursor.getString(0));
assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
assertEquals("Device A Storage A", cursor.getString(2));
assertTrue(cursor.isNull(3));
@@ -272,44 +300,47 @@
}
public void testQueryChildDocuments() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, mResources, new MtpRoot[] {
- new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
- });
- mDatabase.stopAddingRootDocuments(0);
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+ setupDocuments(
+ 0,
+ 0,
+ MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
+ "1",
+ new MtpObjectInfo[] {
+ new MtpObjectInfo.Builder()
+ .setObjectHandle(100)
+ .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
+ .setName("image.jpg")
+ .setCompressedSize(1024 * 1024 * 5)
+ .setThumbCompressedSize(5 * 1024)
+ .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
+ .build()
+ });
- mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
- .setObjectHandle(1)
- .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
- .setName("image.jpg")
- .setCompressedSize(1024 * 1024 * 5)
- .setThumbCompressedSize(5 * 1024)
- .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
- .build());
-
- final Cursor cursor = mProvider.queryChildDocuments("0_0_0", null, null);
+ final Cursor cursor = mProvider.queryChildDocuments("1", null, null);
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToNext());
- assertEquals("0_0_1", cursor.getString(0));
+ assertEquals("2", cursor.getString(0));
assertEquals("image/jpeg", cursor.getString(1));
assertEquals("image.jpg", cursor.getString(2));
assertEquals(0, cursor.getLong(3));
assertEquals(DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4));
assertEquals(1024 * 1024 * 5, cursor.getInt(5));
- assertFalse(cursor.moveToNext());
+ cursor.close();
}
public void testQueryChildDocuments_cursorError() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
try {
- mProvider.queryChildDocuments("0_0_0", null, null);
+ mProvider.queryChildDocuments("1", null, null);
fail();
} catch (Throwable error) {
assertTrue(error instanceof FileNotFoundException);
@@ -317,44 +348,134 @@
}
public void testQueryChildDocuments_documentError() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
try {
- mProvider.queryChildDocuments("0_0_0", null, null);
+ mProvider.queryChildDocuments("1", null, null);
fail();
} catch (Throwable error) {
assertTrue(error instanceof FileNotFoundException);
}
}
- public void testDeleteDocument() throws IOException {
+ public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
- .setObjectHandle(1)
- .setParent(2)
- .build());
- mProvider.deleteDocument("0_0_1");
+ setupRoots(0, new MtpRoot[] {
+ new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+ });
+ setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
+ new MtpObjectInfo.Builder()
+ .setName("test.txt")
+ .setObjectHandle(1)
+ .setParent(-1)
+ .build()
+ });
+
+ mProvider.deleteDocument("2");
assertEquals(1, mResolver.getChangeCount(
DocumentsContract.buildChildDocumentsUri(
- MtpDocumentsProvider.AUTHORITY, "0_0_2")));
+ MtpDocumentsProvider.AUTHORITY, "1")));
}
- public void testDeleteDocument_error() throws IOException {
+ public void testDeleteDocument_error()
+ throws IOException, InterruptedException, TimeoutException {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
- .setObjectHandle(2)
- .build());
+ setupRoots(0, new MtpRoot[] {
+ new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+ });
+ setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
+ new MtpObjectInfo.Builder()
+ .setName("test.txt")
+ .setObjectHandle(1)
+ .setParent(-1)
+ .build()
+ });
try {
- mProvider.deleteDocument("0_0_1");
+ mProvider.deleteDocument("3");
fail();
} catch (Throwable e) {
assertTrue(e instanceof IOException);
}
assertEquals(0, mResolver.getChangeCount(
DocumentsContract.buildChildDocumentsUri(
- MtpDocumentsProvider.AUTHORITY, "0_0_2")));
+ MtpDocumentsProvider.AUTHORITY, "1")));
+ }
+
+ @MediumTest
+ public void testPauseAndResume() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
+ mMtpManager.addValidDevice(0);
+ mProvider.openDevice(0);
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")});
+
+ {
+ final Cursor cursor = mProvider.queryRoots(
+ strings(DocumentsContract.Root.COLUMN_ROOT_ID));
+ cursor.moveToNext();
+ assertEquals(1, cursor.getInt(0));
+ }
+
+ mProvider.shutdown();
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
+
+ {
+ // We can still fetch roots after relaunching the provider.
+ final Cursor cursor = mProvider.queryRoots(
+ strings(DocumentsContract.Root.COLUMN_ROOT_ID));
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals(1, cursor.getInt(0));
+ assertEquals(1, mMtpManager.getOpenedDeviceIds().length);
+ }
+ }
+
+ private void setupProvider(int flag) {
+ mDatabase = new MtpDatabase(getContext(), flag);
+ mProvider = new MtpDocumentsProvider();
+ mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase);
+ }
+
+ private String[] getStrings(Cursor cursor) {
+ try {
+ final String[] results = new String[cursor.getCount()];
+ for (int i = 0; cursor.moveToNext(); i++) {
+ results[i] = cursor.getString(0);
+ }
+ return results;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private String[] setupRoots(int deviceId, MtpRoot[] roots)
+ throws FileNotFoundException, InterruptedException, TimeoutException {
+ final int changeCount = mResolver.getChangeCount(ROOTS_URI);
+ mMtpManager.setRoots(deviceId, roots);
+ mResolver.waitForNotification(ROOTS_URI, changeCount + 1);
+ return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID)));
+ }
+
+ private String[] setupDocuments(
+ int deviceId,
+ int storageId,
+ int parentHandle,
+ String parentDocumentId,
+ MtpObjectInfo[] objects) throws FileNotFoundException {
+ final int[] handles = new int[objects.length];
+ int i = 0;
+ for (final MtpObjectInfo info : objects) {
+ handles[i] = info.getObjectHandle();
+ mMtpManager.setObjectInfo(deviceId, info);
+ }
+ mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles);
+ return getStrings(mProvider.queryChildDocuments(
+ parentDocumentId, strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null));
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
index 5547771..a045d06 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
@@ -16,26 +16,18 @@
package com.android.mtp;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.test.InstrumentationTestCase;
import java.io.IOException;
-import java.util.HashMap;
-import java.util.concurrent.CountDownLatch;
@RealDeviceTest
public class MtpManagerTest extends InstrumentationTestCase {
- private static final String ACTION_USB_PERMISSION =
- "com.android.mtp.USB_PERMISSION";
+
private static final int TIMEOUT_MS = 1000;
UsbManager mUsbManager;
MtpManager mManager;
@@ -45,10 +37,8 @@
@Override
public void setUp() throws Exception {
mUsbManager = getContext().getSystemService(UsbManager.class);
- mUsbDevice = findDevice();
mManager = new MtpManager(getContext());
- mManager.openDevice(mUsbDevice.getDeviceId());
- waitForStorages(mManager, mUsbDevice.getDeviceId());
+ mUsbDevice = TestUtil.setupMtpDevice(getInstrumentation(), mUsbManager, mManager);
}
@Override
@@ -56,6 +46,11 @@
mManager.closeDevice(mUsbDevice.getDeviceId());
}
+ @Override
+ public TestResultInstrumentation getInstrumentation() {
+ return (TestResultInstrumentation) super.getInstrumentation();
+ }
+
public void testCancelEvent() throws Exception {
final CancellationSignal signal = new CancellationSignal();
final Thread thread = new Thread() {
@@ -64,7 +59,7 @@
try {
mManager.readEvent(mUsbDevice.getDeviceId(), signal);
} catch (OperationCanceledException | IOException e) {
- show(e.getMessage());
+ getInstrumentation().show(e.getMessage());
}
}
};
@@ -74,72 +69,6 @@
thread.join(TIMEOUT_MS);
}
- private void requestPermission(UsbDevice device) throws InterruptedException {
- if (mUsbManager.hasPermission(device)) {
- return;
- }
- final CountDownLatch latch = new CountDownLatch(1);
- final BroadcastReceiver receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- latch.countDown();
- getInstrumentation().getTargetContext().unregisterReceiver(this);
- }
- };
- getInstrumentation().getTargetContext().registerReceiver(
- receiver, new IntentFilter(ACTION_USB_PERMISSION));
- mUsbManager.requestPermission(device, PendingIntent.getBroadcast(
- getInstrumentation().getTargetContext(),
- 0 /* requstCode */,
- new Intent(ACTION_USB_PERMISSION),
- 0 /* flags */));
- latch.await();
- assertTrue(mUsbManager.hasPermission(device));
- }
-
- private UsbDevice findDevice() throws InterruptedException {
- while (true) {
- final HashMap<String,UsbDevice> devices = mUsbManager.getDeviceList();
- if (devices.size() == 0) {
- show("Wait for devices.");
- Thread.sleep(1000);
- continue;
- }
- final UsbDevice device = devices.values().iterator().next();
- requestPermission(device);
- final UsbDeviceConnection connection = mUsbManager.openDevice(device);
- if (connection == null) {
- fail("Cannot open USB connection.");
- }
- for (int i = 0; i < device.getInterfaceCount(); i++) {
- // Since the test runs real environment, we need to call claim interface with
- // force = true to rob interfaces from other applications.
- connection.claimInterface(device.getInterface(i), true);
- connection.releaseInterface(device.getInterface(i));
- }
- connection.close();
- return device;
- }
- }
-
- private void waitForStorages(MtpManager manager, int deviceId) throws Exception {
- while (true) {
- if (manager.getRoots(deviceId).length == 0) {
- show("Wait for storages.");
- Thread.sleep(1000);
- continue;
- }
- return;
- }
- }
-
- private void show(String message) {
- if (!(getInstrumentation() instanceof TestResultInstrumentation)) {
- return;
- }
- ((TestResultInstrumentation) getInstrumentation()).show(message);
- }
-
private Context getContext() {
return getInstrumentation().getContext();
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
index 7c947f5..ccdea03 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
@@ -44,13 +44,13 @@
public void testReadDocument_basic() throws Exception {
mtpManager.setImportFileBytes(0, 1, HELLO_BYTES);
final ParcelFileDescriptor descriptor = mPipeManager.readDocument(
- mtpManager, new Identifier(0, 0, 1));
+ mtpManager, new Identifier(0, 0, 1, null));
assertDescriptor(descriptor, HELLO_BYTES);
}
public void testReadDocument_error() throws Exception {
final ParcelFileDescriptor descriptor =
- mPipeManager.readDocument(mtpManager, new Identifier(0, 0, 1));
+ mPipeManager.readDocument(mtpManager, new Identifier(0, 0, 1, null));
assertDescriptorError(descriptor);
}
@@ -62,7 +62,7 @@
// Upload testing bytes.
final ParcelFileDescriptor descriptor = mPipeManager.writeDocument(
- getContext(), mtpManager, new Identifier(0, 0, 1));
+ getContext(), mtpManager, new Identifier(0, 0, 1, null));
final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
new ParcelFileDescriptor.AutoCloseOutputStream(descriptor);
outputStream.write(HELLO_BYTES, 0, HELLO_BYTES.length);
@@ -94,13 +94,13 @@
public void testReadThumbnail_basic() throws Exception {
mtpManager.setThumbnail(0, 1, HELLO_BYTES);
final ParcelFileDescriptor descriptor = mPipeManager.readThumbnail(
- mtpManager, new Identifier(0, 0, 1));
+ mtpManager, new Identifier(0, 0, 1, null));
assertDescriptor(descriptor, HELLO_BYTES);
}
public void testReadThumbnail_error() throws Exception {
final ParcelFileDescriptor descriptor =
- mPipeManager.readThumbnail(mtpManager, new Identifier(0, 0, 1));
+ mPipeManager.readThumbnail(mtpManager, new Identifier(0, 0, 1, null));
assertDescriptorError(descriptor);
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
index 0fb0f34..3e64f9a2 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
@@ -1,3 +1,19 @@
+/*
+ * 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.mtp;
import android.os.Bundle;
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java
new file mode 100644
index 0000000..e6c12cb
--- /dev/null
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java
@@ -0,0 +1,134 @@
+/*
+ * 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.mtp;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbManager;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import junit.framework.Assert;
+
+/**
+ * Static utility methods for testing.
+ */
+class TestUtil {
+ private static final String ACTION_USB_PERMISSION =
+ "com.android.mtp.USB_PERMISSION";
+
+ private TestUtil() {}
+
+ /**
+ * Requests permission for a MTP device and returns the first MTP device that has at least one
+ * storage.
+ * @throws Exception
+ */
+ static UsbDevice setupMtpDevice(
+ TestResultInstrumentation instrumentation,
+ UsbManager usbManager,
+ MtpManager manager) throws Exception {
+ for (int i = 0; i < 2; i++) {
+ final UsbDevice device = findMtpDevice(instrumentation, usbManager);
+ manager.openDevice(device.getDeviceId());
+ try {
+ waitForStorages(instrumentation, manager, device.getDeviceId());
+ return device;
+ } catch (IOException exp) {
+ // When the MTP device is Android, and it changes the USB device type from
+ // "Charging" to "MTP", the device ID will be updated. We need to find a device
+ // again.
+ continue;
+ }
+ }
+ throw new IOException("Failed to obtain MTP devices");
+ }
+
+ private static UsbDevice findMtpDevice(
+ TestResultInstrumentation instrumentation,
+ UsbManager usbManager) throws InterruptedException {
+ while (true) {
+ final HashMap<String,UsbDevice> devices = usbManager.getDeviceList();
+ if (devices.size() == 0) {
+ instrumentation.show("Wait for devices.");
+ Thread.sleep(1000);
+ continue;
+ }
+ final UsbDevice device = devices.values().iterator().next();
+ requestPermission(instrumentation, usbManager, device);
+ final UsbDeviceConnection connection = usbManager.openDevice(device);
+ if (connection == null) {
+ Assert.fail("Cannot open USB connection.");
+ return null;
+ }
+ for (int i = 0; i < device.getInterfaceCount(); i++) {
+ // Since the test runs real environment, we need to call claim interface with
+ // force = true to rob interfaces from other applications.
+ connection.claimInterface(device.getInterface(i), true);
+ connection.releaseInterface(device.getInterface(i));
+ }
+ connection.close();
+ return device;
+ }
+ }
+
+ private static void requestPermission(
+ final TestResultInstrumentation instrumentation,
+ UsbManager usbManager,
+ UsbDevice device) throws InterruptedException {
+ if (usbManager.hasPermission(device)) {
+ return;
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ latch.countDown();
+ instrumentation.getTargetContext().unregisterReceiver(this);
+ }
+ };
+ instrumentation.getTargetContext().registerReceiver(
+ receiver, new IntentFilter(ACTION_USB_PERMISSION));
+ usbManager.requestPermission(device, PendingIntent.getBroadcast(
+ instrumentation.getTargetContext(),
+ 0 /* requstCode */,
+ new Intent(ACTION_USB_PERMISSION),
+ 0 /* flags */));
+ latch.await();
+ Assert.assertTrue(usbManager.hasPermission(device));
+ }
+
+ private static void waitForStorages(
+ TestResultInstrumentation instrumentation,
+ MtpManager manager,
+ int deviceId) throws Exception {
+ while (true) {
+ if (manager.getRoots(deviceId).length == 0) {
+ instrumentation.show("Wait for storages.");
+ Thread.sleep(1000);
+ continue;
+ }
+ return;
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index f006ccb..82fd512 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -16,6 +16,8 @@
package com.android.printspooler.model;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.Notification.InboxStyle;
@@ -129,68 +131,69 @@
mContext.getString(R.string.cancel), createCancelIntent(printJob)).build();
}
- private void createPrintingNotification(PrintJobInfo printJob) {
+ /**
+ * Create a notification for a print job.
+ *
+ * @param printJob the job the notification is for
+ * @param firstAction the first action shown in the notification
+ * @param secondAction the second action shown in the notification
+ */
+ private void createNotification(@NonNull PrintJobInfo printJob, @Nullable Action firstAction,
+ @Nullable Action secondAction) {
Notification.Builder builder = new Notification.Builder(mContext)
.setContentIntent(createContentIntent(printJob.getId()))
.setSmallIcon(computeNotificationIcon(printJob))
.setContentTitle(computeNotificationTitle(printJob))
- .addAction(createCancelAction(printJob))
- .setContentText(printJob.getPrinterName())
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setShowWhen(true)
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
+
+ if (firstAction != null) {
+ builder.addAction(firstAction);
+ }
+
+ if (secondAction != null) {
+ builder.addAction(secondAction);
+ }
+
+ if (printJob.getState() == PrintJobInfo.STATE_STARTED) {
+ float progress = printJob.getProgress();
+ if (progress >= 0) {
+ builder.setProgress(Integer.MAX_VALUE, (int)(Integer.MAX_VALUE * progress),
+ false);
+ }
+ }
+
+ CharSequence status = printJob.getStatus();
+ if (status != null) {
+ builder.setContentText(status);
+ } else {
+ builder.setContentText(printJob.getPrinterName());
+ }
+
mNotificationManager.notify(0, builder.build());
}
+ private void createPrintingNotification(PrintJobInfo printJob) {
+ createNotification(printJob, createCancelAction(printJob), null);
+ }
+
private void createFailedNotification(PrintJobInfo printJob) {
Action.Builder restartActionBuilder = new Action.Builder(
Icon.createWithResource(mContext, R.drawable.ic_restart),
mContext.getString(R.string.restart), createRestartIntent(printJob.getId()));
- Notification.Builder builder = new Notification.Builder(mContext)
- .setContentIntent(createContentIntent(printJob.getId()))
- .setSmallIcon(computeNotificationIcon(printJob))
- .setContentTitle(computeNotificationTitle(printJob))
- .addAction(createCancelAction(printJob))
- .addAction(restartActionBuilder.build())
- .setContentText(printJob.getPrinterName())
- .setWhen(System.currentTimeMillis())
- .setOngoing(true)
- .setShowWhen(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- mNotificationManager.notify(0, builder.build());
+ createNotification(printJob, createCancelAction(printJob), restartActionBuilder.build());
}
private void createBlockedNotification(PrintJobInfo printJob) {
- Notification.Builder builder = new Notification.Builder(mContext)
- .setContentIntent(createContentIntent(printJob.getId()))
- .setSmallIcon(computeNotificationIcon(printJob))
- .setContentTitle(computeNotificationTitle(printJob))
- .addAction(createCancelAction(printJob))
- .setContentText(printJob.getPrinterName())
- .setWhen(System.currentTimeMillis())
- .setOngoing(true)
- .setShowWhen(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- mNotificationManager.notify(0, builder.build());
+ createNotification(printJob, createCancelAction(printJob), null);
}
private void createCancellingNotification(PrintJobInfo printJob) {
- Notification.Builder builder = new Notification.Builder(mContext)
- .setContentIntent(createContentIntent(printJob.getId()))
- .setSmallIcon(computeNotificationIcon(printJob))
- .setContentTitle(computeNotificationTitle(printJob))
- .setContentText(printJob.getPrinterName())
- .setWhen(System.currentTimeMillis())
- .setOngoing(true)
- .setShowWhen(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- mNotificationManager.notify(0, builder.build());
+ createNotification(printJob, null, null);
}
private void createStackedNotification(List<PrintJobInfo> printJobs) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
index 7adcfec..b92a389 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -16,6 +16,9 @@
package com.android.printspooler.model;
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -497,7 +500,7 @@
success = true;
printJob.setState(state);
- printJob.setStateReason(error);
+ printJob.setStatus(error);
printJob.setCancelling(false);
if (DEBUG_PRINT_JOB_LIFECYCLE) {
@@ -547,6 +550,35 @@
return success;
}
+ /**
+ * Set the progress for a print job.
+ *
+ * @param printJobId ID of the print job to update
+ * @param progress the new progress
+ */
+ public void setProgress(@NonNull PrintJobId printJobId,
+ @FloatRange(from=0.0, to=1.0) float progress) {
+ synchronized (mLock) {
+ getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setProgress(progress);
+
+ mNotificationController.onUpdateNotifications(mPrintJobs);
+ }
+ }
+
+ /**
+ * Set the status for a print job.
+ *
+ * @param printJobId ID of the print job to update
+ * @param status the new status
+ */
+ public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
+ synchronized (mLock) {
+ getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status);
+
+ mNotificationController.onUpdateNotifications(mPrintJobs);
+ }
+ }
+
public boolean hasActivePrintJobsLocked() {
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
@@ -693,6 +725,8 @@
private static final String ATTR_COPIES = "copies";
private static final String ATTR_PRINTER_NAME = "printerName";
private static final String ATTR_STATE_REASON = "stateReason";
+ private static final String ATTR_STATUS = "status";
+ private static final String ATTR_PROGRESS = "progress";
private static final String ATTR_CANCELLING = "cancelling";
private static final String TAG_ADVANCED_OPTIONS = "advancedOptions";
@@ -801,13 +835,19 @@
if (!TextUtils.isEmpty(printerName)) {
serializer.attribute(null, ATTR_PRINTER_NAME, printerName);
}
- String stateReason = printJob.getStateReason();
- if (!TextUtils.isEmpty(stateReason)) {
- serializer.attribute(null, ATTR_STATE_REASON, stateReason);
- }
serializer.attribute(null, ATTR_CANCELLING, String.valueOf(
printJob.isCancelling()));
+ float progress = printJob.getProgress();
+ if (progress != Float.NaN) {
+ serializer.attribute(null, ATTR_PROGRESS, String.valueOf(progress));
+ }
+
+ CharSequence status = printJob.getStatus();
+ if (!TextUtils.isEmpty(status)) {
+ serializer.attribute(null, ATTR_STATUS, status.toString());
+ }
+
PrinterId printerId = printJob.getPrinterId();
if (printerId != null) {
serializer.startTag(null, TAG_PRINTER_ID);
@@ -1025,8 +1065,25 @@
printJob.setCopies(Integer.parseInt(copies));
String printerName = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
printJob.setPrinterName(printerName);
+
+ String progressString = parser.getAttributeValue(null, ATTR_PROGRESS);
+ if (progressString != null) {
+ float progress = Float.parseFloat(progressString);
+
+ if (progress != Float.NaN) {
+ printJob.setProgress(progress);
+ }
+ }
+
+ CharSequence status = parser.getAttributeValue(null, ATTR_STATUS);
+ printJob.setStatus(status);
+
+ // stateReason is deprecated, but might be used by old print jobs
String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON);
- printJob.setStateReason(stateReason);
+ if (stateReason != null) {
+ printJob.setStatus(stateReason);
+ }
+
String cancelling = parser.getAttributeValue(null, ATTR_CANCELLING);
printJob.setCancelling(!TextUtils.isEmpty(cancelling)
? Boolean.parseBoolean(cancelling) : false);
@@ -1323,6 +1380,18 @@
.removeApprovedService(serviceToRemove);
}
+ @Override
+ public void setProgress(@NonNull PrintJobId printJobId,
+ @FloatRange(from=0.0, to=1.0) float progress) throws RemoteException {
+ PrintSpoolerService.this.setProgress(printJobId, progress);
+ }
+
+ @Override
+ public void setStatus(@NonNull PrintJobId printJobId,
+ @Nullable CharSequence status) throws RemoteException {
+ PrintSpoolerService.this.setStatus(printJobId, status);
+ }
+
public PrintSpoolerService getService() {
return PrintSpoolerService.this;
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 67e170f..6521565 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -556,8 +556,7 @@
// Make sure the IME is not on the way of preview as
// the user may have used it to type copies or range.
- InputMethodManager imm = (InputMethodManager) getSystemService(
- Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
imm.hideSoftInputFromWindow(mDestinationSpinner.getWindowToken(), 0);
}
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 8fa5abf..98a171e 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -72,7 +72,7 @@
<string name="bluetooth_pairing_accept" msgid="6163520056536604875">"Sparuj"</string>
<string name="bluetooth_pairing_accept_all_caps" msgid="6061699265220789149">"SPARUJ"</string>
<string name="bluetooth_pairing_decline" msgid="4185420413578948140">"Anuluj"</string>
- <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"Parowanie spowoduje przyznanie dostępu do historii połączeń i Twoich kontaktów w trakcie połączenia."</string>
+ <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"Sparowanie powoduje przyznanie dostępu do historii połączeń i Twoich kontaktów w trakcie połączenia."</string>
<string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"Nie można sparować z urządzeniem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"Nie można sparować z urządzeniem <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ze względu na błędny kod PIN lub klucz."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"Nie można skomunikować się z urządzeniem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 029962e..c3c287c 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -38,7 +38,7 @@
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"กำลังตัดการเชื่อมต่อ..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"กำลังเชื่อมต่อ…"</string>
<string name="bluetooth_connected" msgid="6038755206916626419">"เชื่อมต่อแล้ว"</string>
- <string name="bluetooth_pairing" msgid="1426882272690346242">"กำลังกำหนดค่าอุปกรณ์ให้ตรงกัน..."</string>
+ <string name="bluetooth_pairing" msgid="1426882272690346242">"กำลังจับคู่อุปกรณ์..."</string>
<string name="bluetooth_connected_no_headset" msgid="2866994875046035609">"เชื่อมต่อแล้ว (ยกเว้นเสียงโทรศัพท์)"</string>
<string name="bluetooth_connected_no_a2dp" msgid="4576188601581440337">"เชื่อมต่อแล้ว (ยกเว้นเสียงสื่อ)"</string>
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"เชื่อมต่อแล้ว (ไม่มีการเข้าถึงข้อความ)"</string>
@@ -69,14 +69,14 @@
<string name="bluetooth_headset_profile_summary_use_for" msgid="8705753622443862627">"ใช้สำหรับระบบเสียงของโทรศัพท์"</string>
<string name="bluetooth_opp_profile_summary_use_for" msgid="1255674547144769756">"ใช้สำหรับการโอนไฟล์"</string>
<string name="bluetooth_hid_profile_summary_use_for" msgid="232727040453645139">"ใช้สำหรับการป้อนข้อมูล"</string>
- <string name="bluetooth_pairing_accept" msgid="6163520056536604875">"กำหนดค่าอุปกรณ์ให้ตรงกัน"</string>
+ <string name="bluetooth_pairing_accept" msgid="6163520056536604875">"จับคู่อุปกรณ์"</string>
<string name="bluetooth_pairing_accept_all_caps" msgid="6061699265220789149">"จับคู่อุปกรณ์"</string>
<string name="bluetooth_pairing_decline" msgid="4185420413578948140">"ยกเลิก"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"การจับคู่อุปกรณ์จะให้สิทธิ์การเข้าถึงที่อยู่ติดต่อและประวัติการโทรเมื่อเชื่อมต่อแล้ว"</string>
<string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"ไม่สามารถจับคู่กับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
<string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"ไม่สามารถจับคู่กับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ได้เพราะ PIN หรือรหัสผ่านไม่ถูกต้อง"</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"ไม่สามารถเชื่อมต่อกับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"การกำหนดค่าอุปกรณ์ให้ตรงกันถูกปฏิเสธโดย <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+ <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ปฏิเสธการจับคู่อุปกรณ์"</string>
<string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi ปิดอยู่"</string>
<string name="accessibility_no_wifi" msgid="8834610636137374508">"ไม่ได้เชื่อมต่อ Wi-Fi"</string>
<string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"สัญญาณ Wi-Fi 1 ขีด"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 6102bef..00dadb8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -143,6 +143,9 @@
public boolean openTile(DashboardTile tile) {
closeDrawer();
+ if (tile == null) {
+ return false;
+ }
int numUserHandles = tile.userHandle.size();
if (numUserHandles > 1) {
ProfileSelectDialog.show(getFragmentManager(), tile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index e28b2e5..2b1c66d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -19,8 +19,12 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkRequest;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
@@ -62,6 +66,9 @@
private final Context mContext;
private final WifiManager mWifiManager;
private final IntentFilter mFilter;
+ private final ConnectivityManager mConnectivityManager;
+ private final NetworkRequest mNetworkRequest;
+ private WifiTrackerNetworkCallback mNetworkCallback;
private final AtomicBoolean mConnected = new AtomicBoolean(false);
private final WifiListener mListener;
@@ -104,13 +111,15 @@
public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
boolean includeSaved, boolean includeScans, boolean includePasspoints) {
this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
- (WifiManager) context.getSystemService(Context.WIFI_SERVICE), Looper.myLooper());
+ context.getSystemService(WifiManager.class),
+ context.getSystemService(ConnectivityManager.class), Looper.myLooper());
}
@VisibleForTesting
WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
boolean includeSaved, boolean includeScans, boolean includePasspoints,
- WifiManager wifiManager, Looper currentLooper) {
+ WifiManager wifiManager, ConnectivityManager connectivityManager,
+ Looper currentLooper) {
if (!includeSaved && !includeScans) {
throw new IllegalArgumentException("Must include either saved or scans");
}
@@ -127,6 +136,7 @@
mIncludeScans = includeScans;
mIncludePasspoints = includePasspoints;
mListener = wifiListener;
+ mConnectivityManager = connectivityManager;
// check if verbose logging has been turned on or off
sVerboseLogging = mWifiManager.getVerboseLoggingLevel();
@@ -139,7 +149,11 @@
mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+
+ mNetworkRequest = new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
}
/**
@@ -192,6 +206,9 @@
resumeScanning();
if (!mRegistered) {
mContext.registerReceiver(mReceiver, mFilter);
+ // NetworkCallback objects cannot be reused. http://b/20701525 .
+ mNetworkCallback = new WifiTrackerNetworkCallback();
+ mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
mRegistered = true;
}
}
@@ -207,6 +224,7 @@
mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_NETWORK_INFO);
mContext.unregisterReceiver(mReceiver);
+ mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
mRegistered = false;
}
pauseScanning();
@@ -461,12 +479,12 @@
mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING);
}
- mLastInfo = mWifiManager.getConnectionInfo();
if (networkInfo != null) {
mLastNetworkInfo = networkInfo;
}
WifiConfiguration connectionConfig = null;
+ mLastInfo = mWifiManager.getConnectionInfo();
if (mLastInfo != null) {
connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
}
@@ -532,12 +550,21 @@
mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
.sendToTarget();
- } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
- mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
}
}
};
+ private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+ if (network.equals(mWifiManager.getCurrentNetwork())) {
+ // We don't send a NetworkInfo object along with this message, because even if we
+ // fetch one from ConnectivityManager, it might be older than the most recent
+ // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
+ mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
+ }
+ }
+ }
+
private final class MainHandler extends Handler {
private static final int MSG_CONNECTED_CHANGED = 0;
private static final int MSG_WIFI_STATE_CHANGED = 1;
diff --git a/packages/SettingsProvider/res/values-it/strings.xml b/packages/SettingsProvider/res/values-it/strings.xml
index 40735cc..ba1431d 100644
--- a/packages/SettingsProvider/res/values-it/strings.xml
+++ b/packages/SettingsProvider/res/values-it/strings.xml
@@ -19,5 +19,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_label" msgid="4567566098528588863">"Archiviazione impostazioni"</string>
+ <string name="app_label" msgid="4567566098528588863">"Memoria impostazioni"</string>
</resources>
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bf3982d..25346ac 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -144,6 +144,7 @@
android:name=".BugreportReceiver"
android:permission="android.permission.DUMP">
<intent-filter>
+ <action android:name="android.intent.action.BUGREPORT_STARTED" />
<action android:name="android.intent.action.BUGREPORT_FINISHED" />
</intent-filter>
</receiver>
diff --git a/packages/Shell/res/values-af/strings.xml b/packages/Shell/res/values-af/strings.xml
index d1998bd..8430bf0 100644
--- a/packages/Shell/res/values-af/strings.xml
+++ b/packages/Shell/res/values-af/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Tuisskerm"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Besig met foutverslag"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Foutverslag vasgevang"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Swiep na links om jou foutverslag te deel"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Raak om jou foutverslag te deel"</string>
diff --git a/packages/Shell/res/values-am/strings.xml b/packages/Shell/res/values-am/strings.xml
index 6f905a9..9400f37 100644
--- a/packages/Shell/res/values-am/strings.xml
+++ b/packages/Shell/res/values-am/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ቀፎ"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"የሳንካ ሪፓርት በሂደት ላይ"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"የሳንካ ሪፖርት ተይዟል"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"የሳንካ ሪፖርትዎን ለማጋራት ወደ ግራ ያንሸራትቱ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"የሳንካ ሪፖርትዎን ለማጋራት ይንክኩ"</string>
diff --git a/packages/Shell/res/values-ar/strings.xml b/packages/Shell/res/values-ar/strings.xml
index 76100b5..2161a76 100644
--- a/packages/Shell/res/values-ar/strings.xml
+++ b/packages/Shell/res/values-ar/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"تقرير الخطأ قيد التقدم"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"تم الحصول على تقرير الأخطاء"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"مرر بسرعة لليمين لمشاركة تقرير الخطأ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"المس لمشاركة تقرير الأخطاء"</string>
diff --git a/packages/Shell/res/values-az-rAZ/strings.xml b/packages/Shell/res/values-az-rAZ/strings.xml
index d8176f5..76ee4bf 100644
--- a/packages/Shell/res/values-az-rAZ/strings.xml
+++ b/packages/Shell/res/values-az-rAZ/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Baq hesabatı davam edir"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Baq raport alındı"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Baq raportunu paylaşmaq üçün sola sürüşdürün"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Xətanı şikayətini paylaşmaq üçün toxunun"</string>
diff --git a/packages/Shell/res/values-bg/strings.xml b/packages/Shell/res/values-bg/strings.xml
index fc2dad0..bbfae69 100644
--- a/packages/Shell/res/values-bg/strings.xml
+++ b/packages/Shell/res/values-bg/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Команден ред"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Отчетът за програмни грешки е записан"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Прекарайте пръст наляво, за да споделите сигнала си за програмна грешка"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Докоснете, за да споделите отчета си за програмни грешки"</string>
diff --git a/packages/Shell/res/values-bn-rBD/strings.xml b/packages/Shell/res/values-bn-rBD/strings.xml
index 5aa3e9d..5ca5835 100644
--- a/packages/Shell/res/values-bn-rBD/strings.xml
+++ b/packages/Shell/res/values-bn-rBD/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"শেল"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ত্রুটির প্রতিবেদন করা হচ্ছে"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"ত্রুটির প্রতিবেদন নেওয়া হয়েছে"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"আপনার বাগ রিপোর্ট শেয়ার করতে বামে সোয়াইপ করুন"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"আপনার ত্রুটির প্রতিবেদন ভাগ করতে স্পর্শ করুন"</string>
diff --git a/packages/Shell/res/values-ca/strings.xml b/packages/Shell/res/values-ca/strings.xml
index bef1a67..1e6ec53 100644
--- a/packages/Shell/res/values-ca/strings.xml
+++ b/packages/Shell/res/values-ca/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Protecció"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Informe d\'errors en curs"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"S\'ha registrat l\'informe d\'error"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Llisca cap a l\'esquerra per compartir l\'informe d\'errors."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toca aquí per compartir el teu informe d\'error."</string>
diff --git a/packages/Shell/res/values-cs/strings.xml b/packages/Shell/res/values-cs/strings.xml
index 46c8f4e..336c21f 100644
--- a/packages/Shell/res/values-cs/strings.xml
+++ b/packages/Shell/res/values-cs/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Probíhá zpracování zprávy o chybě"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Bylo vytvořeno chybové hlášení"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Chcete-li hlášení chyby sdílet, přejeďte doleva."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Chybové hlášení můžete sdílet klepnutím."</string>
diff --git a/packages/Shell/res/values-da/strings.xml b/packages/Shell/res/values-da/strings.xml
index 7a87b9b..6f65894 100644
--- a/packages/Shell/res/values-da/strings.xml
+++ b/packages/Shell/res/values-da/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Fejlrapporten er under udførelse"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Fejlrapporten er registreret"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Stryg til venstre for at dele din fejlrapport"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Tryk for at dele din fejlrapport"</string>
diff --git a/packages/Shell/res/values-de/strings.xml b/packages/Shell/res/values-de/strings.xml
index 7a25c4d..03c6166 100644
--- a/packages/Shell/res/values-de/strings.xml
+++ b/packages/Shell/res/values-de/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Fehlerbericht in Bearbeitung"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Fehlerbericht erfasst"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Wischen Sie nach links, um Ihren Fehlerbericht zu teilen."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Tippen, um Fehlerbericht zu teilen"</string>
diff --git a/packages/Shell/res/values-el/strings.xml b/packages/Shell/res/values-el/strings.xml
index 02e37f7..ceec189 100644
--- a/packages/Shell/res/values-el/strings.xml
+++ b/packages/Shell/res/values-el/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Κέλυφος"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Αναφορά σφάλματος σε εξέλιξη"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Η λήψη της αναφοράς ήταν επιτυχής"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Σύρετε προς τα αριστερά για κοινή χρήση της αναφοράς σφαλμάτων"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Αγγίξτε για να μοιραστείτε τη αναφορά σφαλμάτων"</string>
diff --git a/packages/Shell/res/values-en-rAU/strings.xml b/packages/Shell/res/values-en-rAU/strings.xml
index 1b55115..99c9f4f 100644
--- a/packages/Shell/res/values-en-rAU/strings.xml
+++ b/packages/Shell/res/values-en-rAU/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Bug report in progress"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Bug report captured"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Swipe left to share your bug report"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Touch to share your bug report"</string>
diff --git a/packages/Shell/res/values-en-rGB/strings.xml b/packages/Shell/res/values-en-rGB/strings.xml
index 1b55115..99c9f4f 100644
--- a/packages/Shell/res/values-en-rGB/strings.xml
+++ b/packages/Shell/res/values-en-rGB/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Bug report in progress"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Bug report captured"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Swipe left to share your bug report"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Touch to share your bug report"</string>
diff --git a/packages/Shell/res/values-en-rIN/strings.xml b/packages/Shell/res/values-en-rIN/strings.xml
index 1b55115..99c9f4f 100644
--- a/packages/Shell/res/values-en-rIN/strings.xml
+++ b/packages/Shell/res/values-en-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Bug report in progress"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Bug report captured"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Swipe left to share your bug report"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Touch to share your bug report"</string>
diff --git a/packages/Shell/res/values-es-rUS/strings.xml b/packages/Shell/res/values-es-rUS/strings.xml
index 1937349..263459e 100644
--- a/packages/Shell/res/values-es-rUS/strings.xml
+++ b/packages/Shell/res/values-es-rUS/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Informe de errores en progreso"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Informe de errores capturado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Desliza el dedo hacia la izquierda para compartir el informe de errores."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toca para compartir tu informe de errores."</string>
diff --git a/packages/Shell/res/values-es/strings.xml b/packages/Shell/res/values-es/strings.xml
index 002bea9..3b37d40 100644
--- a/packages/Shell/res/values-es/strings.xml
+++ b/packages/Shell/res/values-es/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Informe de errores en curso"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Informe de error registrado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Desliza el dedo hacia la izquierda para compartir el informe de error"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toca para compartir tu informe de error"</string>
diff --git a/packages/Shell/res/values-et-rEE/strings.xml b/packages/Shell/res/values-et-rEE/strings.xml
index a16875c..652de64 100644
--- a/packages/Shell/res/values-et-rEE/strings.xml
+++ b/packages/Shell/res/values-et-rEE/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Kest"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Veaaruande töötlemine on pooleli"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Veaaruanne jäädvustati"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Veaaruande jagamiseks pühkige vasakule"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Veaaruande jagamiseks puudutage"</string>
diff --git a/packages/Shell/res/values-eu-rES/strings.xml b/packages/Shell/res/values-eu-rES/strings.xml
index e7f1766..3234786 100644
--- a/packages/Shell/res/values-eu-rES/strings.xml
+++ b/packages/Shell/res/values-eu-rES/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell-interfazea"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Abian da akatsen txostena egiteko prozesua"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Akatsen txostena jaso da"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Programa-akatsen txostena partekatzeko, pasatu hatza ezkerrera"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Akatsen txostena partekatzeko, ukitu"</string>
diff --git a/packages/Shell/res/values-fa/strings.xml b/packages/Shell/res/values-fa/strings.xml
index 9138b28..ff58c25 100644
--- a/packages/Shell/res/values-fa/strings.xml
+++ b/packages/Shell/res/values-fa/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"گزارش اشکال در حال انجام است"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"گزارش اشکال دریافت شد"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"برای اشتراکگذاری گزارش اشکال، به تندی آن را به چپ بکشید"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"جهت اشتراکگذاری گزارش اشکال خود لمس کنید"</string>
diff --git a/packages/Shell/res/values-fi/strings.xml b/packages/Shell/res/values-fi/strings.xml
index 079433d..df6851c 100644
--- a/packages/Shell/res/values-fi/strings.xml
+++ b/packages/Shell/res/values-fi/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Komentotulkki"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Virheraportti käynnissä"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Virheraportti tallennettu"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Jaa virheraportti pyyhkäisemällä vasemmalle"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Jaa virheraportti koskettamalla tätä"</string>
diff --git a/packages/Shell/res/values-fr-rCA/strings.xml b/packages/Shell/res/values-fr-rCA/strings.xml
index 4ead085..5f58164 100644
--- a/packages/Shell/res/values-fr-rCA/strings.xml
+++ b/packages/Shell/res/values-fr-rCA/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Création du rapport de bogue en cours..."</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Rapport de bogue enregistré"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Faites glisser le doigt vers la gauche pour partager votre rapport de bogue."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Appuyer ici pour partager votre rapport de bogue"</string>
diff --git a/packages/Shell/res/values-fr/strings.xml b/packages/Shell/res/values-fr/strings.xml
index 14c1d3b..9562c6cf 100644
--- a/packages/Shell/res/values-fr/strings.xml
+++ b/packages/Shell/res/values-fr/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Rapport de bug en cours"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Rapport de bug enregistré"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Faites glisser le doigt vers la gauche pour partager votre rapport d\'erreur."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Appuyez ici pour partager le rapport de bug"</string>
diff --git a/packages/Shell/res/values-gl-rES/strings.xml b/packages/Shell/res/values-gl-rES/strings.xml
index 61b0335..1f8da5d 100644
--- a/packages/Shell/res/values-gl-rES/strings.xml
+++ b/packages/Shell/res/values-gl-rES/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Informe de erro en curso"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Informe de erros rexistrado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Pasa o dedo á esquerda para compartir o teu informe de erros"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toca aquí para compartir o teu informe de erros"</string>
diff --git a/packages/Shell/res/values-gu-rIN/strings.xml b/packages/Shell/res/values-gu-rIN/strings.xml
index 746ac45..7a94dd7 100644
--- a/packages/Shell/res/values-gu-rIN/strings.xml
+++ b/packages/Shell/res/values-gu-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"શેલ"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"બગ રિપોર્ટ ચાલુ છે"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"બગ રિપોર્ટ કેપ્ચર કરી"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"તમારી બગ રિપોર્ટ શેર કરવા માટે ડાબે સ્વાઇપ કરો"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"તમારી બગ રિપોર્ટ શેર કરવા માટે ટચ કરો"</string>
diff --git a/packages/Shell/res/values-hi/strings.xml b/packages/Shell/res/values-hi/strings.xml
index 97db607..273b4841 100644
--- a/packages/Shell/res/values-hi/strings.xml
+++ b/packages/Shell/res/values-hi/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"शेल"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"बग रिपोर्ट प्रगति में है"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"बग रिपोर्ट कैप्चर कर ली गई"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"अपनी बग रिपोर्ट साझा करने के लिए बाएं स्वाइप करें"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"अपनी बग रिपोर्ट साझा करने के लिए स्पर्श करें"</string>
diff --git a/packages/Shell/res/values-hr/strings.xml b/packages/Shell/res/values-hr/strings.xml
index b1fad84..3836edb 100644
--- a/packages/Shell/res/values-hr/strings.xml
+++ b/packages/Shell/res/values-hr/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Ljuska"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Izvješće o programskoj pogrešci u tijeku"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Prijava programske pogreške snimljena je"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Prijeđite prstom ulijevo da biste poslali izvješće o programskim pogreškama"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Dodirnite za dijeljenje prijave programske pogreške"</string>
diff --git a/packages/Shell/res/values-hu/strings.xml b/packages/Shell/res/values-hu/strings.xml
index 7dae6c1..9191e02 100644
--- a/packages/Shell/res/values-hu/strings.xml
+++ b/packages/Shell/res/values-hu/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Héj"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Hibajelentés folyamatban"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Programhiba-jelentés rögzítve"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Húzza ujját balra a hibajelentés megosztásához"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Érintse meg a programhiba-jelentés megosztásához"</string>
diff --git a/packages/Shell/res/values-hy-rAM/strings.xml b/packages/Shell/res/values-hy-rAM/strings.xml
index 149b677..bfa9b04 100644
--- a/packages/Shell/res/values-hy-rAM/strings.xml
+++ b/packages/Shell/res/values-hy-rAM/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Խեցի"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Վրիպակի զեկույց է ստացվել"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Սահեցրեք ձախ՝ սխալի հաշվետվությունը համօգտագործելու համար"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Հպեք` ձեր վրիպակի մասին զեկույցը տարածելու համար"</string>
diff --git a/packages/Shell/res/values-in/strings.xml b/packages/Shell/res/values-in/strings.xml
index 534badc..9a13d8b 100644
--- a/packages/Shell/res/values-in/strings.xml
+++ b/packages/Shell/res/values-in/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Kerangka"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Laporan bug sedang berlangsung"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Laporan bug tercatat"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Gesek ke kiri untuk membagikan laporan bug Anda"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Sentuh untuk membagikan laporan bug Anda"</string>
diff --git a/packages/Shell/res/values-is-rIS/strings.xml b/packages/Shell/res/values-is-rIS/strings.xml
index ce742f6..4304c8e 100644
--- a/packages/Shell/res/values-is-rIS/strings.xml
+++ b/packages/Shell/res/values-is-rIS/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Skipanalína"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Verið er að útbúa villutilkynningu"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Villutilkynning útbúin"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Strjúktu til vinstri til að deila villuskýrslunni"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Snertu til að deila villutilkynningunni"</string>
diff --git a/packages/Shell/res/values-it/strings.xml b/packages/Shell/res/values-it/strings.xml
index 38e360f..63d39d0 100644
--- a/packages/Shell/res/values-it/strings.xml
+++ b/packages/Shell/res/values-it/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Segnalazione di bug in corso"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Segnalazione di bug acquisita"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Scorri verso sinistra per condividere il rapporto sui bug"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Tocca per condividere la segnalazione di bug"</string>
diff --git a/packages/Shell/res/values-iw/strings.xml b/packages/Shell/res/values-iw/strings.xml
index ab9aecf..94a167f 100644
--- a/packages/Shell/res/values-iw/strings.xml
+++ b/packages/Shell/res/values-iw/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"מעטפת"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"הדוח על הבאג מתבצע כעת"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"דוח הבאגים צולם"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"החלק שמאלה כדי לשתף את דוח הבאגים"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"גע כדי לשתף את דוח הבאגים שלך"</string>
diff --git a/packages/Shell/res/values-ja/strings.xml b/packages/Shell/res/values-ja/strings.xml
index 619ee4f..77adb37 100644
--- a/packages/Shell/res/values-ja/strings.xml
+++ b/packages/Shell/res/values-ja/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"シェル"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"バグレポートを処理しています"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"バグレポートが記録されました"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"バグレポートを共有するには左にスワイプ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"バグレポートを共有するにはタップします"</string>
diff --git a/packages/Shell/res/values-ka-rGE/strings.xml b/packages/Shell/res/values-ka-rGE/strings.xml
index f98045f..2521d3e 100644
--- a/packages/Shell/res/values-ka-rGE/strings.xml
+++ b/packages/Shell/res/values-ka-rGE/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"გარეკანი"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"ანგარიში ხარვეზების შესახებ შექმნილია"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"გაასრიალეთ მარცხნივ თქვენი ხარვეზის შეტყობინების გასაზიარებლად"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"შეეხეთ თქვენი ხარვეზების ანგარიშის გასაზიარებლად"</string>
diff --git a/packages/Shell/res/values-kk-rKZ/strings.xml b/packages/Shell/res/values-kk-rKZ/strings.xml
index a24f2cc..5ce7815a 100644
--- a/packages/Shell/res/values-kk-rKZ/strings.xml
+++ b/packages/Shell/res/values-kk-rKZ/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Қабыршық"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Вирус туралы баянат қабылданды"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Қате туралы есепті бөлісу үшін солға жанаңыз"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Бөліс үшін, вирус туралы баянатты түртіңіз."</string>
diff --git a/packages/Shell/res/values-km-rKH/strings.xml b/packages/Shell/res/values-km-rKH/strings.xml
index 2439248..2814a73 100644
--- a/packages/Shell/res/values-km-rKH/strings.xml
+++ b/packages/Shell/res/values-km-rKH/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"សែល"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"កំពុងដំណើរការរបាយការណ៍កំហុស"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"បានចាប់យករបាយការណ៍កំហុស"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"អូសទៅឆ្វេង ដើម្បីចែករំលែករបាយការណ៍កំហុសរបស់អ្នក"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"ប៉ះ ដើម្បីចែករំលែករបាយការណ៍កំហុសរបស់អ្នក"</string>
diff --git a/packages/Shell/res/values-kn-rIN/strings.xml b/packages/Shell/res/values-kn-rIN/strings.xml
index bca9c45..be34c85 100644
--- a/packages/Shell/res/values-kn-rIN/strings.xml
+++ b/packages/Shell/res/values-kn-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ಶೆಲ್"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ದೋಷ ವರದಿ ಪ್ರಗತಿಯಲ್ಲಿದೆ"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"ದೋಷದ ವರದಿಯನ್ನು ಸೆರೆಹಿಡಿಯಲಾಗಿದೆ"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"ನಿಮ್ಮ ದೋಷ ವರದಿಯನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ಎಡಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"ನಿಮ್ಮ ದೋಷದ ವರದಿಯನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ಸ್ಪರ್ಶಿಸಿ"</string>
diff --git a/packages/Shell/res/values-ko/strings.xml b/packages/Shell/res/values-ko/strings.xml
index a3a0bf3..46c0daa 100644
--- a/packages/Shell/res/values-ko/strings.xml
+++ b/packages/Shell/res/values-ko/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"셸"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"버그 신고서 캡처됨"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"왼쪽으로 스와이프하여 버그 신고서를 공유하세요."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"버그 신고서를 공유하려면 터치하세요."</string>
diff --git a/packages/Shell/res/values-ky-rKG/strings.xml b/packages/Shell/res/values-ky-rKG/strings.xml
index 622920a..aa8c538 100644
--- a/packages/Shell/res/values-ky-rKG/strings.xml
+++ b/packages/Shell/res/values-ky-rKG/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Командалык кабык"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Ката тууралуу билдирүү түзүлдү"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Ката жөнүндө кабар менен бөлүшүү үчүн солго серпип коюңуз"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Ката тууралуу билдирүүңүздү жөнөтүш үчүн, тийиңиз"</string>
diff --git a/packages/Shell/res/values-lo-rLA/strings.xml b/packages/Shell/res/values-lo-rLA/strings.xml
index 537f197..caf11c4 100644
--- a/packages/Shell/res/values-lo-rLA/strings.xml
+++ b/packages/Shell/res/values-lo-rLA/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ລາຍງານບັນຫາພວມດຳເນີນຢູ່"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"ລາຍງານຈຸດບົກພ່ອງຖືກເກັບກຳແລ້ວ"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"ປັດໄປຊ້າຍເພື່ອສົ່ງລາຍງານຂໍ້ຜິດພາດຂອງທ່ານ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"ແຕະເພື່ອສົ່ງການລາຍງານປັນຫາຂອງທ່ານ"</string>
diff --git a/packages/Shell/res/values-lt/strings.xml b/packages/Shell/res/values-lt/strings.xml
index 6965e4c..e70cf9f 100644
--- a/packages/Shell/res/values-lt/strings.xml
+++ b/packages/Shell/res/values-lt/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Apvalkalas"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Riktų ataskaita užfiksuota"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Perbraukite kairėn, kad bendrintumėte rikto ataskaitą"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Palieskite, kad bendrintumėte riktų ataskaitą"</string>
diff --git a/packages/Shell/res/values-lv/strings.xml b/packages/Shell/res/values-lv/strings.xml
index efc40f3..c5d10bb 100644
--- a/packages/Shell/res/values-lv/strings.xml
+++ b/packages/Shell/res/values-lv/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Aizsargs"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Izveidots kļūdu pārskats"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Velciet pa kreisi, lai kopīgotu savu kļūdu ziņojumu."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Pieskarieties, lai kopīgotu kļūdu pārskatu."</string>
diff --git a/packages/Shell/res/values-mk-rMK/strings.xml b/packages/Shell/res/values-mk-rMK/strings.xml
index 7571c2c..4baa86a 100644
--- a/packages/Shell/res/values-mk-rMK/strings.xml
+++ b/packages/Shell/res/values-mk-rMK/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Обвивка"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Извештајот за грешка е во тек"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Извештајот за грешка е снимен"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Повлечете налево за да споделите пријава за грешка"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Допри да се сподели твојот извештај за грешка"</string>
diff --git a/packages/Shell/res/values-ml-rIN/strings.xml b/packages/Shell/res/values-ml-rIN/strings.xml
index 4779e4d7..5dd7763 100644
--- a/packages/Shell/res/values-ml-rIN/strings.xml
+++ b/packages/Shell/res/values-ml-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ഷെൽ"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ബഗ് റിപ്പോർട്ട് പുരോഗതിയിലാണ്"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"ബഗ് റിപ്പോർട്ട് ക്യാപ്ചർ ചെയ്തു"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"നിങ്ങളുടെ ബഗ് റിപ്പോർട്ട് പങ്കിടുന്നതിന് ഇടത്തേയ്ക്ക് സ്വൈപ്പുചെയ്യുക"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"നിങ്ങളുടെ ബഗ് റിപ്പോർട്ട് പങ്കിടാൻ സ്പർശിക്കുക"</string>
diff --git a/packages/Shell/res/values-mn-rMN/strings.xml b/packages/Shell/res/values-mn-rMN/strings.xml
index 71ad7f6..f7a9688 100644
--- a/packages/Shell/res/values-mn-rMN/strings.xml
+++ b/packages/Shell/res/values-mn-rMN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Шел"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Алдааны тайлан үргэлжилж байна"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Алдааны мэдээлэл хүлээн авав"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Өөрийн согог репортыг хуваалцахын тулд зүүн шудрана уу"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Та алдааны мэдэгдлийг хуваалцах бол хүрнэ үү"</string>
diff --git a/packages/Shell/res/values-mr-rIN/strings.xml b/packages/Shell/res/values-mr-rIN/strings.xml
index c32bb5b..e0db42b 100644
--- a/packages/Shell/res/values-mr-rIN/strings.xml
+++ b/packages/Shell/res/values-mr-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"शेल"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"दोष अहवाल प्रगतीपथावर आहे"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"दोष अहवाल कॅप्चर केला"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"आपला दोष अहवाल सामायिक करण्यासाठी डावीकडे स्वाइप करा"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"आपला दोष अहवाल सामायिक करण्यासाठी स्पर्श करा"</string>
diff --git a/packages/Shell/res/values-ms-rMY/strings.xml b/packages/Shell/res/values-ms-rMY/strings.xml
index 852a9e4..bbbb42a 100644
--- a/packages/Shell/res/values-ms-rMY/strings.xml
+++ b/packages/Shell/res/values-ms-rMY/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Laporan pepijat telah ditangkap"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Leret ke kiri untuk berkongsi laporan pepijat anda"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Sentuh untuk berkongsi laporan pepijat anda"</string>
diff --git a/packages/Shell/res/values-my-rMM/strings.xml b/packages/Shell/res/values-my-rMM/strings.xml
index 4ba9037..d49dc15 100644
--- a/packages/Shell/res/values-my-rMM/strings.xml
+++ b/packages/Shell/res/values-my-rMM/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"အခွံ"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ချွတ်ယွင်းချက် အစီရင်ခံစာ ပြုလုပ်ဆဲ"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"အမှားအယွင်းမှတ်တမ်းကို အောင်မြင်စွာ သိမ်းဆည်းပြီး"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"သင်၏ ဘာဂ် အစီရင်ခံစာကို မျှပေးရန် ဘယ်ဘက်သို့ ပွတ်ဆွဲရန်"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"အမှားအယွင်း မှတ်တမ်းကို မျှဝေရန် ထိလိုက်ပါ"</string>
diff --git a/packages/Shell/res/values-nb/strings.xml b/packages/Shell/res/values-nb/strings.xml
index c543e49..d3aafdd 100644
--- a/packages/Shell/res/values-nb/strings.xml
+++ b/packages/Shell/res/values-nb/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Kommandoliste"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Feilrapport pågår"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Feilrapporten er lagret"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Sveip til venstre for å dele feilrapporten din"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Trykk for å dele feilrapporten din"</string>
diff --git a/packages/Shell/res/values-ne-rNP/strings.xml b/packages/Shell/res/values-ne-rNP/strings.xml
index f57112a..fb81e55 100644
--- a/packages/Shell/res/values-ne-rNP/strings.xml
+++ b/packages/Shell/res/values-ne-rNP/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"सेल"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"बग प्रतिवेदन समातियो"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"तपाईँको बग रिपोर्ट साझेदारी गर्न बायाँ स्वाइप गर्नुहोस्"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"तपाईंको बग रिपोर्ट साझेदारी गर्न छुनुहोस्"</string>
diff --git a/packages/Shell/res/values-nl/strings.xml b/packages/Shell/res/values-nl/strings.xml
index 1519454..a097fea 100644
--- a/packages/Shell/res/values-nl/strings.xml
+++ b/packages/Shell/res/values-nl/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Bugrapport wordt uitgevoerd"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Foutenrapport vastgelegd"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Veeg naar links om je bugmelding te delen"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Raak aan om je foutenrapport te delen"</string>
diff --git a/packages/Shell/res/values-pa-rIN/strings.xml b/packages/Shell/res/values-pa-rIN/strings.xml
index 3f59bb9..54e30d6 100644
--- a/packages/Shell/res/values-pa-rIN/strings.xml
+++ b/packages/Shell/res/values-pa-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ਸ਼ੈਲ"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ਬੱਗ ਰਿਪੋਰਟ \'ਤੇ ਕਾਰਵਾਈ ਹੋ ਰਹੀ ਹੈ"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"ਬਗ ਰਿਪੋਰਟ ਕੈਪਚਰ ਕੀਤੀ"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"ਤੁਹਾਡੀ ਬਗ ਰਿਪੋਰਟ ਸ਼ੇਅਰ ਕਰਨ ਲਈ ਖੱਬੇ ਪਾਸੇ ਸਵਾਈਪ ਕਰੋ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"ਆਪਣੀ ਬਗ ਰਿਪੋਰਟ ਸ਼ੇਅਰ ਕਰਨ ਲਈ ਛੋਹਵੋ"</string>
diff --git a/packages/Shell/res/values-pl/strings.xml b/packages/Shell/res/values-pl/strings.xml
index accc92e8..599f8c1 100644
--- a/packages/Shell/res/values-pl/strings.xml
+++ b/packages/Shell/res/values-pl/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Powłoka"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Trwa zgłaszanie błędu"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Raport o błędach został zapisany"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Przesuń palcem w lewo, by udostępnić swoje zgłoszenie błędu"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Kliknij, by udostępnić raport o błędach"</string>
diff --git a/packages/Shell/res/values-pt-rBR/strings.xml b/packages/Shell/res/values-pt-rBR/strings.xml
index 7d4b0d3..a1a9ad1 100644
--- a/packages/Shell/res/values-pt-rBR/strings.xml
+++ b/packages/Shell/res/values-pt-rBR/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Relatório de bugs em andamento"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Relatório de bugs capturado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Deslize para a esquerda para compartilhar seu relatório de bugs"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toque para compartilhar seu relatório de bugs"</string>
diff --git a/packages/Shell/res/values-pt-rPT/strings.xml b/packages/Shell/res/values-pt-rPT/strings.xml
index 007d683..2e25f8c 100644
--- a/packages/Shell/res/values-pt-rPT/strings.xml
+++ b/packages/Shell/res/values-pt-rPT/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Relatório de erro em curso"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Relatório de erros capturado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Deslizar rapidamente para a esquerda para partilhar o seu relatório de erros"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toque para partilhar o relatório de erros"</string>
diff --git a/packages/Shell/res/values-pt/strings.xml b/packages/Shell/res/values-pt/strings.xml
index 7d4b0d3..a1a9ad1 100644
--- a/packages/Shell/res/values-pt/strings.xml
+++ b/packages/Shell/res/values-pt/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Relatório de bugs em andamento"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Relatório de bugs capturado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Deslize para a esquerda para compartilhar seu relatório de bugs"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toque para compartilhar seu relatório de bugs"</string>
diff --git a/packages/Shell/res/values-ro/strings.xml b/packages/Shell/res/values-ro/strings.xml
index e7395e1..d8a3b92 100644
--- a/packages/Shell/res/values-ro/strings.xml
+++ b/packages/Shell/res/values-ro/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Raportul de eroare se creează"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Raportul despre erori a fost creat"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Glisați la stânga pentru a trimite raportul de erori"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Atingeți pentru a permite accesul la raportul despre erori"</string>
diff --git a/packages/Shell/res/values-ru/strings.xml b/packages/Shell/res/values-ru/strings.xml
index c9276cf..436a590 100644
--- a/packages/Shell/res/values-ru/strings.xml
+++ b/packages/Shell/res/values-ru/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Оболочка"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Отправка сообщения об ошибке..."</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Отчет об ошибке сохранен"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Проведите влево, чтобы отправить отчет"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Нажмите, чтобы отправить отчет об ошибках"</string>
diff --git a/packages/Shell/res/values-si-rLK/strings.xml b/packages/Shell/res/values-si-rLK/strings.xml
index 937c1bc..a0bc5f6 100644
--- a/packages/Shell/res/values-si-rLK/strings.xml
+++ b/packages/Shell/res/values-si-rLK/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ෂෙල්"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"දෝෂය වාර්තා කිරීම සිදු කරමින් පවතී"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"දෝෂ වාර්තාව ලබාගන්නා ලදි"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"ඔබගේ දෝෂ වාර්තාව බෙදාගැනීමට වමට ස්වයිප් කරන්න"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"ඔබගේ දෝෂ වාර්තාව බෙදා ගැනීමට ස්පර්ශ කරන්න"</string>
diff --git a/packages/Shell/res/values-sk/strings.xml b/packages/Shell/res/values-sk/strings.xml
index cd52efc..e568946 100644
--- a/packages/Shell/res/values-sk/strings.xml
+++ b/packages/Shell/res/values-sk/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Prostredie"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Prebieha hlásenie chyby"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Hlásenie o chybách bolo vytvorené"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Ak chcete hlásenie o chybe zdieľať, prejdite prstom doľava."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Hlásenie o chybách môžete zdielať klepnutím"</string>
diff --git a/packages/Shell/res/values-sl/strings.xml b/packages/Shell/res/values-sl/strings.xml
index 25553f2..6ac5ac8c 100644
--- a/packages/Shell/res/values-sl/strings.xml
+++ b/packages/Shell/res/values-sl/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Lupina"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Poročanje o napakah poteka"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Poročilo o napaki je posneto"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Povlecite v levo, če želite poslati sporočilo o napaki"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Dotaknite se, če želite deliti sporočilo o napaki z drugimi"</string>
diff --git a/packages/Shell/res/values-sq-rAL/strings.xml b/packages/Shell/res/values-sq-rAL/strings.xml
index add53d8..54f2aad 100644
--- a/packages/Shell/res/values-sq-rAL/strings.xml
+++ b/packages/Shell/res/values-sq-rAL/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Guaska"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Raporti i gabimeve në kod është në progres"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Raporti i defektit në kod u regjistrua"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Rrëshqit majtas për të ndarë raportin e defektit në kod"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Prek për të ndarë raportin e defektit në kod"</string>
diff --git a/packages/Shell/res/values-sr/strings.xml b/packages/Shell/res/values-sr/strings.xml
index bb60f3f..81cde00 100644
--- a/packages/Shell/res/values-sr/strings.xml
+++ b/packages/Shell/res/values-sr/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Прављење извештаја о грешци је у току"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Извештај о грешци је снимљен"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Превуците улево да бисте делили извештај о грешкама"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Додирните да бисте делили извештај о грешци"</string>
diff --git a/packages/Shell/res/values-sv/strings.xml b/packages/Shell/res/values-sv/strings.xml
index dd23e16..2287319 100644
--- a/packages/Shell/res/values-sv/strings.xml
+++ b/packages/Shell/res/values-sv/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Skal"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Felrapportering pågår"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Felrapporten har skapats"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Svep åt vänster om du vill dela felrapporten"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Tryck om du vill dela felrapporten"</string>
diff --git a/packages/Shell/res/values-sw/strings.xml b/packages/Shell/res/values-sw/strings.xml
index 3773cd7..adb97dd 100644
--- a/packages/Shell/res/values-sw/strings.xml
+++ b/packages/Shell/res/values-sw/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Ganda"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Mchakato wa kuripoti hitilafu unaendelea"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Ripoti ya hitilafu imenaswa"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Telezesha kidole kushoto ili ushiriki ripoti yako ya hitilafu"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Gusa ili ushiriki ripoti yako ya hitilafu"</string>
diff --git a/packages/Shell/res/values-ta-rIN/strings.xml b/packages/Shell/res/values-ta-rIN/strings.xml
index 244c929..15a6242 100644
--- a/packages/Shell/res/values-ta-rIN/strings.xml
+++ b/packages/Shell/res/values-ta-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ஷெல்"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"பிழை அறிக்கை செயலிலுள்ளது"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"பிழை அறிக்கைகள் படமெடுக்கப்பட்டன"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"பிழை அறிக்கையைப் பகிர இடது புறமாகத் தேய்க்கவும்"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"உங்கள் பிழை அறிக்கையைப் பகிர, தொடவும்"</string>
diff --git a/packages/Shell/res/values-te-rIN/strings.xml b/packages/Shell/res/values-te-rIN/strings.xml
index cd9b4b7..cd951a2 100644
--- a/packages/Shell/res/values-te-rIN/strings.xml
+++ b/packages/Shell/res/values-te-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"షెల్"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"బగ్ నివేదిక ప్రోగ్రెస్లో ఉంది"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"బగ్ నివేదిక క్యాప్చర్ చేయబడింది"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"మీ బగ్ నివేదికను భాగస్వామ్యం చేయడానికి ఎడమవైపుకు స్వైప్ చేయండి"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"మీ బగ్ నివేదికను భాగస్వామ్యం చేయడానికి తాకండి"</string>
diff --git a/packages/Shell/res/values-th/strings.xml b/packages/Shell/res/values-th/strings.xml
index 35b7ec9..c61ffeb 100644
--- a/packages/Shell/res/values-th/strings.xml
+++ b/packages/Shell/res/values-th/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"รายงานข้อบกพร่องอยู่ระหว่างดำเนินการ"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"จับภาพรายงานข้อบกพร่องแล้ว"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"กวาดไปทางซ้ายเพื่อแชร์รายงานข้อบกพร่อง"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"แตะเพื่อแชร์รายงานข้อบกพร่องของคุณ"</string>
diff --git a/packages/Shell/res/values-tl/strings.xml b/packages/Shell/res/values-tl/strings.xml
index 21df206..3e9a68d 100644
--- a/packages/Shell/res/values-tl/strings.xml
+++ b/packages/Shell/res/values-tl/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Kasalukuyang ginagawa ang ulat ng bug"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Na-capture ang ulat ng bug"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Mag-swipe pakaliwa upang ibahagi ang iyong ulat ng bug"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Pindutin upang ibahagi ang iyong ulat ng bug"</string>
diff --git a/packages/Shell/res/values-tr/strings.xml b/packages/Shell/res/values-tr/strings.xml
index fa8b82b..f90dae0 100644
--- a/packages/Shell/res/values-tr/strings.xml
+++ b/packages/Shell/res/values-tr/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Kabuk"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Hata raporu hazırlanıyor"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Hata raporu kaydedildi"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Hata raporunuzu paylaşmak için hızlıca sola kaydırın"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Hata raporunuzu paylaşmak için dokunun"</string>
diff --git a/packages/Shell/res/values-uk/strings.xml b/packages/Shell/res/values-uk/strings.xml
index ba667f2..dac4fe0 100644
--- a/packages/Shell/res/values-uk/strings.xml
+++ b/packages/Shell/res/values-uk/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Оболонка"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Генерується повідомлення про помилку"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Звіт про помилки створено"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Проведіть пальцем ліворуч, щоб надіслати звіт про помилки"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Торкніться, щоб надіслати звіт про помилки"</string>
diff --git a/packages/Shell/res/values-ur-rPK/strings.xml b/packages/Shell/res/values-ur-rPK/strings.xml
index 255aaf8..206f02a 100644
--- a/packages/Shell/res/values-ur-rPK/strings.xml
+++ b/packages/Shell/res/values-ur-rPK/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"شیل"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"بگ رپورٹ پر پیشرفت جاری ہے"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"بَگ رپورٹ کیپچر کر لی گئی"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"اپنی بگ رپورٹ کا اشتراک کرنے کیلئے بائیں سوائپ کریں"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"اپنی بَگ رپورٹ کا اشتراک کرنے کیلئے ٹچ کریں"</string>
diff --git a/packages/Shell/res/values-uz-rUZ/strings.xml b/packages/Shell/res/values-uz-rUZ/strings.xml
index 998387e..0b8c1fb 100644
--- a/packages/Shell/res/values-uz-rUZ/strings.xml
+++ b/packages/Shell/res/values-uz-rUZ/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Terminal"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Xatoliklar hisoboti yuborilmoqda…"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Xatolik hisobotini yozib olindi"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Xatolik hisobotini yuborish uchun barmog‘ingiz bilan chapga suring"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Xatolik hisobotini bo‘lishish uchun barmog‘ingizni tegizing."</string>
diff --git a/packages/Shell/res/values-vi/strings.xml b/packages/Shell/res/values-vi/strings.xml
index 5c11b13..9ef7cf9 100644
--- a/packages/Shell/res/values-vi/strings.xml
+++ b/packages/Shell/res/values-vi/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Đang tiến hành báo cáo lỗi"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Báo cáo lỗi đã được chụp"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Vuốt sang trái để chia sẻ báo cáo lỗi của bạn"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Chạm để chia sẻ báo cáo lỗi của bạn"</string>
diff --git a/packages/Shell/res/values-zh-rCN/strings.xml b/packages/Shell/res/values-zh-rCN/strings.xml
index 0a2f81b..0928bb3 100644
--- a/packages/Shell/res/values-zh-rCN/strings.xml
+++ b/packages/Shell/res/values-zh-rCN/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"已抓取错误报告"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"向左滑动即可分享错误报告"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"触摸即可分享您的错误报告"</string>
diff --git a/packages/Shell/res/values-zh-rHK/strings.xml b/packages/Shell/res/values-zh-rHK/strings.xml
index 227d937..26a84c3 100644
--- a/packages/Shell/res/values-zh-rHK/strings.xml
+++ b/packages/Shell/res/values-zh-rHK/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"命令介面"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"已擷取錯誤報告"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"向左滑動即可分享錯誤報告"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"輕觸即可分享您的錯誤報告"</string>
diff --git a/packages/Shell/res/values-zh-rTW/strings.xml b/packages/Shell/res/values-zh-rTW/strings.xml
index b9f8ee8..f79e58e 100644
--- a/packages/Shell/res/values-zh-rTW/strings.xml
+++ b/packages/Shell/res/values-zh-rTW/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"殼層"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"已擷取錯誤報告"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"向左滑動即可分享錯誤報告"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"輕觸即可分享您的錯誤報告"</string>
diff --git a/packages/Shell/res/values-zu/strings.xml b/packages/Shell/res/values-zu/strings.xml
index b675858..2e209e1 100644
--- a/packages/Shell/res/values-zu/strings.xml
+++ b/packages/Shell/res/values-zu/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"I-Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Umbiko wesiphazamisi uyaqhubeka"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Umbiko wesiphazamisi uthwetshuliwe"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Swayiphela kwesokunxele ukuze wabelane umbiko wesiphazamiso sakho"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Thinta ukuze wabelane ngombiko wakho wesiphazamisi"</string>
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 4469d38..cff36f7 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -17,6 +17,8 @@
<resources>
<string name="app_label">Shell</string>
+ <!-- Title of notification indicating a bugreport process is in progress. [CHAR LIMIT=50] -->
+ <string name="bugreport_in_progress_title">Bug report in progress</string>
<!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
<string name="bugreport_finished_title">Bug report captured</string>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index a2030ef..d0e91d2 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -21,11 +21,16 @@
import java.io.BufferedOutputStream;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -45,26 +50,102 @@
import android.content.res.Configuration;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.Process;
import android.os.SystemProperties;
import android.support.v4.content.FileProvider;
+import android.text.format.DateUtils;
import android.util.Log;
import android.util.Patterns;
+import android.util.SparseArray;
import android.widget.Toast;
+/**
+ * Service used to keep progress of bug reports processes ({@code dumpstate}).
+ * <p>
+ * The workflow is:
+ * <ol>
+ * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with its pid and the
+ * estimated total effort.
+ * <li>{@link BugreportReceiver} receives the intent and delegates it to this service.
+ * <li>Upon start, this service:
+ * <ol>
+ * <li>Issues a system notification so user can watch the progresss (which is 0% initially).
+ * <li>Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress.
+ * <li>If the progress changed, it updates the system notification.
+ * </ol>
+ * <li>As {@code dumpstate} progresses, it updates the system property.
+ * <li>When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent.
+ * <li>{@link BugreportReceiver} receives the intent and delegates it to this service, which in
+ * turn:
+ * <ol>
+ * <li>Updates the system notification so user can share the bug report.
+ * <li>Stops monitoring that {@code dumpstate} process.
+ * <li>Stops itself if it doesn't have any process left to monitor.
+ * </ol>
+ * </ol>
+ */
public class BugreportProgressService extends Service {
private static final String TAG = "Shell";
+ private static final boolean DEBUG = false;
private static final String AUTHORITY = "com.android.shell";
+ static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED";
+ static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED";
+
static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+ static final String EXTRA_PID = "android.intent.extra.PID";
+ static final String EXTRA_MAX = "android.intent.extra.MAX";
+ static final String EXTRA_NAME = "android.intent.extra.NAME";
+ static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
+
+ private static final int MSG_SERVICE_COMMAND = 1;
+ private static final int MSG_POLL = 2;
+
+ /** Polling frequency, in milliseconds. */
+ private static final long POLLING_FREQUENCY = 500;
+
+ /** How long (in ms) a dumpstate process will be monitored if it didn't show progress. */
+ private static final long INACTIVITY_TIMEOUT = 3 * DateUtils.MINUTE_IN_MILLIS;
+
+ /** System property used for monitoring progress. */
+ private static final String PROGRESS_PROPERTY_TEMPLATE = "dumpstate.%d.progress";
+
+ /** Managed dumpstate processes (keyed by pid) */
+ private final SparseArray<BugreportInfo> mProcesses = new SparseArray<>();
+
+ private Looper mServiceLooper;
+ private ServiceHandler mServiceHandler;
+
+ @Override
+ public void onCreate() {
+ HandlerThread thread = new HandlerThread("BugreportProgressServiceThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+
+ mServiceLooper = thread.getLooper();
+ mServiceHandler = new ServiceHandler(mServiceLooper);
+ }
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
- onBugreportFinished(intent);
+ // Handle it in a separate thread.
+ Message msg = mServiceHandler.obtainMessage();
+ msg.what = MSG_SERVICE_COMMAND;
+ msg.obj = intent;
+ mServiceHandler.sendMessage(msg);
}
+
+ // If service is killed it cannot be recreated because it would not know which
+ // dumpstate PIDs it would have to watch.
return START_NOT_STICKY;
}
@@ -73,26 +154,249 @@
return null;
}
- private void onBugreportFinished(Intent intent) {
- final Context context = getApplicationContext();
- final Configuration conf = context.getResources().getConfiguration();
- final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+ @Override
+ public void onDestroy() {
+ mServiceLooper.quit();
+ super.onDestroy();
+ }
- if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
- triggerLocalNotification(context, bugreportFile, screenshotFile);
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.printf("Monitored dumpstate processes: \n");
+ synchronized (mProcesses) {
+ for (int i = 0; i < mProcesses.size(); i++) {
+ writer.printf("\t%s\n", mProcesses.valueAt(i));
+ }
}
- stopSelf();
+ }
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ pollProgress();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_POLL) {
+ pollProgress();
+ return;
+ }
+
+ if (msg.what != MSG_SERVICE_COMMAND) {
+ // Sanity check.
+ Log.e(TAG, "Invalid message type: " + msg.what);
+ return;
+ }
+
+ // At this point it's handling onStartCommand(), whose intent contains the extras
+ // originally received by BugreportReceiver.
+ if (!(msg.obj instanceof Intent)) {
+ // Sanity check.
+ Log.e(TAG, "Internal error: invalid msg.obj: " + msg.obj);
+ return;
+ }
+ final Parcelable parcel = ((Intent) msg.obj).getParcelableExtra(EXTRA_ORIGINAL_INTENT);
+ if (!(parcel instanceof Intent)) {
+ // Sanity check.
+ Log.e(TAG, "Internal error: msg.obj is missing extra " + EXTRA_ORIGINAL_INTENT);
+ return;
+ }
+
+ final Intent intent = (Intent) parcel;
+ final String action = intent.getAction();
+ int pid = intent.getIntExtra(EXTRA_PID, 0);
+ int max = intent.getIntExtra(EXTRA_MAX, -1);
+ String name = intent.getStringExtra(EXTRA_NAME);
+
+ if (DEBUG) Log.v(TAG, "action: " + action + ", name: " + name + ", pid: " + pid
+ + ", max: "+ max);
+ switch (action) {
+ case INTENT_BUGREPORT_STARTED:
+ if (!startProgress(name, pid, max)) {
+ stopSelfWhenDone();
+ return;
+ }
+ break;
+ case INTENT_BUGREPORT_FINISHED:
+ if (pid == -1) {
+ // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy,
+ // out-of-sync dumpstate process.
+ Log.w(TAG, "Missing " + EXTRA_PID + " on intent " + intent);
+ }
+ stopProgress(pid, intent);
+ break;
+ default:
+ Log.w(TAG, "Unsupported intent: " + action);
+ }
+ return;
+
+ }
+
+ /**
+ * Creates the {@link BugreportInfo} for a process and issue a system notification to
+ * indicate its progress.
+ *
+ * @return whether it succeeded or not.
+ */
+ private boolean startProgress(String name, int pid, int max) {
+ if (name == null) {
+ Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent");
+ name = "N/A";
+ }
+ if (pid == -1) {
+ Log.e(TAG, "Missing " + EXTRA_PID + " on start intent");
+ return false;
+ }
+ if (max <= 0) {
+ Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
+ return false;
+ }
+
+ final BugreportInfo info = new BugreportInfo(pid, name, max);
+ synchronized (mProcesses) {
+ if (mProcesses.indexOfKey(pid) >= 0) {
+ Log.w(TAG, "PID " + pid + " already watched");
+ } else {
+ mProcesses.put(info.pid, info);
+ }
+ }
+ updateProgress(info);
+ return true;
+ }
+
+ /**
+ * Updates the system notification for a given bug report.
+ */
+ private void updateProgress(BugreportInfo info) {
+ if (info.max <= 0 || info.progress < 0 || info.name == null) {
+ Log.e(TAG, "Invalid progress values for " + info);
+ return;
+ }
+
+ final Context context = getApplicationContext();
+ final NumberFormat nf = NumberFormat.getPercentInstance();
+ nf.setMinimumFractionDigits(2);
+ nf.setMaximumFractionDigits(2);
+ final String percentText = nf.format((double) info.progress / info.max);
+
+ final String title = context.getString(R.string.bugreport_in_progress_title);
+ final Notification notification = new Notification.Builder(context)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setContentTitle(title)
+ .setTicker(title)
+ .setContentText(info.name)
+ .setContentInfo(percentText)
+ .setProgress(info.max, info.progress, false)
+ // TODO: .setOngoing(true) once it has a CANCEL action
+ .setLocalOnly(true)
+ .setColor(context.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .build();
+
+ NotificationManager.from(context).notify(TAG, info.pid, notification);
+ }
+
+ /**
+ * Finalizes the progress on a given process and sends the finished intent.
+ */
+ private void stopProgress(int pid, Intent intent) {
+ synchronized (mProcesses) {
+ if (mProcesses.indexOfKey(pid) < 0) {
+ Log.w(TAG, "PID not watched: " + pid);
+ } else {
+ mProcesses.remove(pid);
+ }
+ stopSelfWhenDone();
+ }
+ if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): cancel notification");
+ NotificationManager.from(getApplicationContext()).cancel(TAG, pid);
+ if (intent != null) {
+ // Bug report finished fine: send a new, different notification.
+ if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): finish bug report");
+ onBugreportFinished(pid, intent);
+ }
+ }
+
+ /**
+ * Poll {@link SystemProperties} to get the progress on each monitored process.
+ */
+ private void pollProgress() {
+ synchronized (mProcesses) {
+ if (mProcesses.size() == 0) {
+ Log.d(TAG, "No process to poll progress.");
+ }
+ for (int i = 0; i < mProcesses.size(); i++) {
+ int pid = mProcesses.keyAt(i);
+ String property = String.format(PROGRESS_PROPERTY_TEMPLATE, pid);
+ int progress;
+ try {
+ progress = SystemProperties.getInt(property, 0);
+ } catch (IllegalArgumentException e) {
+ Log.v(TAG, "Could not read system property " + property, e);
+ continue;
+ }
+ if (progress == 0) {
+ Log.v(TAG, "System property " + property + " is not set yet");
+ continue;
+ }
+
+ BugreportInfo info = mProcesses.valueAt(i);
+
+ if (progress != info.progress) {
+ if (DEBUG) Log.v(TAG, "Updating progress for PID " + pid + " from "
+ + info.progress + " to " + progress);
+ info.progress = progress;
+ info.lastUpdate = System.currentTimeMillis();
+ updateProgress(info);
+ } else {
+ long inactiveTime = System.currentTimeMillis() - info.lastUpdate;
+ if (inactiveTime >= INACTIVITY_TIMEOUT) {
+ Log.w(TAG, "No progress update for process " + pid + " since "
+ + info.getFormattedLastUpdate());
+ stopProgress(info.pid, null);
+ }
+ }
+ }
+ // Keep polling...
+ sendEmptyMessageDelayed(MSG_POLL, POLLING_FREQUENCY);
+ }
+ }
+
+ /**
+ * Finishes the service when it's not monitoring any more processes.
+ */
+ private void stopSelfWhenDone() {
+ synchronized (mProcesses) {
+ if (mProcesses.size() > 0) {
+ if (DEBUG) Log.v(TAG, "Staying alive, waiting for pids " + mProcesses);
+ return;
+ }
+ Log.v(TAG, "No more pids to handle, shutting down");
+ stopSelf();
+ }
+ }
+
+ private void onBugreportFinished(int pid, Intent intent) {
+ final Context context = getApplicationContext();
+ final Configuration conf = context.getResources().getConfiguration();
+ final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
+ final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+
+ if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
+ triggerLocalNotification(context, pid, bugreportFile, screenshotFile);
+ }
+ }
}
/**
- * Responsible for triggering a notification that allows the user to start a
- * "share" intent with the bug report. On watches we have other methods to allow the user to
- * start this intent (usually by triggering it on another connected device); we don't need to
- * display the notification in this case.
+ * Responsible for triggering a notification that allows the user to start a "share" intent with
+ * the bug report. On watches we have other methods to allow the user to start this intent
+ * (usually by triggering it on another connected device); we don't need to display the
+ * notification in this case.
*/
- private static void triggerLocalNotification(final Context context, final File bugreportFile,
- final File screenshotFile) {
+ private static void triggerLocalNotification(final Context context, final int pid,
+ final File bugreportFile, final File screenshotFile) {
if (!bugreportFile.exists() || !bugreportFile.canRead()) {
Log.e(TAG, "Could not read bugreport file " + bugreportFile);
Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
@@ -103,10 +407,10 @@
boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
if (!isPlainText) {
// Already zipped, send it right away.
- sendBugreportNotification(context, bugreportFile, screenshotFile);
+ sendBugreportNotification(context, pid, bugreportFile, screenshotFile);
} else {
// Asynchronously zip the file first, then send it.
- sendZippedBugreportNotification(context, bugreportFile, screenshotFile);
+ sendZippedBugreportNotification(context, pid, bugreportFile, screenshotFile);
}
}
@@ -155,7 +459,7 @@
/**
* Sends a bugreport notitication.
*/
- private static void sendBugreportNotification(Context context, File bugreportFile,
+ private static void sendBugreportNotification(Context context, int pid, File bugreportFile,
File screenshotFile) {
// Files are kept on private storage, so turn into Uris that we can
// grant temporary permissions for.
@@ -173,10 +477,11 @@
}
notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final String title = context.getString(R.string.bugreport_finished_title);
final Notification.Builder builder = new Notification.Builder(context)
.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setContentTitle(context.getString(R.string.bugreport_finished_title))
- .setTicker(context.getString(R.string.bugreport_finished_title))
+ .setContentTitle(title)
+ .setTicker(title)
.setContentText(context.getString(R.string.bugreport_finished_text))
.setContentIntent(PendingIntent.getActivity(
context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT))
@@ -185,19 +490,19 @@
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color));
- NotificationManager.from(context).notify(TAG, 0, builder.build());
+ NotificationManager.from(context).notify(TAG, pid, builder.build());
}
/**
* Sends a zipped bugreport notification.
*/
private static void sendZippedBugreportNotification(final Context context,
- final File bugreportFile, final File screenshotFile) {
+ final int pid, final File bugreportFile, final File screenshotFile) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
File zippedFile = zipBugreport(bugreportFile);
- sendBugreportNotification(context, zippedFile, screenshotFile);
+ sendBugreportNotification(context, pid, zippedFile, screenshotFile);
return null;
}
}.execute();
@@ -213,8 +518,8 @@
Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
File bugreportZippedFile = new File(zippedPath);
try (InputStream is = new FileInputStream(bugreportFile);
- ZipOutputStream zos = new ZipOutputStream(
- new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
+ ZipOutputStream zos = new ZipOutputStream(
+ new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
ZipEntry entry = new ZipEntry(bugreportFile.getName());
entry.setTime(bugreportFile.lastModified());
zos.putNextEntry(entry);
@@ -230,8 +535,8 @@
}
return bugreportZippedFile;
} catch (IOException e) {
- Log.e(TAG, "exception zipping file " + zippedPath, e);
- return bugreportFile; // Return original.
+ Log.e(TAG, "exception zipping file " + zippedPath, e);
+ return bugreportFile; // Return original.
}
}
@@ -281,4 +586,55 @@
return null;
}
}
+
+ /**
+ * Information about a bug report process while its in progress.
+ */
+ private static final class BugreportInfo {
+ /**
+ * {@code pid} of the {@code dumpstate} process generating the bug report.
+ */
+ final int pid;
+
+ /**
+ * Name of the bug report, will be used to rename the final files.
+ * <p>
+ * Initial value is the bug report filename reported by {@code dumpstate}, but user can
+ * change it later to a more meaningful name.
+ */
+ final String name;
+
+ /**
+ * Maximum progress of the bug report generation.
+ */
+ final int max;
+
+ /**
+ * Current progress of the bug report generation.
+ */
+ int progress;
+
+ /**
+ * Time of the last progress update.
+ */
+ long lastUpdate = System.currentTimeMillis();
+
+ BugreportInfo(int pid, String name, int max) {
+ this.pid = pid;
+ this.name = name;
+ this.max = max;
+ }
+
+ String getFormattedLastUpdate() {
+ return SimpleDateFormat.getDateTimeInstance().format(new Date(lastUpdate));
+ }
+
+ @Override
+ public String toString() {
+ final float percent = ((float) progress * 100 / max);
+ return String.format("Progress for %s (pid=%d): %d/%d (%2.2f%%) Last update: %s", name,
+ pid, progress, max, percent,
+ getFormattedLastUpdate());
+ }
+ }
}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index f1da14d..5133162 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -17,6 +17,8 @@
package com.android.shell;
import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_ORIGINAL_INTENT;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
import static com.android.shell.BugreportProgressService.getFileExtra;
import java.io.File;
@@ -50,13 +52,16 @@
// Clean up older bugreports in background
cleanupOldFiles(intent);
- // Delegate to service.
+ // Delegate intent handling to service.
Intent serviceIntent = new Intent(context, BugreportProgressService.class);
- serviceIntent.putExtras(intent.getExtras());
+ serviceIntent.putExtra(EXTRA_ORIGINAL_INTENT, intent);
context.startService(serviceIntent);
}
private void cleanupOldFiles(Intent intent) {
+ if (!INTENT_BUGREPORT_FINISHED.equals(intent.getAction())) {
+ return;
+ }
final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
final PendingResult result = goAsync();
new AsyncTask<Void, Void, Void>() {
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 1bdd9dd..33c4ef1 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -19,7 +19,12 @@
import static android.test.MoreAsserts.assertContainsRegex;
import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_MAX;
+import static com.android.shell.BugreportProgressService.EXTRA_NAME;
+import static com.android.shell.BugreportProgressService.EXTRA_PID;
import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_STARTED;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
@@ -96,6 +101,33 @@
cancelExistingNotifications();
}
+ public void testFullWorkflow() throws Exception {
+ final String name = "BUG, Y U NO REPORT?";
+ // TODO: call method to remove property instead
+ SystemProperties.set("dumpstate.42.progress", "-1");
+
+ Intent intent = new Intent(INTENT_BUGREPORT_STARTED);
+ intent.putExtra(EXTRA_PID, 42);
+ intent.putExtra(EXTRA_NAME, name);
+ intent.putExtra(EXTRA_MAX, 1000);
+ mContext.sendBroadcast(intent);
+
+ assertProgressNotification(name, "0.00%");
+
+ SystemProperties.set("dumpstate.42.progress", "108");
+ assertProgressNotification(name, "10.80%");
+
+ SystemProperties.set("dumpstate.42.progress", "500");
+ assertProgressNotification(name, "50.00%");
+
+ createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT);
+ createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT);
+ Bundle extras = sendBugreportFinishedIntent(42, PLAIN_TEXT_PATH, SCREENSHOT_PATH);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT);
+
+ // TODO: assert service is down
+ }
+
public void testBugreportFinished_plainBugreportAndScreenshot() throws Exception {
createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT);
createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT);
@@ -131,13 +163,32 @@
}
}
+ private void assertProgressNotification(String name, String percent) {
+ // TODO: it current looks for 3 distinct objects, without taking advantage of their
+ // relationship.
+ String title = mContext.getString(R.string.bugreport_in_progress_title);
+ Log.v(TAG, "Looking for progress notification title: '" + title+ "'");
+ mUiBot.getNotification(title);
+ Log.v(TAG, "Looking for progress notification details: '" + name + "-" + percent + "'");
+ mUiBot.getObject(name);
+ mUiBot.getObject(percent);
+ }
+
/**
* Sends a "bugreport finished" intent and waits for the result.
*
* @return extras sent to the bugreport finished consumer.
*/
private Bundle sendBugreportFinishedIntent(String bugreportPath, String screenshotPath) {
- Intent intent = new Intent("android.intent.action.BUGREPORT_FINISHED");
+ return sendBugreportFinishedIntent(null, bugreportPath, screenshotPath);
+ }
+
+ private Bundle sendBugreportFinishedIntent(Integer pid, String bugreportPath,
+ String screenshotPath) {
+ Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
+ if (pid != null) {
+ intent.putExtra(EXTRA_PID, pid);
+ }
if (bugreportPath != null) {
intent.putExtra(EXTRA_BUGREPORT, bugreportPath);
}
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
index f5dd31c..fa1714e 100644
--- a/packages/Shell/tests/src/com/android/shell/UiBot.java
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -42,32 +42,49 @@
}
/**
- * Opens the system notification and clicks a given notification.
+ * Opens the system notification and gets a given notification.
*
* @param text Notificaton's text as displayed by the UI.
+ * @return notification object.
*/
- public void clickOnNotification(String text) {
+ public UiObject getNotification(String text) {
boolean opened = mDevice.openNotification();
Log.v(TAG, "openNotification(): " + opened);
boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGED)), mTimeout);
assertTrue("could not get system ui (" + SYSTEMUI_PACKAGED + ")", gotIt);
- gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
- assertTrue("object with text '(" + text + "') not visible yet", gotIt);
+ return getObject(text);
+ }
- UiObject notification = getVisibleObject(text);
-
+ /**
+ * Opens the system notification and clicks a given notification.
+ *
+ * @param text Notificaton's text as displayed by the UI.
+ */
+ public void clickOnNotification(String text) {
+ UiObject notification = getNotification(text);
click(notification, "bug report notification");
}
/**
- * Gets an object which is guaranteed to be present in the current UI.\
+ * Gets an object that might not yet be available in current UI.
+ *
+ * @param text Object's text as displayed by the UI.
+ */
+ public UiObject getObject(String text) {
+ boolean gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
+ assertTrue("object with text '(" + text + "') not visible yet", gotIt);
+ return getVisibleObject(text);
+ }
+
+ /**
+ * Gets an object which is guaranteed to be present in the current UI.
*
* @param text Object's text as displayed by the UI.
*/
public UiObject getVisibleObject(String text) {
UiObject uiObject = mDevice.findObject(new UiSelector().text(text));
- assertTrue("could not find object with text '(" + text + "')", uiObject.exists());
+ assertTrue("could not find object with text '" + text + "'", uiObject.exists());
return uiObject;
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6fda2c6..2f79adf 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -87,6 +87,7 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
<uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+ <uses-permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" />
<uses-permission android:name="android.permission.SET_ORIENTATION" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
diff --git a/packages/SystemUI/res/drawable/notification_expand_more.xml b/packages/SystemUI/res/drawable/notification_expand_more.xml
index 5aa7937..430fb0d 100644
--- a/packages/SystemUI/res/drawable/notification_expand_more.xml
+++ b/packages/SystemUI/res/drawable/notification_expand_more.xml
@@ -12,7 +12,7 @@
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.
+ limitations under the License._more
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22.0dp"
diff --git a/packages/SystemUI/res/layout/notification_header.xml b/packages/SystemUI/res/layout/notification_header.xml
deleted file mode 100644
index 3475d00..0000000
--- a/packages/SystemUI/res/layout/notification_header.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2015 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<com.android.systemui.statusbar.notification.NotificationHeaderView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_header_height"
- android:clipChildren="false">
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_header_height"
- android:layout_gravity="start"
- android:gravity="center_vertical"
- android:paddingStart="@dimen/notification_content_margin_start">
- <ImageView
- android:id="@+id/header_notification_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginEnd="4dp"
- />
- <TextView
- android:id="@+id/number_of_children"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.Material.Notification"
- android:layout_marginEnd="2dp"
- android:layout_marginStart="1dp"
- />
- <TextView
- android:id="@+id/app_name_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.HeaderTitle"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="8dp"
- />
- <TextView
- android:id="@+id/app_title_sub_text_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.HeaderTitle"
- android:layout_marginEnd="8dp"
- android:text="@string/notification_header_divider_symbol"/>
- <TextView
- android:id="@+id/title_sub_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.HeaderTitle"
- android:layout_marginEnd="8dp" />
- <TextView
- android:id="@+id/post_time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="end|center_vertical"
- android:textAppearance="@*android:style/TextAppearance.Material.Notification.Time"
- android:paddingEnd="8dp"
- />
- </LinearLayout>
- <ImageButton
- android:id="@+id/notification_expand_button"
- android:background="@null"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_gravity="end|center_vertical"
- android:src="@drawable/notification_expand_more"
- android:layout_marginEnd="8dp"
- />
-</com.android.systemui.statusbar.notification.NotificationHeaderView>
diff --git a/packages/SystemUI/res/layout/recents_history.xml b/packages/SystemUI/res/layout/recents_history.xml
index de70d30..b65a5c5 100644
--- a/packages/SystemUI/res/layout/recents_history.xml
+++ b/packages/SystemUI/res/layout/recents_history.xml
@@ -19,16 +19,6 @@
android:layout_height="match_parent"
android:background="#99000000"
android:orientation="vertical">
- <TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="14dp"
- android:gravity="start"
- android:text="@string/recents_history_label"
- android:textSize="24sp"
- android:textColor="#FFFFFF"
- android:fontFamily="sans-serif-medium" />
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/recents_history_button.xml b/packages/SystemUI/res/layout/recents_history_button.xml
index 471f518..601c5ed 100644
--- a/packages/SystemUI/res/layout/recents_history_button.xml
+++ b/packages/SystemUI/res/layout/recents_history_button.xml
@@ -22,5 +22,9 @@
android:textSize="14sp"
android:textColor="#FFFFFF"
android:textAllCaps="true"
+ android:shadowColor="#99000000"
+ android:shadowDx="0"
+ android:shadowDy="2"
+ android:shadowRadius="5"
android:fontFamily="sans-serif-medium"
android:visibility="invisible" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index b9088ec..0cea7ae 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -61,14 +61,6 @@
/>
<ViewStub
- android:layout="@layout/notification_header"
- android:id="@+id/notification_header_stub"
- android:inflatedId="@+id/notification_header"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_header_height"
- />
-
- <ViewStub
android:layout="@layout/notification_guts"
android:id="@+id/notification_guts_stub"
android:inflatedId="@+id/notification_guts"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 1e18523..7006d43 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"skermvaspen"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"soek"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Kon nie <xliff:g id="APP">%s</xliff:g> begin nie."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Meer"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Geskiedenis"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Verdeel horisontaal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Verdeel vertikaal"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Verdeel gepasmaak"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Wys horlosiesekondes op die statusbalk. Sal batterylewe dalk beïnvloed."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Herrangskik Kitsinstellings"</string>
<string name="show_brightness" msgid="6613930842805942519">"Wys helderheid in Kitsinstellings"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Aktiveer oproep"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Aktiveer oproep deur die Oorsig-knoppie"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Aktiveer vinnige wissel"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Aktiveer beginuitteltyd terwyl daar opgeroep word"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Aktiveer volskerm-skermkiekies"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Aktiveer volskerm-skermkiekies in Oorsig"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimenteel"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Skakel Bluetooth aan?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Jy moet Bluetooth aanskakel om jou sleutelbord aan jou tablet te koppel."</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 8dae911..948c538 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ማያ ገጽ መሰካት"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ፈልግ"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>ን መጀመር አልተቻለም።"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"ተጨማሪ"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ታሪክ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"አግድም ክፈል"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ቁልቁል ክፈል"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"በብጁ ክፈል"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"የሰዓት ሰከንዶችን በሁኔታ አሞሌ ውስጥ አሳይ። በባትሪ ዕድሜ ላይ ተጽዕኖ ሊኖርው ይችል ይሆናል።"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ፈጣን ቅንብሮችን ዳግም ያደራጁ"</string>
<string name="show_brightness" msgid="6613930842805942519">"በፈጣን ቅንብሮች ውስጥ ብሩህነትን አሳይ"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"ገጽ መስራትን አንቃ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"በአጠቃላይ እይታ አዝራር በኩል ገጽ መስራትን አንቃ"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"ፈጣን ቅይይርን አንቃ"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"ገጽ እየሰራ ሳለ የማስጀመሪያ ጊዜ ማብቂያውን አንቃ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"የሙሉ ማያ ገጽ ቅጽበታዊ ገጽ እይታዎችን አንቃ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"በአጠቃላይ እይታ ውስጥ የሙሉ ማያ ገጽ ቅጽበታዊ ገጽ እይታዎችን አንቃ"</string>
<string name="experimental" msgid="6198182315536726162">"የሙከራ"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ብሉቱዝ ይብራ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"የቁልፍ ሰሌዳዎን ከእርስዎ ጡባዊ ጋር ለማገናኘት በመጀመሪያ ብሉቱዝን ማብራት አለብዎት።"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index b7d85b7..e272364 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -298,6 +298,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"تثبيت الشاشة"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"بحث"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"تعذر بدء <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"المزيد"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"السجلّ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"تقسيم أفقي"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"تقسيم رأسي"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"تقسيم مخصص"</string>
@@ -441,12 +443,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"عرض ثواني الساعة في شريط الحالة. قد يؤثر ذلك في عمر البطارية."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"إعادة ترتيب الإعدادات السريعة"</string>
<string name="show_brightness" msgid="6613930842805942519">"عرض السطوع في الإعدادات السريعة"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"تمكين الترحيل"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"تمكين الترحيل عبر زر \"نظرة عامة\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"تمكين التبديل السريع"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"تمكين مهلة الإطلاق أثناء الترحيل"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"تمكين اللقطات بملء الشاشة"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"تمكين اللقطات بملء الشاشة في النظرة العامة"</string>
<string name="experimental" msgid="6198182315536726162">"إعدادات تجريبية"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"تشغيل البلوتوث؟"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"لتوصيل لوحة المفاتيح بالجهاز اللوحي، يلزمك تشغيل بلوتوث أولاً."</string>
diff --git a/packages/SystemUI/res/values-az-rAZ/strings.xml b/packages/SystemUI/res/values-az-rAZ/strings.xml
index b872db3..7f2a6b07 100644
--- a/packages/SystemUI/res/values-az-rAZ/strings.xml
+++ b/packages/SystemUI/res/values-az-rAZ/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ekran sancağı"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"axtarış"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> başlana bilmir."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Daha çox"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Tarixçə"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Üfüqi Böl"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Şaquli Böl"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Fərdi Böl"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Saatın saniyəsini status panelində göstərin. Batareyaya təsir edə bilər."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Sürətli Ayarları yenidən tənzimləyin"</string>
<string name="show_brightness" msgid="6613930842805942519">"Sürətli ayarlarda parlaqlılığı göstərin"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Səhifə nömrələməni aktiv edin"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Baxış düyməsi vasitəsilə səhifələməni aktiv edin"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Sürətli keçidi aktiv edin"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Səhifə nömrələyərkən işə salma vaxtının bitməsini aktiv edin"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Tam ekran ani görüntülərini aktiv edin"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Tam ekran ani görüntülərini İcmalda aktiv edin"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth aktivləşsin?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Tabletinizlə klaviaturaya bağlanmaq üçün ilk olaraq Bluetooth\'u aktivləşdirməlisiniz."</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index fd5ddbf..6ed2adb 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"фиксиране на екрана"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"търсене"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> не можа да стартира."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Още"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"История"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Хоризонтално разделяне"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Вертикално разделяне"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Персонализирано разделяне"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Показване на секундите на часовника в лентата на състоянието. Може да се отрази на живота на батерията."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Пренареждане на бързите настройки"</string>
<string name="show_brightness" msgid="6613930842805942519">"Показване на яркостта от бързите настройки"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Активиране на разделянето на страници"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Активиране на разделянето на страници чрез бутона за общ преглед"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Активиране на бързото превключване"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Активиране на времето за изчакване при стартиране за разделянето на страници"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Активиране на екранните снимки на цял екран"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Активиране на екранните снимки на цял екран в режима на общ преглед"</string>
<string name="experimental" msgid="6198182315536726162">"Експериментални"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Да се включи ли Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"За да свържете клавиатурата с таблета си, първо трябва да включите Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-bn-rBD/strings.xml b/packages/SystemUI/res/values-bn-rBD/strings.xml
index 442b179..20e2c35 100644
--- a/packages/SystemUI/res/values-bn-rBD/strings.xml
+++ b/packages/SystemUI/res/values-bn-rBD/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"স্ক্রীন পিন করা"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"অনুসন্ধান"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> শুরু করা যায়নি৷"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"আরো"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ইতিহাস"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"অনুভূমিক স্প্লিট"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"উল্লম্ব স্প্লিট"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"কাস্টম স্প্লিট করুন"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"স্থিতি দন্ডে ঘড়ির সেকেন্ড দেখায়৷ ব্যাটারি লাইফকে প্রভাবিত করতে পারে৷"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"দ্রুত সেটিংসে পুনরায় সাজান"</string>
<string name="show_brightness" msgid="6613930842805942519">"দ্রুত সেটিংসে উজ্জ্বলতা দেখান"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"পেজিং সক্ষম করুন"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"\'এক নজরে\' বোতামের মাধ্যমে পেজিং সক্ষম করুন"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"\'দ্রুত টগল করা\'র ব্যবস্থাটি সক্ষম করুন"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"পেজিং এর সময় \'লঞ্চ সময় সমাপ্ত\' সক্ষম করুন"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"পূর্ণস্ক্রীন স্ক্রীনশট সক্ষম করুন"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"\'এক নজরে\'র মধ্যে পূর্ণস্ক্রীন স্ক্রীনশট সক্ষম করুন"</string>
<string name="experimental" msgid="6198182315536726162">"পরীক্ষামূলক"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth চালু করবেন?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"আপনার ট্যাবলেটের সাথে আপনার কীবোর্ড সংযুক্ত করতে, আপনাকে প্রথমে Bluetooth চালু করতে হবে।"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index f67b425..0bb4826 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixació de pantalla"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"cerca"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"No s\'ha pogut iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Més"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historial"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisió horitzontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisió vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisió personalitzada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra els segons del rellotge a la barra d\'estat. Això pot afectar la durada de la bateria."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganitza Configuració ràpida"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostra la brillantor a Configuració ràpida"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Activa la paginació"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Activa la paginació mitjançant el botó Visió general"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Activa el canvi ràpid"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Activa el temps d\'espera de llançament durant la paginació"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Activa les captures de pantalla completa"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Activa les captures de pantalla completa a Visió general"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vols activar el Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Per connectar el teclat amb la tauleta, primer has d\'activar el Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 08d833e..63a16ad 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"připnutí obrazovky"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"vyhledat"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Aplikaci <xliff:g id="APP">%s</xliff:g> nelze spustit."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Další"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historie"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Vodorovné rozdělení"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikální rozdělení"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Vlastní rozdělení"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Na stavovém řádku se bude zobrazovat sekundová ručička. Může být ovlivněna výdrž baterie."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Změnit uspořádání Rychlého nastavení"</string>
<string name="show_brightness" msgid="6613930842805942519">"Zobrazit jas v Rychlém nastavení"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Povolit stránkování"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Povolit stránkování pomocí tlačítka Přehled"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Povolit rychlé přepínání"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Povolit časový limit pro spuštění při stránkování"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Povolit snímky celé obrazovky"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Povolit snímky celé obrazovky v Přehledu"</string>
<string name="experimental" msgid="6198182315536726162">"Experimentální"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Zapnout Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Chcete-li klávesnici připojit k tabletu, nejdříve musíte zapnout Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 1fb1a51..eb8053d 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"bliv i app"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"søg"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> kunne ikke startes."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mere"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historik"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Opdel vandret"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Opdel lodret"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Opdel brugerdefineret"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Vis sekunder i statuslinjen. Dette kan påvirke batteriets levetid."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Omarranger Hurtige indstillinger"</string>
<string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i Hurtige indstillinger"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Aktivér sideopdeling"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Aktivér sideopdeling via knappen Oversigt"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Aktivér hurtigt skift"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Aktivér åbningstimeout ved sideinddeling"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Aktivér skærmbilleder i fuld størrelse"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Aktivér skærmbilleder i fuld størrelse i Oversigt"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentel"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vil du slå Bluetooth til?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Bluetooth skal være slået til, før du kan knytte dit tastatur til din tablet."</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index a9196d6..e156c92 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"Bildschirmfixierung"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"Suche"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> konnte nicht gestartet werden."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mehr"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Verlauf"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Geteilte Schaltfläche – horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Geteilte Schaltfläche – vertikal"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Geteilte Schaltfläche – benutzerdefiniert"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Uhrsekunden in der Statusleiste anzeigen. Kann sich auf die Akkulaufzeit auswirken."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Schnelleinstellungen neu anordnen"</string>
<string name="show_brightness" msgid="6613930842805942519">"Helligkeit in den Schnelleinstellungen anzeigen"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Paging aktivieren"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Paging über die Schaltfläche \"Übersicht\" aktivieren"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Schnelles Wechseln aktivieren"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Beim Paging Zeitlimit für Start aktivieren"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Vollbild-Screenshots aktivieren"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Vollbild-Screenshots in der Übersicht aktivieren"</string>
<string name="experimental" msgid="6198182315536726162">"Experimentell"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth aktivieren?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Zum Verbinden von Tastatur und Tablet muss Bluetooth aktiviert sein."</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index e982df6..b0dcc40 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"καρφίτσωμα οθόνης"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"αναζήτηση"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Δεν ήταν δυνατή η εκκίνηση της εφαρμογής <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Περισσότερα"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Ιστορικό"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Οριζόντιος διαχωρισμός"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Κάθετος διαχωρισμός"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Προσαρμοσμένος διαχωρισμός"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Εμφάνιση δευτερολέπτων ρολογιού στη γραμμή κατάστασης. Ενδέχεται να επηρεάσει τη διάρκεια ζωής της μπαταρίας."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Αναδιάταξη Γρήγορων ρυθμίσεων"</string>
<string name="show_brightness" msgid="6613930842805942519">"Εμφάνιση φωτεινότητας στις Γρήγορες ρυθμίσεις"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Ενεργοποίηση σελιδοποίησης"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Ενεργοποίηση σελιδοποίησης μέσω του κουμπιού \"Επισκόπηση\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Ενεργοποίηση γρήγορης εναλλαγής"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Ενεργοποίηση του χρονικού ορίου λήξης εκκίνησης κατά τη σελιδοποίηση"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Ενεργοποίηση στιγμιοτύπων πλήρους οθόνης"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Ενεργοποίηση των στιγμιοτύπων πλήρους οθόνης στην Προεπισκόπηση"</string>
<string name="experimental" msgid="6198182315536726162">"Σε πειραματικό στάδιο"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Ενεργοποίηση Bluetooth;"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Για να συνδέσετε το πληκτρολόγιο με το tablet σας, θα πρέπει πρώτα να ενεργοποιήσετε το Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 32c486f..84f1785 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"screen pinning"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"search"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Could not start <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"More"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"History"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Customised"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Show clock seconds in the status bar. May impact battery life."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
<string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Enable paging"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Enable paging via the Overview button"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Enable fast toggle"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Enable launch timeout while paging"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Enable fullscreen screenshots"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Enable fullscreen screenshots in Overview"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 32c486f..84f1785 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"screen pinning"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"search"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Could not start <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"More"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"History"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Customised"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Show clock seconds in the status bar. May impact battery life."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
<string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Enable paging"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Enable paging via the Overview button"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Enable fast toggle"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Enable launch timeout while paging"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Enable fullscreen screenshots"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Enable fullscreen screenshots in Overview"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 32c486f..84f1785 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"screen pinning"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"search"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Could not start <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"More"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"History"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Customised"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Show clock seconds in the status bar. May impact battery life."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
<string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Enable paging"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Enable paging via the Overview button"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Enable fast toggle"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Enable launch timeout while paging"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Enable fullscreen screenshots"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Enable fullscreen screenshots in Overview"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 27451ec6..c9af193 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"Fijar pantalla"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"buscar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"No se pudo iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Más"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historial"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"División horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"División vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"División personalizada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Muestra los segundos del reloj en la barra de estado. Puede afectar la duración de la batería."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar la Configuración rápida"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar el brillo en la Configuración rápida"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Habilitar paginación"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Habilita la paginación a través del botón Recientes"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Habilitar la activación rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Habilita el tiempo de espera de inicio durante la paginación"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Habilitar capturas de pantalla en pantalla completa"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Habilita las capturas de pantalla en pantalla completa en Recientes"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"¿Activar Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar el teclado con la tablet, primero debes activar Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 1a7fea5..51984bf 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fijación de pantalla"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"buscar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"No se ha podido iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Más"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historial"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"División horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"División vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"División personalizada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Muestra los segundos del reloj en la barra de estado. Puede afectar a la duración de la batería."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar Ajustes rápidos"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar brillo en Ajustes rápidos"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Habilitar paginación"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Habilitar paginación con el botón Visión general"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Habilitar activación rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Habilitar tiempo de espera de lanzamiento durante paginación"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Habilitar capturas de pantalla completa"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Habilitar capturas de pantalla completa en Visión general"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"¿Activar Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para poder conectar tu teclado a tu tablet, debes activar el Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml
index 1369252..b527b9f 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ekraanikuva kinnitamine"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"otsing"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Rakendust <xliff:g id="APP">%s</xliff:g> ei saanud käivitada."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Rohkem"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Ajalugu"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horisontaalne poolitamine"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikaalne poolitamine"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Kohandatud poolitamine"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Olekuribal kella sekundite kuvamine. See võib mõjutada aku kasutusaega."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Korralda kiirseaded ümber"</string>
<string name="show_brightness" msgid="6613930842805942519">"Kuva kiirseadetes heledus"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Luba sirvimine"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Saate lubada sirvimise nupuga Ülevaade"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Luba kiire vahetamine"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Luba sirvimisel käivitamise ajalõpp"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Luba täisekraanil ekraanipildid"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Saate lubada lehel Ülevaade täisekraanil ekraanipildid"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentaalne"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Kas lülitada Bluetooth sisse?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Klaviatuuri ühendamiseks tahvelarvutiga peate esmalt Bluetoothi sisse lülitama."</string>
diff --git a/packages/SystemUI/res/values-eu-rES/strings.xml b/packages/SystemUI/res/values-eu-rES/strings.xml
index 6a5212e..b7cfb2a2 100644
--- a/packages/SystemUI/res/values-eu-rES/strings.xml
+++ b/packages/SystemUI/res/values-eu-rES/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"pantaila-ainguratzea"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"bilatu"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Ezin izan da hasi <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Gehiago"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historia"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Banaketa horizontala"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Banaketa bertikala"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Banaketa pertsonalizatua"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Erakutsi erlojuko segundoak egoera-barran. Baliteke bateria gehiago erabiltzea."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Berrantolatu ezarpen bizkorrak"</string>
<string name="show_brightness" msgid="6613930842805942519">"Erakutsi distira Ezarpen bizkorretan"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Gaitu orriak pasatzeko aukera"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Gaitu ikuspegi orokorraren botoiaren bidez orriak pasatzeko aukera"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Gaitu bizkor aldatzeko aukera"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Gaitu orriak pasatu bitarteko abiarazteen denbora-muga"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Gaitu pantaila osoko pantaila-argazkiak"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Gaitu pantaila osoko pantaila-argazkiak ikuspegi orokorrean"</string>
<string name="experimental" msgid="6198182315536726162">"Esperimentala"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth eginbidea aktibatu nahi duzu?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Teklatua tabletara konektatzeko, Bluetooth eginbidea aktibatu behar duzu."</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 6bfef66..b3d8216 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"پین کردن صفحه"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"جستجو"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> شروع نشد."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"بیشتر"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"سابقه"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"تقسیم افقی"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"تقسیم عمودی"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"سفارشی کردن تقسیم"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ثانیههای ساعت را در نوار وضعیت نشان میدهد. ممکن است بر ماندگاری باتری تأثیر بگذارد."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ترتیب مجدد در تنظیمات سریع"</string>
<string name="show_brightness" msgid="6613930842805942519">"نمایش روشنایی در تنظیمات سریع"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"فعال کردن صفحهبندی"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"فعال کردن صفحهبندی از طریق دکمه «نمای کلی»"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"فعال کردن جابهجایی سریع"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"فعال کردن مهلت زمانی راهاندازی هنگام صفحهبندی"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"فعال کردن عکسهای صفحهنمایش تمامصفحه"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"فعال کردن عکسهای صفحهنمایش تمامصفحه در مرور کلی"</string>
<string name="experimental" msgid="6198182315536726162">"آزمایشی"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"بلوتوث روشن شود؟"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"برای مرتبط کردن صفحهکلید با رایانه لوحی، ابتدا باید بلوتوث را روشن کنید."</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 4d0debe..235eef0 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"näytön kiinnitys"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"haku"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Sovelluksen <xliff:g id="APP">%s</xliff:g> käynnistäminen epäonnistui."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Lisää"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historia"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Vaakasuuntainen jako"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Pystysuuntainen jako"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Muokattu jako"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Näytä sekunnit tilapalkin kellossa. Tämä voi vaikuttaa akun kestoon."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Järjestä pika-asetukset uudelleen"</string>
<string name="show_brightness" msgid="6613930842805942519">"Näytä kirkkaus pika-asetuksissa"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Ota sivutus käyttöön"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Ottaa sivutuksen käyttöön Yleiskatsaus-painikkeen avulla."</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Ota käyttöön nopea päälle/pois-toiminto."</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Ottaa käynnistyksen aikakatkaisun käyttöön sivutuksen aikana."</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Salli koko ruudun kuvakaappaukset"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Sallii koko ruudun kuvakaappaukset Viimeisimmät-näkymässä."</string>
<string name="experimental" msgid="6198182315536726162">"Kokeellinen"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Otetaanko Bluetooth käyttöön?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Jotta voit yhdistää näppäimistön tablettiisi, sinun on ensin otettava Bluetooth käyttöön."</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 24a033f..3493067 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"épinglage d\'écran"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"rechercher"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Impossible de lancer <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Plus"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historique"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Séparation horizontale"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Séparation verticale"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Séparation personnalisée"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Afficher les secondes sur l\'horloge dans la barre d\'état. Cela peut réduire l\'autonomie de la pile."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Réorganiser les paramètres rapides"</string>
<string name="show_brightness" msgid="6613930842805942519">"Afficher la luminosité dans les paramètres rapides"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Activer la mise en page"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Activer la mise en page à l\'aide du bouton Aperçu"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Activer le basculement rapide"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Activer le délai avant expiration du lancement pendant la mise en page"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Activer les saisies d\'écran plein écran"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Activer les saisies d\'écran plein écran dans Aperçu"</string>
<string name="experimental" msgid="6198182315536726162">"Fonctions expérimentales"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Activer Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Pour connecter votre clavier à votre tablette, vous devez d\'abord activer la connectivité Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index d652251..ebed6e9 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"épinglage d\'écran"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"rechercher"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Impossible de lancer <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Plus"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historique"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Séparation horizontale"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Séparation verticale"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Séparation personnalisée"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Afficher les secondes dans la barre d\'état. Cela risque de réduire l\'autonomie de la batterie."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Réorganiser la fenêtre de configuration rapide"</string>
<string name="show_brightness" msgid="6613930842805942519">"Afficher la luminosité dans fenêtre de configuration rapide"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Activer la mise en page"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Activer la mise en page via le bouton Aperçu"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Activer le basculement rapide"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Activer le délai avant expiration du lancement pendant la mise en page"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Activer les captures d\'écran en plein écran"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Activer les captures d\'écran en plein écran en mode Aperçu"</string>
<string name="experimental" msgid="6198182315536726162">"Paramètres expérimentaux"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Activer le Bluetooth ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Pour connecter un clavier à votre tablette, vous devez avoir activé le Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-gl-rES/strings.xml b/packages/SystemUI/res/values-gl-rES/strings.xml
index 3a8b986..a9648b9 100644
--- a/packages/SystemUI/res/values-gl-rES/strings.xml
+++ b/packages/SystemUI/res/values-gl-rES/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixación de pantalla"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"buscar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Non foi posible iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Máis"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historial"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Dividir en horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Dividir en vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Dividir de xeito personalizado"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra os segundos do reloxo na barra de estado. Pode influír na duración da batería."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar Configuración rápida"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar brillo en Configuración rápida"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Activar paxinación"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Activar paxinación a través do botón Visión xeral"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Activar alternancia rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Activa o tempo de espera do lanzamento durante a paxinación"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Activa as capturas de pantalla en pantalla completa"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Activa as capturas de pantalla en pantalla completa en Visión xeral"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Queres activar o Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teu teclado co tablet, primeiro tes que activar o Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-gu-rIN/strings.xml b/packages/SystemUI/res/values-gu-rIN/strings.xml
index 86c1502..804a940 100644
--- a/packages/SystemUI/res/values-gu-rIN/strings.xml
+++ b/packages/SystemUI/res/values-gu-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"સ્ક્રીન પિનિંગ"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"શોધ"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> પ્રારંભ કરી શકાયું નથી."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"વધુ"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ઇતિહાસ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"આડું વિભક્ત કરો"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ઊભું વિભક્ત કરો"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"કસ્ટમ વિભક્ત કરો"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ઘડિયાળ સેકન્ડ સ્થિતિ બારમાં બતાવો. બૅટરીની આવરદા પર અસર કરી શકે છે."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ઝડપી સેટિંગ્સને ફરીથી ગોઠવો"</string>
<string name="show_brightness" msgid="6613930842805942519">"ઝડપી સેટિંગ્સમાં તેજ બતાવો"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"પેજિંગ સક્ષમ કરો"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"વિહંગાવલોકન બટન મારફતે પેજિંગ સક્ષમ કરો"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"ઝડપી ટૉગલ સક્ષમ કરો"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"પેજિંગ વખતે લોંચ સમયસમાપ્તિ સક્ષમ કરો"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"પૂર્ણસ્ક્રીન સ્ક્રીનશોટ્સ સક્ષમ કરો"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"વિહંગાવલોકનમાં પૂર્ણસ્ક્રીન સ્ક્રીનશોટ્સ સક્ષમ કરો"</string>
<string name="experimental" msgid="6198182315536726162">"પ્રાયોગિક"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ચાલુ કરવુ છે?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"તમારા ટેબ્લેટ સાથે કીબોર્ડ કનેક્ટ કરવા માટે, તમારે પહેલાં Bluetooth ચાલુ કરવાની જરૂર પડશે."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 834d025..354de04 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"स्क्रीन पिन करना"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"खोज"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> प्रारंभ नहीं किया जा सका."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"अधिक"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"इतिहास"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"क्षैतिज रूप से विभाजित करें"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"लम्बवत रूप से विभाजित करें"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"कस्टम रूप से विभाजित करें"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"स्थिति बार में घड़ी के सेकंड दिखाएं. इससे बैटरी के जीवनकाल पर प्रभाव पड़ सकता है."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"त्वरित सेटिंग को पुन: व्यवस्थित करें"</string>
<string name="show_brightness" msgid="6613930842805942519">"त्वरित सेटिंग में स्क्रीन की रोशनी दिखाएं"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"पेजिंग सक्षम करें"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"अवलोकन बटन के माध्यम से पेजिंग सक्षम करें"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"तेज़ टॉगल सक्षम करें"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"पेजिंग के दौरान लॉन्च समयबाह्य सक्षम करें"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"पूर्णस्क्रीन स्क्रीनशॉट सक्षम करें"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"अवलोकन में पूर्ण स्क्रीनशॉट सक्षम करें"</string>
<string name="experimental" msgid="6198182315536726162">"प्रयोगात्मक"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लूटूथ चालू करें?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"अपने कीबोर्ड को अपने टैबलेट से कनेक्ट करने के लिए, आपको पहले ब्लूटूथ चालू करना होगा."</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index c929d9c..b35360e 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -295,6 +295,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"prikvačivanje zaslona"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"pretraži"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Aplikacija <xliff:g id="APP">%s</xliff:g> nije pokrenuta."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Više"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Povijest"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Podijeli vodoravno"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Podijeli okomito"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Podijeli prilagođeno"</string>
@@ -438,12 +440,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Prikazuju se sekunde na satu na traci statusa. Može utjecati na trajanje baterije."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Promijeni raspored Brzih postavki"</string>
<string name="show_brightness" msgid="6613930842805942519">"Prikaži svjetlinu u Brzim postavkama"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Omogući pregledavanje stranica"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Omogućivanje pregledavanja stranica gumbom Pregled"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Omogući brzo prebacivanje"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Omogućivanje vremenskog ograničenja pokretanja tijekom pregledavanja stranica"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Omogući snimanje cijelog zaslona"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Omogući snimanje cijelog zaslona u Pregledu"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentalno"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Želite li uključiti Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Da biste povezali tipkovnicu s tabletom, morate uključiti Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 145d904..7956a21 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"képernyő rögzítése"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"keresés"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Nem lehet elindítani a következőt: <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Továbbiak"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Előzmények"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Osztott vízszintes"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Osztott függőleges"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Osztott egyéni"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Másodpercek megjelenítése az állapotsor óráján. Ez hatással lehet az akkumulátor üzemidejére."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Gyorsbeállítások átrendezése"</string>
<string name="show_brightness" msgid="6613930842805942519">"Fényerő megjelenítése a gyorsbeállításokban"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Lapozás engedélyezése"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Lapozás engedélyezése az Áttekintés gombbal"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Gyors váltás engedélyezése"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Indítási időtúllépés engedélyezése lapozás közben"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Teljes képernyős képernyőképek engedélyezése"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Teljes képernyős képernyőképek engedélyezése Áttekintés módban"</string>
<string name="experimental" msgid="6198182315536726162">"Kísérleti"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Engedélyezi a Bluetooth-kapcsolatot?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Ha a billentyűzetet csatlakoztatni szeretné táblagépéhez, először engedélyeznie kell a Bluetooth-kapcsolatot."</string>
diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml
index 5a6ec62..de70fec 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"էկրանի ամրակցում"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"որոնել"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Հնարավոր չէ գործարկել <xliff:g id="APP">%s</xliff:g>-ը:"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Ավելին"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Պատմություն"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Հորիզոնական տրոհում"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Ուղղահայաց տրոհում"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Հատուկ տրոհում"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Ցույց տալ ժամացույցի վայրկյանները կարգավիճակի տողում: Կարող է ազդել մարտկոցի աշխատանքի ժամանակի վրա:"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Վերադասավորել Արագ կարգավորումները"</string>
<string name="show_brightness" msgid="6613930842805942519">"Ցույց տալ պայծառությունն Արագ կարգավորումներում"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Միացնել թերթումը"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Միացնել Համատեսք կոճակի միջոցով թերթումը"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Միացնել արագ փոխարկումը"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Միացնել գործարկման ժամանակի սպառումը թերթելու ժամանակ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Միացնել էկրանի պատկերների լիաէկրան ցուցադրումը"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Միացնել էկրանի պատկերների լիաէկրան ցուցադրումը Համատեսքում"</string>
<string name="experimental" msgid="6198182315536726162">"Փորձնական"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Միացնե՞լ Bluetooth-ը:"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Ստեղնաշարը ձեր պլանշետին միացնելու համար նախ անհրաժեշտ է միացնել Bluetooth-ը:"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index cb2fe53..51ddcc0 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"pin ke layar"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"telusuri"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Tidak dapat memulai <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Lainnya"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Riwayat"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Pisahkan Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Pisahkan Vertikal"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Pisahkan Khusus"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Tampilkan detik jam di bilah status. Dapat memengaruhi masa pakai baterai."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Atur Ulang Setelan Cepat"</string>
<string name="show_brightness" msgid="6613930842805942519">"Tampilkan kecerahan di Setelan Cepat"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Aktifkan tampilan laman"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Mengaktifkan tampilan laman melalui tombol Ringkasan"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Mengaktifkan pengalih cepat"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Aktifkan waktu tunggu peluncuran saat menampilkan laman"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Aktifkan tangkapan layar untuk layar penuh"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Aktifkan tangkapan layar untuk layar penuh di Ringkasan"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Aktifkan Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Untuk menghubungkan keyboard dengan tablet, terlebih dahulu aktifkan Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-is-rIS/strings.xml b/packages/SystemUI/res/values-is-rIS/strings.xml
index 63a4fe8..20d8693 100644
--- a/packages/SystemUI/res/values-is-rIS/strings.xml
+++ b/packages/SystemUI/res/values-is-rIS/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"skjáfesting"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"leita"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Ekki var hægt að ræsa <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Meira"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Ferill"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Lárétt skipting"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Lóðrétt skipting"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Sérsniðin skipting"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Sýna sekúndur á klukku í stöðustikunni. Getur haft áhrif á endingu rafhlöðu."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Endurraða flýtistillingum"</string>
<string name="show_brightness" msgid="6613930842805942519">"Sýna birtustig í flýtistillingum"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Kveikja á síðuskoðun"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Virkja síðuskoðun með yfirlitshnappinum"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Kveikja á flýtiskiptingum"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Kveikja á tímamörkum ræsingar þegar flett er"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Kveikja á skjámyndum sem fylla upp í skjáinn"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Kveikja á skjámyndum sem fylla upp í skjáinn í yfirliti"</string>
<string name="experimental" msgid="6198182315536726162">"Tilraunastillingar"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Kveikja á Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Til að geta tengt lyklaborðið við spjaldtölvuna þarftu fyrst að kveikja á Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 24bb49f3..87267995 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"blocco su schermo"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"cerca"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Impossibile avviare <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Altro"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Cronologia"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisione in orizzontale"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisione in verticale"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisione personalizzata"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra i secondi nell\'orologio nella barra di stato. Ciò potrebbe ridurre la durata della carica della batteria."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Riorganizza Impostazioni rapide"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostra luminosità in Impostazioni rapide"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Attiva paging"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Attiva il paging tramite il pulsante Panoramica"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Abilita attivazione/disattivazione veloce"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Attiva timeout lancio durante il paging"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Attiva screenshot a schermo intero"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Attiva screenshot a schermo intero in Panoramica"</string>
<string name="experimental" msgid="6198182315536726162">"Sperimentali"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Attivare il Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Per connettere la tastiera al tablet, devi prima attivare il Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 54a9ffe..a28dc2a 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"הצמדת מסך"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"חפש"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"לא ניתן היה להפעיל את <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"עוד"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"היסטוריה"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"פיצול אופקי"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"פיצול אנכי"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"פיצול מותאם אישית"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"הצג שניות בשעון בשורת הסטטוס. פעולה זו עשויה להשפיע על אורך חיי הסוללה."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"סידור מחדש של הגדרות מהירות"</string>
<string name="show_brightness" msgid="6613930842805942519">"הצג בהירות בהגדרות מהירות"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"אפשר דפדוף"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"הפעל דפדוף באמצעות לחצן הסקירה"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"הפעל החלפת מצב מהירה"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"אפשר זמן קצוב להפעלה במהלך הדפדוף"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"אפשר צילומי מסך במסך מלא"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"אפשר צילומי מסך במסך מלא ב\'סקירה\'"</string>
<string name="experimental" msgid="6198182315536726162">"ניסיוניות"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"האם להפעיל את ה-Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"כדי לחבר את המקלדת לטאבלט, תחילה עליך להפעיל את ה-Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index b0a1974..13217a0 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"画面固定"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"検索"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>を開始できません。"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"もっと見る"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"履歴"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"横に分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"縦に分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"分割(カスタム)"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ステータスバーに時計の秒を表示します。電池使用量に影響する可能性があります。"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"クイック設定を並べ替え"</string>
<string name="show_brightness" msgid="6613930842805942519">"クイック設定に明るさ調整バーを表示する"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"ページ切り替えを有効にする"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"[概要] ボタンによるページ切り替えを有効にします"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"高速切り替えを有効にする"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"ページ切り替え中の起動タイムアウトを有効にします"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"全画面スクリーンショットを有効にする"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"[概要] で全画面スクリーンショットを有効にします"</string>
<string name="experimental" msgid="6198182315536726162">"試験運用版"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"BluetoothをONにしますか?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"タブレットでキーボードに接続するには、最初にBluetoothをONにする必要があります。"</string>
diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml
index ce8b417..c4e84a8 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ეკრანზე ჩამაგრება"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ძიება"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>-ის გამოძახება ვერ მოხერხდა."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"მეტი"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ისტორია"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ჰორიზონტალური გაყოფა"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ვერტიკალური გაყოფა"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ინდივიდუალური გაყობა"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"საათის წამების ჩვენება სტატუსის ზოლში. შეიძლება გავლენა იქონიოს ბატარეაზე."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"სწრაფი პარამეტრების გადაწყობა"</string>
<string name="show_brightness" msgid="6613930842805942519">"სიკაშკაშის ჩვენება სწრაფ პარამეტრებში"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"გვერდების გადაფურცვლის ჩართვა"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"მიმოხილვის ღილაკის მეშვეობით გვერდების გადაფურცვლის ჩართვა"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"სწრაფი გადართვის ჩართვა"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"გვერდების გადაფურცვლისას გაშვების დროის ლიმიტის ჩართვა"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"სრულეკრანიანი ეკრანის ანაბეჭდების ჩართვა"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"მიმოხილვაში სრულეკრანიანი ეკრანის ანაბეჭდების ჩართვა"</string>
<string name="experimental" msgid="6198182315536726162">"ექსპერიმენტული"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"გსურთ Bluetooth-ის ჩართვა?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"კლავიატურის ტაბლეტთან დასაკავშირებლად, ჯერ უნდა ჩართოთ Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-kk-rKZ/strings.xml b/packages/SystemUI/res/values-kk-rKZ/strings.xml
index 586b1bf9..2133170 100644
--- a/packages/SystemUI/res/values-kk-rKZ/strings.xml
+++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"экранды бекіту"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"іздеу"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> іске қосу мүмкін болмады."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Қосымша"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Тарих"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Бөлінген көлденең"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Бөлінген тік"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Бөлінген теңшелетін"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Күйін көрсету жолағында сағат секундтарын көрсету. Батареяның қызмет көрсету мерзіміне әсер етуі мүмкін."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Жылдам параметрлерді қайта реттеу"</string>
<string name="show_brightness" msgid="6613930842805942519">"Жылдам параметрлерде жарықтықты көрсету"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Беттерді аудару"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"\"Шолу\" түймесі арқылы беттерді аударуды қосу"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Тез ауыстырып қосуды қосу"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Беттерді аудару кезінде іске қосуды күту уақытын қосу"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Толық экрандық скриншоттарды қосу"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"\"Шолу\" ішінде толық экрандық скриншоттарды қосу"</string>
<string name="experimental" msgid="6198182315536726162">"Эксперименттік"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth функциясын қосу керек пе?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Пернетақтаны планшетке қосу үшін алдымен Bluetooth функциясын қосу керек."</string>
diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml
index f0f0d5f..6fbb325 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ការភ្ជាប់អេក្រង់"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ស្វែងរក"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"មិនអាចចាប់ផ្ដើម <xliff:g id="APP">%s</xliff:g> ទេ។"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"ច្រើនទៀត"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ប្រវត្តិ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"បំបែកផ្តេក"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"បំបែកបញ្ឈរ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"បំបែកផ្ទាល់ខ្លួន"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"បង្ហាញវិនាទីនៅលើរបារស្ថានភាពអាចនឹងប៉ះពាល់ដល់ថាមពលថ្ម។"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"រៀបចំការកំណត់រហ័សឡើងវិញ"</string>
<string name="show_brightness" msgid="6613930842805942519">"បង្ហាញកម្រិតពន្លឺនៅក្នុងការកំណត់រហ័ស"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"បើកដំណើរការចុះទំព័រ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"បើកដំណើការចុះទំព័រតាមរយៈប៊ូតុងទិដ្ឋភាព"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"បើកដំណើរការបិទ/បើករហ័ស"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"បើកដំណើរការអស់ពេលចាប់ផ្តើមខណៈពេលចុះទំព័រ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"បើកដំណើរការរូបថតអេក្រង់ពេញអេក្រង់"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"បើកដំណើរការរូបថតអេក្រង់ពេញអេក្រង់នៅក្នុងទិដ្ឋភាព"</string>
<string name="experimental" msgid="6198182315536726162">"ពិសោធន៍"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"បើកប៊្លូធូសឬ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ដើម្បីភ្ជាប់ក្តារចុចរបស់អ្នកជាមួយនឹងថេប្លេតរបស់អ្នក អ្នកត្រូវតែបើកប៊្លូធូសជាមុនសិន។"</string>
diff --git a/packages/SystemUI/res/values-kn-rIN/strings.xml b/packages/SystemUI/res/values-kn-rIN/strings.xml
index 9dec425..c8fc91d 100644
--- a/packages/SystemUI/res/values-kn-rIN/strings.xml
+++ b/packages/SystemUI/res/values-kn-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ಸ್ಕ್ರೀನ್ ಪಿನ್ನಿಂಗ್"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ಹುಡುಕಾಟ"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> ಪ್ರಾರಂಭಿಸಲು ಸಾದ್ಯವಿಲ್ಲ."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"ಇನ್ನಷ್ಟು"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ಇತಿಹಾಸ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ಅಡ್ಡಲಾಗಿ ವಿಭಜಿಸಿದ"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ಲಂಬವಾಗಿ ವಿಭಜಿಸಿದ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ಕಸ್ಟಮ್ ವಿಭಜಿಸಿದ"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ಸ್ಥಿತಿ ಪಟ್ಟಿಯಲ್ಲಿ ಗಡಿಯಾರ ಸೆಕೆಂಡುಗಳನ್ನು ತೋರಿಸು. ಇದಕ್ಕೆ ಬ್ಯಾಟರಿ ಬಾಳಿಕೆಯು ಪರಿಣಾಮಬೀರಬಹುದು."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಮರುಹೊಂದಿಸಿ"</string>
<string name="show_brightness" msgid="6613930842805942519">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಪ್ರಖರತೆಯನ್ನು ತೋರಿಸಿ"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"ಪೇಜಿಂಗ್ ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"ಸಮಗ್ರ ನೋಟದ ಬಟನ್ ಮೂಲಕ ಪೇಜಿಂಗ್ ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"ವೇಗವಾಗಿ ಟಾಗಲ್ ಮಾಡುವಿಕೆ ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"ಪೇಜಿಂಗ್ ಸಂದರ್ಭದಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆ ಕಾಲಾವಧಿಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"ಪೂರ್ಣ ಪರದೆ ಸ್ಕ್ರೀನ್ಶಾಟ್ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"ಸಮಗ್ರ ನೋಟದಲ್ಲಿ ಪೂರ್ಣ ಪರದೆ ಸ್ಕ್ರೀನ್ಶಾಟ್ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="experimental" msgid="6198182315536726162">"ಪ್ರಾಯೋಗಿಕ"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ಬ್ಲೂಟೂತ್ ಆನ್ ಮಾಡುವುದೇ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ನಿಮ್ಮ ಕೀಬೋರ್ಡ್ ಅನ್ನು ಟ್ಯಾಬ್ಲೆಟ್ಗೆ ಸಂಪರ್ಕಿಸಲು, ನೀವು ಮೊದಲು ಬ್ಲೂಟೂತ್ ಆನ್ ಮಾಡಬೇಕಾಗುತ್ತದೆ."</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 126d84a8..1296d21 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"화면 고정"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"검색"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>을(를) 시작할 수 없습니다."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"더보기"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"기록"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"수평 분할"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"수직 분할"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"맞춤 분할"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"상태 표시줄에 시계 초 단위를 표시합니다. 배터리 수명에 영향을 줄 수도 있습니다."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"빠른 설정 재정렬"</string>
<string name="show_brightness" msgid="6613930842805942519">"빠른 설정에서 밝기 표시"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"페이징을 사용 설정합니다."</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"최근 사용 버튼을 통해 페이징 사용"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"빠른 전환 사용"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"페이징하는 동안 실행 제한시간을 사용 설정합니다."</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"전체 화면 스크린샷을 사용 설정합니다."</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"최근 사용에서 전체 화면 스크린샷을 사용 설정합니다."</string>
<string name="experimental" msgid="6198182315536726162">"베타"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"블루투스를 켜시겠습니까?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"키보드를 태블릿에 연결하려면 먼저 블루투스를 켜야 합니다."</string>
diff --git a/packages/SystemUI/res/values-ky-rKG/strings.xml b/packages/SystemUI/res/values-ky-rKG/strings.xml
index 1be6f05..ead55c7 100644
--- a/packages/SystemUI/res/values-ky-rKG/strings.xml
+++ b/packages/SystemUI/res/values-ky-rKG/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"экран кадоо"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"издөө"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> баштай алган жок."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Дагы"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Таржымал"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Туурасынан бөлүү"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Тигинен бөлүү"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Ыңгайлаштырылган бөлүү"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Абал тилкесинен сааттын секунддары көрсөтүлсүн. Батареянын кубаты көбүрөөк сарпталышы мүмкүн."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Ыкчам жөндөөлөрдү кайра коюу"</string>
<string name="show_brightness" msgid="6613930842805942519">"Ыкчам жөндөөлөрдөн жарык деңгээлин көрсөтүү"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Барактоону иштетүү"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Сереп баскычы менен барактоону иштетүү"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Тез которгучту иштетүү"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Барактап жатканда таймаутту иштетүү"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Толук экран скриншотторун иштетүү"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Толук экран скриншотторун Серепте иштетүү"</string>
<string name="experimental" msgid="6198182315536726162">"Сынамык"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth күйгүзүлсүнбү?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Баскычтобуңузду планшетиңизге туташтыруу үчүн, адегенде Bluetooth\'ту күйгүзүшүңүз керек."</string>
diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml
index 2fac9b9..c0689c3 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ການປັກໝຸດໜ້າຈໍ"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ຊອກຫາ"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"ບໍ່ສາມາດເລີ່ມ <xliff:g id="APP">%s</xliff:g> ໄດ້."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"ເພີ່ມເຕີມ"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ປະຫວັດ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ການແຍກລວງຂວາງ"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ການແຍກລວງຕັ້ງ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ການແຍກກຳນົດເອງ"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ສະແດງວິນາທີໂມງຢູ່ໃນແຖບສະຖານະ. ອາດຈະມີຜົນກະທົບຕໍ່ອາຍຸແບັດເຕີຣີ."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ຈັດວາງການຕັ້ງຄ່າດ່ວນຄືນໃໝ່"</string>
<string name="show_brightness" msgid="6613930842805942519">"ສະແດງຄວາມແຈ້ງຢູ່ໃນການຕັ້ງຄ່າດ່ວນ"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"ເປີດໃຊ້ງານການແບ່ງໜ້າ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"ເປີດນຳໃຊ້ການແບ່ງໜ້າຜ່ານປຸ່ມພາບລວມ"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"ເປີດນຳໃຊ້ການສັບປ່ຽນໄວ"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"ເປີດໃຊ້ງານການໝົດເວລາການເປີດໃຊ້ ໃນຂະນະທີ່ແບ່ງໜ້າ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"ເປີດໃຊ້ງານພາບໜ້າຈໍແບບເຕັມຈໍ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"ເປີດໃຊ້ງານພາບໜ້າຈໍແບບເຕັມຈໍໃນໂໝດພາບລວມ"</string>
<string name="experimental" msgid="6198182315536726162">"ຍັງຢູ່ໃນການທົດລອງ"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ເປີດໃຊ້ Bluetooth ບໍ່?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ເພື່ອເຊື່ອມຕໍ່ແປ້ນພິມຂອງທ່ານກັບແທັບເລັດຂອງທ່ານ, ກ່ອນອື່ນໝົດທ່ານຕ້ອງເປີດ Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 1533ae8d..4425d31 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ekrano prisegimas"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"paieška"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Nepavyko paleisti <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Daugiau"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Istorija"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horizontalus skaidymas"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikalus skaidymas"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Tinkintas skaidymas"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Rodyti laikrodžio sekundes būsenos juostoje. Tai gali paveikti akumuliatoriaus naudojimo laiką."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Pertvarkyti sparčiuosius nustatymus"</string>
<string name="show_brightness" msgid="6613930842805942519">"Rodyti skaistį sparčiuosiuose nustatymuose"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Įgalinti puslapių kaitą"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Įgalinti puslapių kaitą apžvalgos mygtuku"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Įgalinti greitą perjungimą"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Įgalinti paleidimo skirtąjį laiką keičiant puslapius"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Įgalinti viso ekrano kopijas"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Įgalinti viso ekrano kopijas apžvalgoje"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentinė versija"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Įjungti „Bluetooth“?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Kad galėtumėte prijungti klaviatūrą prie planšetinio kompiuterio, pirmiausia turite įjungti „Bluetooth“."</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 3479320..eca57a6 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -295,6 +295,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"Piespraust ekrānu"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"Meklēt"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Nevarēja palaist lietotni <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Vairāk"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Vēsture"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horizontāls dalījums"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikāls dalījums"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Pielāgots dalījums"</string>
@@ -438,12 +440,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Statusa joslā rādīt pulksteņa sekundes. Var ietekmēt akumulatora darbības laiku."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Pārkārtot ātros iestatījumus"</string>
<string name="show_brightness" msgid="6613930842805942519">"Rādīt spilgtumu ātrajos iestatījumos"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Iespējot lapošanu"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Iespējot lapošanu, izmantojot pogu Pārskats"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Iespējot ātro pārslēgšanu"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Iespējot palaišanas noildzi lapošanas laikā"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Iespējot pilnekrāna ekrānuzņēmumus"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Iespējot pilnekrāna ekrānuzņēmumus sadaļā Pārskats"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentāli"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vai ieslēgt Bluetooth savienojumu?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Lai pievienotu tastatūru planšetdatoram, vispirms ir jāieslēdz Bluetooth savienojums."</string>
diff --git a/packages/SystemUI/res/values-mk-rMK/strings.xml b/packages/SystemUI/res/values-mk-rMK/strings.xml
index 277a519..038c669 100644
--- a/packages/SystemUI/res/values-mk-rMK/strings.xml
+++ b/packages/SystemUI/res/values-mk-rMK/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"прикачување екран"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"пребарај"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> не може да се вклучи."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Повеќе"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Историја"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Раздели хоризонтално"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Раздели вертикално"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Раздели прилагодено"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Прикажи ги секундите на часовникот на статусната лента. Може да влијае на траењето на батеријата."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Преуредете ги Брзи поставки"</string>
<string name="show_brightness" msgid="6613930842805942519">"Прикажете ја осветленоста во Брзи поставки"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Овозможете прелистување страници"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Овозможете прелистување страници со копчето Краток преглед"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Овозможете брзо префрлање"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Овозможете истек на време при стартување додека прелистувате страници"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Овозможете слики од цел екран"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Овозможете слики од цел екран во Преглед"</string>
<string name="experimental" msgid="6198182315536726162">"Експериментално"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Да се вклучи Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"За да ја поврзете тастатурата со таблетот, најпрво треба да вклучите Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ml-rIN/strings.xml b/packages/SystemUI/res/values-ml-rIN/strings.xml
index a448d9b..f4964d0 100644
--- a/packages/SystemUI/res/values-ml-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ml-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"സ്ക്രീൻ പിൻ ചെയ്യൽ"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"തിരയുക"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> ആരംഭിക്കാനായില്ല."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"കൂടുതൽ"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ചരിത്രം"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"തിരശ്ചീനമായി വേർതിരിക്കുക"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ലംബമായി വേർതിരിക്കുക"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ഇഷ്ടാനുസൃതമായി വേർതിരിക്കുക"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"സ്റ്റാറ്റസ് ബാറിൽ ക്ലോക്ക് സെക്കൻഡ് കാണിക്കുന്നത് ബാറ്ററിയുടെ ലൈഫിനെ ബാധിക്കാം."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ദ്രുത ക്രമീകരണം പുനഃസജ്ജീകരിക്കുക"</string>
<string name="show_brightness" msgid="6613930842805942519">"ദ്രുത ക്രമീകരണത്തിൽ തെളിച്ചം കാണിക്കുക"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"പേജിംഗ് പ്രവർത്തനക്ഷമമാക്കുക"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"ചുരുക്കവിവരണ ബട്ടൺ വഴി പേജിംഗ് പ്രവർത്തനക്ഷമമാക്കുക"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"അതിവേഗ ടോഗിൾ പ്രവർത്തനക്ഷമമാക്കുക"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"പേജിംഗിനിടെ ലോഞ്ച് ടൈമൗട്ട് പ്രവർത്തനക്ഷമമാക്കുക"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"പൂർണ്ണസ്ക്രീൻ സ്ക്രീൻഷോട്ടുകൾ പ്രവർത്തനക്ഷമമാക്കുക"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"ചുരുക്കവിവരണത്തിൽ പൂർണ്ണസ്ക്രീൻ സ്ക്രീൻഷോട്ടുകൾ പ്രവർത്തനക്ഷമമാക്കുക"</string>
<string name="experimental" msgid="6198182315536726162">"പരീക്ഷണാത്മകം!"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ഓണാക്കണോ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"നിങ്ങളുടെ ടാബ്ലെറ്റുമായി കീബോർഡ് കണക്റ്റുചെയ്യുന്നതിന്, ആദ്യം Bluetooth ഓണാക്കേണ്ടതുണ്ട്."</string>
diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml
index 8dcb6d98..122068c 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -292,6 +292,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"дэлгэц тогтоох"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"хайх"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>-г эхлүүлж чадсангүй."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Илүү"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Түүх"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Хэвтээ чиглэлд хуваах"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Босоо чиглэлд хуваах"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Хүссэн хэлбэрээр хуваах"</string>
@@ -435,12 +437,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Статус талбарт цагийн секундыг харуулах. Энэ нь тэжээлийн цэнэгт нөлөөлж болно."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Түргэн тохиргоог дахин засварлах"</string>
<string name="show_brightness" msgid="6613930842805942519">"Түргэн тохиргоонд гэрэлтүүлэг харах"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Өгөгдөл хадгалах/сэргээхийг идэвхжүүлэх"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Тойм товчлуурыг ашиглан өгөгдөл хадгалах/сэргээхийг идэвхжүүлэх"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Түргэн унтраах/асаахыг идэвхжүүлэх"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Өгөгдөл хадгалах/сэргээх явцад завсарлагыг эхлүүлэх"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Бүтэн дэлгэцийн дэлгэцийн агшинг идэвхжүүлэх"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Тойм хэсэгт бүтэн дэлгэцийн \"Дэлгэцийн агшинг\" идэвхжүүлэх"</string>
<string name="experimental" msgid="6198182315536726162">"Туршилтын"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth-г асаах уу?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Компьютерийн гараа таблетад холбохын тулд эхлээд Bluetooth-г асаана уу."</string>
diff --git a/packages/SystemUI/res/values-mr-rIN/strings.xml b/packages/SystemUI/res/values-mr-rIN/strings.xml
index 00c4965..6415adb 100644
--- a/packages/SystemUI/res/values-mr-rIN/strings.xml
+++ b/packages/SystemUI/res/values-mr-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"स्क्रीन पिन करणे"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"शोधा"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> प्रारंभ करणे शक्य झाले नाही."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"अधिक"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"इतिहास"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"क्षैतिज विभाजित करा"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"अनुलंब विभाजित करा"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"सानुकूल विभाजित करा"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"स्टेटस बारमध्ये घड्याळ सेकंद दर्शवा. कदाचित बॅटरी आयुष्य प्रभावित होऊ शकते."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिंग्जची पुनर्रचना करा"</string>
<string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिंग्जमध्ये चमक दर्शवा"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"लिखाण सक्षम करा"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"विहंगावलोकन बटणाद्वारे लिखाण सक्षम करा"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"जलद टॉगल करा सक्षम करा"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"लिखाण करताना लाँच कालबाह्य सक्षम करा"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"पूर्ण स्क्रीन स्क्रीनशॉट सक्षम करा"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"विहंगावलोकनामध्ये पूर्ण स्क्रीन स्क्रीनशॉट सक्षम करा"</string>
<string name="experimental" msgid="6198182315536726162">"प्रायोगिक"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लूटुथ सुरू करायचे?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"आपला कीबोर्ड आपल्या टॅब्लेटसह कनेक्ट करण्यासाठी, आपल्याला प्रथम ब्लूटुथ चालू करणे आवश्यक आहे."</string>
diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml
index 7b53a6c..ceefc29 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"penyematan skrin"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"cari"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Tidak dapat memulakan <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Lagi"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Sejarah"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Mendatar Terpisah"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Menegak Terpisah"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Tersuai Terpisah"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Tunjukkan saat jam dalam bar status. Mungkin menjejaskan hayat bateri."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Susun Semula Tetapan Pantas"</string>
<string name="show_brightness" msgid="6613930842805942519">"Tunjukkan kecerahan dalam Tetapan Pantas"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Dayakan penghalamanan"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Mendayakan penghalamanan melalui butang Ikhtisar"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Dayakan togol pantas"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Membolehkan pelancaran tamat masa semasa penghalamanan"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Dayakan tangkapan skrin skrin penuh"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Mendayakan tangkapan skrin skrin penuh dalam Ikhtisar"</string>
<string name="experimental" msgid="6198182315536726162">"Percubaan"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Hidupkan Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Untuk menyambungkan papan kekunci anda dengan tablet, anda perlu menghidupkan Bluetooth terlebih dahulu."</string>
diff --git a/packages/SystemUI/res/values-my-rMM/strings.xml b/packages/SystemUI/res/values-my-rMM/strings.xml
index 2f52a02..81c0932 100644
--- a/packages/SystemUI/res/values-my-rMM/strings.xml
+++ b/packages/SystemUI/res/values-my-rMM/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"မျက်နှာပြင် ပင်ထိုးမှု"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ရှာဖွေရန်"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> ကို မစနိုင်ပါ။"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"နောက်ထပ်"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"မှတ်တမ်း"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ရေပြင်ညီ ပိုင်းမည်"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ဒေါင်လိုက်ပိုင်းမည်"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"စိတ်ကြိုက် ပိုင်းမည်"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"အခြေအနေပြနေရာမှာ နာရီ စက္ကန့်များကို ပြပါ။ ဘက်ထရီ သက်တမ်းကို အကျိုးသက်ရောက်နိုင်တယ်။"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"အမြန် ဆက်တင်များကို ပြန်စီစဉ်ရန်"</string>
<string name="show_brightness" msgid="6613930842805942519">"အမြန် ဆက်တင်များထဲက တောက်ပမှုကို ပြရန်"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"စာမျက်နှာပြောင်းမှု ဖွင့်ပါ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"အနှစ်ချုပ်ကြည့်မှု မှတစ်ဆင့် စာမျက်နှာပြောင်းမှုကို ဖွင့်ပါ"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"အမြန်ဖွင့်/ပိတ်ခြင်း ဖွင့်ရန်"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"စာမျက်နှာပြောင်းမှု ဖွင့်တင်ရန်အတွက် ကန့်သတ်ချိန်ကို ဖွင့်ပါ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"မျက်နှာပြင်အပြည့် လျှပ်တစ်ပြက်ပုံများကို ဖွင့်ပါ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"အနှစ်ချုပ်ကြည့်မှုထဲတွင် မျက်နှာပြင်အပြည့် လျှပ်တစ်ပြက်ပုံများကို ဖွင့်ပါ"</string>
<string name="experimental" msgid="6198182315536726162">"စမ်းသပ်ရေး"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ဘလူးတုသ် ဖွင့်ရမလား။"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ကီးဘုတ်ကို တပ်ဘလက်နှင့် ချိတ်ဆက်ရန်၊ ပမထဦးစွာ ဘလူးတုသ်ကို ဖွင့်ပါ။"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 014a6b1..7f6895e 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"én-appsmodus"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"Søk"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Kunne ikke starte <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mer"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Logg"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Del horisontalt"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Del vertikalt"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Del tilpasset"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Vis sekunder i statusfeltet på klokken. Det kan påvirke batteritiden."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Omorganiser hurtiginnstillingene"</string>
<string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i hurtiginnstillingene"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Slå på paginering"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Slå på paginering via Oversikt-knappen"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Slå på hurtigveksling"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Slå på tidsavbrudd mens paginering pågår"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Slå på skjermdumper av fullskjerm"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Slå på skjermdumper av fullskjerm i Oversikt"</string>
<string name="experimental" msgid="6198182315536726162">"På forsøksstadiet"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vil du slå på Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"For å koble tastaturet til nettbrettet ditt må du først slå på Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ne-rNP/strings.xml b/packages/SystemUI/res/values-ne-rNP/strings.xml
index 46fce99..51e8a16 100644
--- a/packages/SystemUI/res/values-ne-rNP/strings.xml
+++ b/packages/SystemUI/res/values-ne-rNP/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"स्क्रिन पिन गर्दै"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"खोजी गर्नुहोस्"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"सुरु गर्न सकिएन <xliff:g id="APP">%s</xliff:g>।"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"थप"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"इतिहास"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"तेर्सो रूपमा विभाजन गर्नुहोस्"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ठाडो रूपमा विभाजन गर्नुहोस्"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"अनुकूलन विभाजन गर्नुहोस्"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"वस्तुस्थिति पट्टीको घडीमा सेकेन्ड देखाउनुहोस्। ब्याट्री आयु प्रभावित हुन सक्छ।"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिङहरू पुनः व्यवस्थित गर्नुहोस्"</string>
<string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिङहरूमा उज्यालो देखाउनुहोस्"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"पेजिंग सक्रिय गर्नुहोस्"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"परिदृश्य बटन मार्फत पेजिङ सक्रिय गर्नुहोस्"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"छिटो टगल सक्रिय गर्नुहोस्"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"जब पेजिंग हुन्छ टाइमआउट प्रक्षेपण सक्रिय गर्नुहोस्"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"पूर्ण स्क्रिन स्क्रीनशटहरू सक्रिय गर्नुहोस्"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"परिदृश्यमा पूर्ण स्क्रिन स्क्रीनशटहरू सक्रिय गर्नुहोस्"</string>
<string name="experimental" msgid="6198182315536726162">"प्रयोगात्मक"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लुटुथ सक्रिय पार्ने हो?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"आफ्नो ट्याब्लेटसँग किबोर्ड जोड्न, पहिले तपाईँले ब्लुटुथ सक्रिय गर्नुपर्छ।"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index dbcaab8..88d0b4b 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -283,7 +283,7 @@
<string name="quick_settings_notifications_label" msgid="4818156442169154523">"Meldingen"</string>
<string name="quick_settings_flashlight_label" msgid="2133093497691661546">"Zaklamp"</string>
<string name="quick_settings_cellular_detail_title" msgid="8575062783675171695">"Mobiele gegevens"</string>
- <string name="quick_settings_cellular_detail_data_usage" msgid="1964260360259312002">"Gegevensgebruik"</string>
+ <string name="quick_settings_cellular_detail_data_usage" msgid="1964260360259312002">"Datagebruik"</string>
<string name="quick_settings_cellular_detail_remaining_data" msgid="722715415543541249">"Resterende gegevens"</string>
<string name="quick_settings_cellular_detail_over_limit" msgid="967669665390990427">"Limiet overschreden"</string>
<string name="quick_settings_cellular_detail_data_used" msgid="1476810587475761478">"<xliff:g id="DATA_USED">%s</xliff:g> gebruikt"</string>
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"scherm vastzetten"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"zoeken"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Kan <xliff:g id="APP">%s</xliff:g> niet starten."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Meer"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Geschiedenis"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horizontaal splitsen"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Verticaal splitsen"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Aangepast splitsen"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Klokseconden op de statusbalk weergeven. Kan van invloed zijn op de accuduur."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Snelle instellingen opnieuw indelen"</string>
<string name="show_brightness" msgid="6613930842805942519">"Helderheid weergeven in Snelle instellingen"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Paginering inschakelen"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Paginering via de knop Overzicht inschakelen"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Snel schakelen inschakelen"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Starttime-out tijdens paginering inschakelen"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Screenshots op volledig scherm inschakelen"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Screenshots op volledig scherm in Overzicht inschakelen"</string>
<string name="experimental" msgid="6198182315536726162">"Experimenteel"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth inschakelen?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Als je je toetsenbord wilt verbinden met je tablet, moet je eerst Bluetooth inschakelen."</string>
diff --git a/packages/SystemUI/res/values-pa-rIN/strings.xml b/packages/SystemUI/res/values-pa-rIN/strings.xml
index 5f21e94..df6c473af 100644
--- a/packages/SystemUI/res/values-pa-rIN/strings.xml
+++ b/packages/SystemUI/res/values-pa-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ਸਕ੍ਰੀਨ ਪਿਨਿੰਗ"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ਖੋਜੋ"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> ਨੂੰ ਚਾਲੂ ਨਹੀਂ ਕਰ ਸਕਿਆ।"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"ਹੋਰ"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ਇਤਿਹਾਸ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ਹੌਰੀਜ਼ੌਂਟਲ ਸਪਲਿਟ"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ਵਰਟੀਕਲ ਸਪਲਿਟ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ਕਸਟਮ ਸਪਲਿਟ"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ਸਥਿਤੀ ਬਾਰ ਵਿੱਚ ਘੜੀ ਸਕਿੰਟ ਦਿਖਾਓ। ਬੈਟਰੀ ਸਮਰੱਥਾ ਤੇ ਅਸਰ ਪੈ ਸਕਦਾ ਹੈ।"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਨੂੰ ਦੁਬਾਰਾ ਕ੍ਰਮ ਦਿਓ"</string>
<string name="show_brightness" msgid="6613930842805942519">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਚਮਕ ਦਿਖਾਓ"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"ਪੇਜਿੰਗ ਨੂੰ ਯੋਗ ਬਣਾਓ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"ਰੂਪ-ਰੇਖਾ ਬਟਨ ਦੁਆਰਾ ਪੇਜਿੰਗ ਨੂੰ ਯੋਗ ਬਣਾਓ"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"ਤੇਜ਼ ਬਦਲੋ ਨੂੰ ਯੋਗ ਬਣਾਓ"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"ਪੇਜਿੰਗ ਦੌਰਾਨ ਲਾਂਚ ਸਮਾਂ ਸਮਾਪਤੀ ਦੀ ਮਿਅਾਦ ਯੋਗ ਬਣਾਓ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"ਪੂਰੀ-ਸਕਰੀਨ ਸਕਰੀਨਸ਼ਾਟ ਨੂੰ ਯੋਗ ਬਣਾਓ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"ਰੂਪ-ਰੇਖਾ ਵਿੱਚ ਪੂਰੀ-ਸਕਰੀਨ ਸਕਰੀਨਸ਼ਾਟ ਨੂੰ ਯੋਗ ਬਣਾਓ"</string>
<string name="experimental" msgid="6198182315536726162">"ਪ੍ਰਯੋਗਾਤਮਿਕ"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ਚਾਲੂ ਕਰੋ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ਆਪਣੇ ਟੈਬਲੇਟ ਨਾਲ ਆਪਣਾ ਕੀ-ਬੋਰਡ ਕਨੈਕਟ ਕਰਨ ਲਈ, ਤੁਹਾਨੂੰ ਪਹਿਲਾਂ Bluetooth ਚਾਲੂ ਕਰਨ ਦੀ ਜ਼ਰੂਰਤ ਹੈ।"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 68816ef..694132b 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"przypinanie ekranu"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"szukaj"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Nie udało się uruchomić aplikacji <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Więcej"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historia"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Podziel poziomo"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Podziel pionowo"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Podziel niestandardowo"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Pokaż sekundy na zegarku na pasku stanu. Może mieć wpływ na czas pracy baterii."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Uporządkuj Szybkie ustawienia"</string>
<string name="show_brightness" msgid="6613930842805942519">"Pokaż jasność w Szybkich ustawieniach"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Włącz stronicowanie"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Włącz stronicowanie za pomocą przycisku Przegląd"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Włącz szybkie przełączanie"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Włącz limit czasu uruchomienia podczas stronicowania"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Włącz pełnoekranowe zrzuty ekranu"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Włącz pełnoekranowe zrzuty ekranu w widoku Przegląd"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperymentalne"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Włączyć Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Aby połączyć klawiaturę z tabletem, musisz najpierw włączyć Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index c875922..e806bfc 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixação de tela"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"pesquisar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Não foi possível iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mais"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Histórico"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisão horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisão vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisão personalizada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostrar segundos do relógio na barra de status. Pode afetar a duração da bateria."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar \"Configurações rápidas\""</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar brilho nas \"Configurações rápidas\""</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Ativar paginação"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Ativar paginação pelo botão \"Visão geral\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Ativar alternância rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Ativar tempo limite de inicialização ao fazer paginação"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Ativar capturas de tela em tela cheia"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Ativar capturas de tela em tela cheia em \"Visão geral\""</string>
<string name="experimental" msgid="6198182315536726162">"Experimentais"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Ativar o Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teclado ao tablet, é preciso primeiro ativar o Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 1594dca..468c75d 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixação no ecrã"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"pesquisar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Não foi possível iniciar o <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mais"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Histórico"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisão horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisão vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisão personalizada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostrar segundos do relógio na barra de estado. Pode afetar a autonomia da bateria."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar as Definições rápidas"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar luminosidade nas Definições rápidas"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Ativar a paginação"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Ativar a paginação através do botão Vista geral"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Ativar alternância rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Ativar o limite de tempo de lançamento durante a paginação"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Ativar capturas de ecrã inteiro"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Ativar capturas de ecrã inteiro na Vista geral"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Pretende ativar o Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para ligar o teclado ao tablet, tem de ativar primeiro o Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index c875922..e806bfc 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixação de tela"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"pesquisar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Não foi possível iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mais"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Histórico"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisão horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisão vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisão personalizada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostrar segundos do relógio na barra de status. Pode afetar a duração da bateria."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar \"Configurações rápidas\""</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar brilho nas \"Configurações rápidas\""</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Ativar paginação"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Ativar paginação pelo botão \"Visão geral\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Ativar alternância rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Ativar tempo limite de inicialização ao fazer paginação"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Ativar capturas de tela em tela cheia"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Ativar capturas de tela em tela cheia em \"Visão geral\""</string>
<string name="experimental" msgid="6198182315536726162">"Experimentais"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Ativar o Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teclado ao tablet, é preciso primeiro ativar o Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index ab182a2..1b8e8de 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -295,6 +295,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixare pe ecran"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"căutare"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> nu a putut porni."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mai mult"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Istoric"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divizare pe orizontală"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divizare pe verticală"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divizare personalizată"</string>
@@ -438,12 +440,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Afișează secundele pe ceas în bara de stare. Poate afecta autonomia bateriei."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Rearanjați Setările rapide"</string>
<string name="show_brightness" msgid="6613930842805942519">"Afișați luminozitatea în Setările rapide"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Activați paginarea"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Activați paginarea prin butonul Recente"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Activați comutarea rapidă"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Activați timpul limită pentru lansare în timpul paginării"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Activați capturile de ecran pe ecran complet"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Activați capturile de ecran pe ecran complet în modul Prezentare generală"</string>
<string name="experimental" msgid="6198182315536726162">"Experimentale"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Activați Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Pentru a conecta tastatura la tabletă, mai întâi trebuie să activați Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index ecacd4b..9068e0b 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"Заблокировать в приложении"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"поиск"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Не удалось запустить приложение \"<xliff:g id="APP">%s</xliff:g>\""</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Ещё"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Журнал"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Разделить по горизонтали"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Разделить по вертикали"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Разделить по-другому"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Показывать в строке состояния время с точностью до секунды (заряд батареи может расходоваться быстрее)."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Изменить порядок Быстрых настроек"</string>
<string name="show_brightness" msgid="6613930842805942519">"Добавить яркость в Быстрые настройки"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Включить подкачку страниц"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Разрешить подкачку страниц с помощью кнопки \"Обзор\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Быстрое переключение"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Включить тайм-аут запуска при подкачке страниц"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Разрешить скриншоты всего экрана"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Разрешить скриншоты всего экрана в режиме обзора"</string>
<string name="experimental" msgid="6198182315536726162">"Экспериментальная функция"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Подключение по Bluetooth"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Чтобы подключить клавиатуру к планшету, включите Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-si-rLK/strings.xml b/packages/SystemUI/res/values-si-rLK/strings.xml
index a8f9469..3be3718 100644
--- a/packages/SystemUI/res/values-si-rLK/strings.xml
+++ b/packages/SystemUI/res/values-si-rLK/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"තිර ඇමිණීම"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"සෙවීම"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> ආරම්භ කළ නොහැක."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"තවත්"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ඉතිහාසය"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"තිරස්ව වෙන් කරන්න"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"සිරස්ව වෙන් කරන්න"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"අභිමත ලෙස වෙන් කරන්න"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"තත්ත්ව තීරුවෙහි ඔරලෝසු තත්පර පෙන්වන්න. බැටරි ආයු කාලයට බලපෑමට හැකිය."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ඉක්මන් සැකසීම් යළි පිළිවෙළට සකසන්න"</string>
<string name="show_brightness" msgid="6613930842805942519">"ඉක්මන් සැකසීම්වල දීප්තිය පෙන්වන්න"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"පේජින් සබල කිරීම"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"දළ විශ්ලේෂණ බොත්තම හරහා පේජින් සබල කිරීම"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"වේගවත් ටොගල කිරීම සබල කරන්න"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"පේජින් අතරතුර දියත් කිරීමේ කාල නිමාව සබල කිරීම"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"පූර්ණ තිරයේ තිර රූ සබල කිරීම"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"දළ විශ්ලේෂණය තුළ පූර්ණ තිරයේ තිර රූ සබල කිරීම"</string>
<string name="experimental" msgid="6198182315536726162">"පරීක්ෂණාත්මක"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"බ්ලූටූත් ක්රියාත්මක කරන්නද?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ඔබේ යතුරු පුවරුව ඔබේ ටැබ්ලට් පරිගණකයට සම්බන්ධ කිරීමට, ඔබ පළමුව බ්ලූටූත් ක්රියාත්මක කළ යුතුය."</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index bd7e210..d96194c 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"pripnutie k obrazovke"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"hľadať"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Aplikáciu <xliff:g id="APP">%s</xliff:g> sa nepodarilo spustiť"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Viac"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"História"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Rozdeliť vodorovné"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Rozdeliť zvislé"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Rozdeliť vlastné"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Zobrazí sekundy v stavovom riadku. Môže to ovplyvňovať výdrž batérie."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Zmeniť usporiadanie Rýchlych nastavení"</string>
<string name="show_brightness" msgid="6613930842805942519">"Zobraziť jas v Rýchlych nastaveniach"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Povoliť prechádzanie po stranách"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Povoľte prechádzanie po stranách prostredníctvom tlačidla Prehľad"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Povoliť rýchle prepínanie"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Povolenie časového limitu pre spustenie počas prechádzania po stranách"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Povoliť snímky celej obrazovky"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Povolenie snímok celej obrazovky v Prehľade"</string>
<string name="experimental" msgid="6198182315536726162">"Experimentálne"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Zapnúť Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Ak chcete klávesnicu pripojiť k tabletu, najprv musíte zapnúť Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 6d7fbc0..f3d3b39 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"pripenjanje zaslona"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"iskanje"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Aplikacije <xliff:g id="APP">%s</xliff:g> ni bilo mogoče zagnati."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Več"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Zgodovina"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Razdeli vodoravno"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Razdeli navpično"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Razdeli po meri"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Prikaže sekunde pri uri v vrstici stanja. To lahko vpliva na čas delovanja pri akumulatorskem napajanju."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Preuredi hitre nastavitve"</string>
<string name="show_brightness" msgid="6613930842805942519">"Prikaz svetlosti v hitrih nastavitvah"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Omogočanje pregleda strani"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Omogoči pregled strani z gumbom za pregled"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Omogoči hiter preklop"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Omogoči potek časovne omejitve za odpiranje med pregledom strani"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Omogočanje celozaslonskih posnetkov zaslona"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Omogoči celozaslonske posnetke zaslona v Pregledu"</string>
<string name="experimental" msgid="6198182315536726162">"Poskusno"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Želite vklopiti Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Če želite povezati tipkovnico in tablični računalnik, vklopite Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-sq-rAL/strings.xml b/packages/SystemUI/res/values-sq-rAL/strings.xml
index 38066be..afad772 100644
--- a/packages/SystemUI/res/values-sq-rAL/strings.xml
+++ b/packages/SystemUI/res/values-sq-rAL/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"gozhdimi i ekranit"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"kërko"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> nuk mundi të nisej."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Më shumë"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historiku"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Ndaje horizontalisht"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Ndaj vertikalisht"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Ndaj të personalizuarën"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Trego sekondat e orës në shiritin e statusit. Mund të ndikojë te jeta e baterisë."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Risistemo Cilësimet e shpejta"</string>
<string name="show_brightness" msgid="6613930842805942519">"Shfaq ndriçimin te Cilësimet e shpejta"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Aktivizo shfletimin"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Aktivizo shfletimin me butonin \"Përmbledhja\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Aktivizo ndërrimin e shpejtë"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Aktivizo kohën e pritjes të nisjes gjatë shfletimit"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Aktivizo pamjet e ekranit në ekranin e plotë"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Aktivizo pamjet e ekranit në ekranin e plotë te \"Përmbledhja\""</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentale"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Të aktivizohet \"bluetooth-i\"?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Për të lidhur tastierën me tabletin, në fillim duhet të aktivizosh \"bluetooth-in\"."</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 5fd78f0..ad14d26 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -295,6 +295,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"качење екрана"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"претражи"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Покретање апликације <xliff:g id="APP">%s</xliff:g> није успело."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Још"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Историја"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Подели хоризонтално"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Подели вертикално"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Прилагођено дељење"</string>
@@ -438,12 +440,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Секунде на сату се приказују на статусној траци. То може да утиче на трајање батерије."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Преуреди Брза подешавања"</string>
<string name="show_brightness" msgid="6613930842805942519">"Прикажи осветљеност у Брзим подешавањима"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Омогући листање"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Омогућава листање помоћу дугмета Преглед"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Омогући брзо листање"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Омогућава временско ограничење покретања при листању"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Омогући снимке екрана преко целог екрана"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Омогућава снимке екрана преко целог екрана у Прегледу"</string>
<string name="experimental" msgid="6198182315536726162">"Експериментално"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Желите ли да укључите Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Да бисте повезали тастатуру са таблетом, прво морате да укључите Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index bec7593..15e8b12 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fästa skärmen"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"sök"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Det gick inte att starta appen <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mer"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historik"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Dela horisontellt"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Dela vertikalt"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Dela anpassad"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Visa klocksekunder i statusfältet. Detta kan påverka batteritiden."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Ordna snabbinställningarna"</string>
<string name="show_brightness" msgid="6613930842805942519">"Visa ljusstyrka i snabbinställningarna"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Aktivera sidindelning"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Aktivera sidindelning via knappen Översikt"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Aktivera snabb aktivering och inaktivering"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Aktivera timeout i lanseringen vid sidindelning"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Aktivera skärmdumpar på helskärm"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Aktivera skärmdumpar på helskärm i översikten"</string>
<string name="experimental" msgid="6198182315536726162">"Experimentella"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vill du aktivera Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Om du vill ansluta tangentbordet till surfplattan måste du först aktivera Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 8cc6712..1d231fc 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"kudumisha programu moja"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"tafuta"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Haikuweza kuanzisha <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Zaidi"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historia"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Gawanya Mlalo"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Gawanya Wima"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Maalum Iliyogawanywa"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Onyesha sekunde za saa katika sehemu ya arifa. Inaweza kuathiri muda wa matumizi ya betri."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Panga Upya Mipangilio ya Haraka"</string>
<string name="show_brightness" msgid="6613930842805942519">"Onyesha unga\'avu katika Mipangilio ya Haraka"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Washa kipengee cha kupata kumbukumbu"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Washa kipengee cha kupata kumbukumbu kupitia kitufe cha Muhtasari"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Washa kugeuza haraka"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Washa kipengee cha kuisha kwa muda wa uzinduzi unapopata kumbukumbu"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Washa picha za skrini nzima"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Washa picha za skrini nzima katika Muhtasari"</string>
<string name="experimental" msgid="6198182315536726162">"Ya majaribio"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Je, ungependa kuwasha Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Ili uunganishe Kibodi yako kwenye kompyuta yako kibao, lazima kwanza uwashe Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ta-rIN/strings.xml b/packages/SystemUI/res/values-ta-rIN/strings.xml
index 5896c3001..c41494b 100644
--- a/packages/SystemUI/res/values-ta-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ta-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"திரையை பின் செய்தல்"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"தேடு"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>ஐத் தொடங்க முடியவில்லை."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"மேலும்"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"வரலாறு"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"கிடைமட்டமாகப் பிரி"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"செங்குத்தாகப் பிரி"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"தனிவிருப்பத்தில் பிரி"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"நிலைப் பட்டியில் கடிகார வினாடிகளைக் காட்டும். பேட்டரியின் ஆயுளைக் குறைக்கலாம்."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"விரைவு அமைப்புகளை மறுவரிசைப்படுத்து"</string>
<string name="show_brightness" msgid="6613930842805942519">"விரைவு அமைப்புகளில் ஒளிர்வுப் பட்டியைக் காட்டு"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"பேஜிங்கை இயக்கு"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"மேலோட்டப் பார்வை பொத்தான் வழியாக பேஜிங்கை இயக்கு"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"வேகமாக மாறுவதை இயக்கு"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"பேஜிங்கின் போது தொடக்க நேர முடிவை இயக்கு"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"முழுத்திரை ஸ்கிரீன் ஷாட்களை இயக்கு"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"மேலோட்டப் பார்வையில் முழுத்திரை ஸ்கிரீன் ஷாட்களை இயக்கு"</string>
<string name="experimental" msgid="6198182315536726162">"சோதனை முயற்சி"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"புளூடூத்தை இயக்கவா?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"உங்கள் டேப்லெட்டுடன் விசைப்பலகையை இணைக்க, முதலில் புளூடூத்தை இயக்க வேண்டும்."</string>
diff --git a/packages/SystemUI/res/values-te-rIN/strings.xml b/packages/SystemUI/res/values-te-rIN/strings.xml
index dccac8b..a628ef7 100644
--- a/packages/SystemUI/res/values-te-rIN/strings.xml
+++ b/packages/SystemUI/res/values-te-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"స్క్రీన్ పిన్నింగ్"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"శోధించు"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>ని ప్రారంభించడం సాధ్యపడలేదు."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"మరింత"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"చరిత్ర"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"సమతలంగా విభజించు"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"లంబంగా విభజించు"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"అనుకూలంగా విభజించు"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"స్థితి పట్టీలో గడియారం సెకన్లు చూపుతుంది. బ్యాటరీ శక్తి ప్రభావితం చేయవచ్చు."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"శీఘ్ర సెట్టింగ్ల ఏర్పాటు క్రమం మార్చు"</string>
<string name="show_brightness" msgid="6613930842805942519">"శీఘ్ర సెట్టింగ్ల్లో ప్రకాశం చూపు"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"పేజింగ్ను ప్రారంభించు"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"స్థూలదృష్టి బటన్ ద్వారా పేజింగ్ను ప్రారంభిస్తుంది"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"వేగవంతమైన టోగుల్ను ప్రారంభించు"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"పేజింగ్ చేస్తున్నప్పుడు గడువు ముగింపు లాంచ్ చేయిని ప్రారంభిస్తుంది"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"పూర్తి స్క్రీన్ స్క్రీన్షాట్లను ప్రారంభించండి"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"స్థూలదృష్టిలో పూర్తి స్క్రీన్ స్క్రీన్షాట్లను ప్రారంభిస్తుంది"</string>
<string name="experimental" msgid="6198182315536726162">"ప్రయోగాత్మకం"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"బ్లూటూత్ ఆన్ చేయాలా?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"మీ కీబోర్డ్ను మీ టాబ్లెట్తో కనెక్ట్ చేయడానికి, మీరు ముందుగా బ్లూటూత్ ఆన్ చేయాలి."</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 625b676..44ad1fa 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"การตรึงหน้าจอ"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ค้นหา"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"ไม่สามารถเริ่มใช้ <xliff:g id="APP">%s</xliff:g>"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"เพิ่มเติม"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ประวัติ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"แยกในแนวนอน"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"แยกในแนวตั้ง"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"แยกแบบกำหนดเอง"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"แสดงวินาทีของนาฬิกาในแถบสถานะ อาจส่งผลต่ออายุแบตเตอรี"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"จัดเรียงการตั้งค่าด่วนใหม่"</string>
<string name="show_brightness" msgid="6613930842805942519">"แสดงความสว่างในการตั้งค่าด่วน"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"เปิดใช้การสลับหน้า"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"เปิดใช้การสลับหน้าผ่านทางปุ่มภาพรวม"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"เปิดใช้การสลับแบบด่วน"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"เปิดใช้ระยะหมดเวลาการเปิดขณะสลับหน้า"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"เปิดใช้ภาพหน้าจอแบบเต็มจอ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"เปิดใช้ภาพหน้าจอแบบเต็มจอใน \"ภาพรวม\""</string>
<string name="experimental" msgid="6198182315536726162">"ทดสอบ"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"เปิดบลูทูธไหม"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"หากต้องการเชื่อมต่อแป้นพิมพ์กับแท็บเล็ต คุณต้องเปิดบลูทูธก่อน"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index e3b8e24..c1bb85c 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"pagpi-pin sa screen"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"maghanap"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Hindi masimulan <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Higit pa"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"History"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Custom"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Ipakita ang mga segundo ng orasan sa status bar. Maaaring makaapekto sa tagal ng baterya."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Ayusing Muli ang Mga Mabilisang Setting"</string>
<string name="show_brightness" msgid="6613930842805942519">"Ipakita ang liwanag sa Mga Mabilisang Setting"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"I-enable ang paging"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"I-enable ang paging sa pamamagitan ng button na Pangkalahatang-ideya"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"I-enable ang mabilis na pag-toggle"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"I-enable ang pag-timeout ng paglunsad habang nagsasagawa ng paging"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"I-enable ang mga fullscreen na screenshot"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"I-enable ang mga fullscreen na screenshot sa Pangkalahatang-ideya"</string>
<string name="experimental" msgid="6198182315536726162">"Pang-eksperimento"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"I-on ang Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Upang ikonekta ang iyong keyboard sa iyong tablet, kailangan mo munang i-on ang Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 2fe49e5..854f850 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ekran sabitleme"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ara"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> başlatılamadı."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Diğer"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Geçmiş"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Yatay Ayırma"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Dikey Ayırma"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Özel Ayırma"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Durum çubuğunda saatin saniyelerini gösterir. Pil ömrünü etkileyebilir."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Hızlı Ayarlar\'ı Yeniden Düzenle"</string>
<string name="show_brightness" msgid="6613930842805942519">"Hızlı Ayarlar\'da parlaklığı göster"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Sayfalara ayırmayı etkinleştir"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Genel bakış düğmesiyle sayfalara ayırmayı etkinleştirin"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Hızlı açma/kapatmayı etkinleştir"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Sayfalara ayırma sırasında başlatma zaman aşımını etkinleştirin"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Tam ekran boyutunda ekran görüntülerini etkinleştir"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Genel bakışta tam ekran boyutundaki ekran görüntülerini etkinleştirin"</string>
<string name="experimental" msgid="6198182315536726162">"Deneysel"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth açılsın mı?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Klavyenizi tabletinize bağlamak için önce Bluetooth\'u açmanız gerekir."</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 5a749c4..5347d93 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"закріпити екран"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"пошук"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Не вдалося запустити <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Більше"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Історія"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Розділити горизонтально"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Розділити вертикально"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Розділити (власний варіант)"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Показувати секунди на годиннику в рядку стану. Акумулятор може розряджатися швидше."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Упорядкувати швидкі налаштування"</string>
<string name="show_brightness" msgid="6613930842805942519">"Показувати панель яскравості у швидких налаштуваннях"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Увімкнути перехід між сторінками"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Увімкнути перехід між сторінками за допомогою кнопки \"Огляд\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Увімкнути швидкий перехід"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Увімкнути очікування на запуск під час переходу на сторінку"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Увімкнути знімки повного екрана"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Увімкнути знімки повного екрана в режимі огляду"</string>
<string name="experimental" msgid="6198182315536726162">"Експериментальні налаштування"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Увімкнути Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Щоб під’єднати клавіатуру до планшета, спершу потрібно ввімкнути Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ur-rPK/strings.xml b/packages/SystemUI/res/values-ur-rPK/strings.xml
index 7dcb076..b9da6e3 100644
--- a/packages/SystemUI/res/values-ur-rPK/strings.xml
+++ b/packages/SystemUI/res/values-ur-rPK/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"اسکرین کو پن کرنا"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"تلاش کریں"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> کو شروع نہیں کیا جا سکا۔"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"مزید"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"سرگزشت"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"بلحاظ افقی الگ کریں"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"بلحاظ عمودی الگ کریں"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"بلحاظ حسب ضرورت الگ کریں"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"گھڑی کے سیکنڈز اسٹیٹس بار میں دکھائیں۔ اس کا بیٹری کی زندگی پر اثر پڑ سکتا ہے۔"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"فوری ترتیبات کو دوبارہ ترتیب دیں"</string>
<string name="show_brightness" msgid="6613930842805942519">"فوری ترتیبات میں چمکیلا پن دکھائیں"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"پیجنگ فعال کریں"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"مجموعی جائزہ بٹن کے ذریعے پیجنگ فعال کریں"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"تیز ٹوگل فعال کریں"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"پیجنگ کے دوران لانچ ٹائم آؤٹ فعال کریں"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"فل اسکرین کے اسکرین شاٹس فعال کریں"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"مجموعی جائزے میں فل اسکرین کے اسکرین شاٹس فعال کریں"</string>
<string name="experimental" msgid="6198182315536726162">"تجرباتی"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"بلوٹوتھ آن کریں؟"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"اپنے کی بورڈ کو اپنے ٹیبلٹ کے ساتھ منسلک کرنے کیلئے پہلے آپ کو اپنا بلو ٹوتھ آن کرنا ہو گا۔"</string>
diff --git a/packages/SystemUI/res/values-uz-rUZ/strings.xml b/packages/SystemUI/res/values-uz-rUZ/strings.xml
index 1f26da6..67cd123 100644
--- a/packages/SystemUI/res/values-uz-rUZ/strings.xml
+++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"o‘zgarmas ekran"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"qidirish"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"“<xliff:g id="APP">%s</xliff:g>” ilovasini ishga tushirib bo‘lmadi."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Ko‘proq"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Jurnal"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Gorizontal yo‘nalishda bo‘lish"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikal yo‘nalishda bo‘lish"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Boshqa usulda bo‘lish"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Holat panelida soat soniyalari ko‘rsatilsin. Bu batareya resursiga ta’sir qilishi mumkin."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Tezkor sozlamalarni qayta tartiblash"</string>
<string name="show_brightness" msgid="6613930842805942519">"Tezkor sozlamalarda yorqinlikni ko‘rsatish"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Sahifalashni yoqish"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Umumiy ma’lumot tugmasi orqali sahifalashni yoqish"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Tezkor almashtirishni yoqish"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Sahifalashda tanaffusni yoqish"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"To‘liq ekranni skrinshotga olishni yoqish"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Umumiy rejimda to‘liq ekranni skrinshotga olishni yoqish"</string>
<string name="experimental" msgid="6198182315536726162">"Tajribaviy"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth yoqilsinmi?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Klaviaturani planshetingizga ulash uchun Bluetooth xizmatini yoqishingiz kerak."</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 41ecb45..aa81068 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"khóa màn hình"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"tìm kiếm"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Không thể khởi động <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Thêm"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Lịch sử"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Phân tách ngang"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Phân tách dọc"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Tùy chỉnh phân tách"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Hiển thị giây đồng hồ trong thanh trạng thái. Có thể ảnh hưởng đến thời lượng pin."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Sắp xếp lại Cài đặt nhanh"</string>
<string name="show_brightness" msgid="6613930842805942519">"Hiển thị độ sáng trong Cài đặt nhanh"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Bật đánh số trang"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Bật đánh số trang qua nút Tổng quan"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Bật chuyển đổi nhanh"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Bật thời gian chờ khởi chạy trong khi đánh số trang"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Bật ảnh chụp toàn màn hình"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Bật ảnh chụp toàn màn hình trong Tổng quan"</string>
<string name="experimental" msgid="6198182315536726162">"Thử nghiệm"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bật Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Để kết nối bàn phím với máy tính bảng, trước tiên, bạn phải bật Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index cc78ef7..fa69975 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"固定屏幕"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"搜索"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"无法启动<xliff:g id="APP">%s</xliff:g>。"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"更多"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"历史记录"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"水平分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"垂直分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"自定义分割"</string>
@@ -437,17 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"在状态栏中显示时钟的秒数。这可能会影响电池的续航时间。"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速设置"</string>
<string name="show_brightness" msgid="6613930842805942519">"在快速设置中显示亮度栏"</string>
- <!-- no translation found for overview_page_on_toggle (7332906295136546986) -->
- <skip />
- <!-- no translation found for overview_page_on_toggle_desc (3350421878356386241) -->
- <skip />
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"启用快速切换"</string>
- <!-- no translation found for overview_fast_toggle_via_button_desc (8983671478896649561) -->
- <skip />
- <!-- no translation found for overview_fullscreen_thumbnails (3052584848522856237) -->
- <skip />
- <!-- no translation found for overview_fullscreen_thumbnails_desc (3588874352141119692) -->
- <skip />
<string name="experimental" msgid="6198182315536726162">"实验性"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"要开启蓝牙吗?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"要将您的键盘连接到平板电脑,您必须先开启蓝牙。"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 1a02914..7375a92 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"螢幕固定"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"搜尋"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"無法啟動「<xliff:g id="APP">%s</xliff:g>」。"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"更多"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"記錄"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"水平分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"垂直分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"自訂分割"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"在狀態列中顯示時鐘秒數,但可能會影響電池壽命。"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速設定"</string>
<string name="show_brightness" msgid="6613930842805942519">"在快速設定顯示亮度"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"啟用分頁"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"透過「概覽」按鈕啟用分頁"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"啟用快速切換"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"進行分頁時啟用啟動逾時"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"啟用全螢幕擷取畫面"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"在「概覽」啟用全螢幕擷取畫面"</string>
<string name="experimental" msgid="6198182315536726162">"實驗版"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"要開啟藍牙嗎?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"如要將鍵盤連接至平板電腦,請先開啟藍牙。"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 659334d..5edda84 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"螢幕固定"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"搜尋"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"無法啟動「<xliff:g id="APP">%s</xliff:g>」。"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"更多"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"紀錄"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"水平分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"垂直分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"自訂分割"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"在狀態列中顯示時鐘秒數。這可能會影響電池續航力。"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速設定"</string>
<string name="show_brightness" msgid="6613930842805942519">"在快速設定中顯示亮度"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"啟用分頁功能"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"啟用透過 [總覽] 按鈕切換分頁的功能"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"啟用快速切換"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"切換分頁時啟用啟動逾時功能"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"啟用全螢幕的螢幕擷取畫面"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"在「總覽」畫面中啟用全螢幕的螢幕擷取畫面"</string>
<string name="experimental" msgid="6198182315536726162">"實驗性"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"要開啟藍牙功能嗎?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"如要將鍵盤連線到平板電腦,您必須先開啟藍牙。"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 8ba9363..51c4389 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ukuphina isikrini"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"sesha"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Ayikwazanga ukuqala i-<xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Okuningi"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Umlando"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Hlukanisa okuvundlile"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Hlukanisa okumile"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Hlukanisa kwezifiso"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Bonisa amasekhondi wewashi kubha yesimo. Ingathinta impilo yebhethri."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Hlela kabusha izilungiselelo ezisheshayo"</string>
<string name="show_brightness" msgid="6613930842805942519">"Bonisa ukukhanya kuzilungiselelo ezisheshayo"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Nika amandla ukuphenya"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Nika amandla wenkinobho yokubuka konke"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Nika amandla ukuguqula ngokushesha"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Nika amandla ukuvula kokuphela kwesikhathi ngesikhathi uphenya"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Nika amandla izithombe-skrini ezigcwele"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Nika amandla izithombe-skrini ezigcwele ngokubuka konke"</string>
<string name="experimental" msgid="6198182315536726162">"Okokulinga"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vula i-Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Ukuze uxhume ikhibhodi yakho nethebhulethi yakho, kufanele uqale ngokuvula i-Bluetooth."</string>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 26a0577..bfd8af9 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -109,7 +109,6 @@
<color name="assist_orb_color">#ffffff</color>
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
- <color name="doze_small_icon_background_color">#ff434343</color>
<!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
<color name="navigation_bar_icon_color">#E5FFFFFF</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c85ada8..086e9f4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -48,11 +48,14 @@
<!-- Height of a single line notification in the status bar -->
<dimen name="notification_single_line_height">32sp</dimen>
- <!-- Height of a small notification in the status bar -->
- <dimen name="notification_min_height">64dp</dimen>
+ <!-- Height of a small notification in the status bar-->
+ <dimen name="notification_min_height">84dp</dimen>
+
+ <!-- Height of a small notification in the status bar which was used before android N -->
+ <dimen name="notification_min_height_legacy">64dp</dimen>
<!-- Height of a large notification in the status bar -->
- <dimen name="notification_max_height">256dp</dimen>
+ <dimen name="notification_max_height">276dp</dimen>
<!-- Height of a medium notification in the status bar -->
<dimen name="notification_mid_height">128dp</dimen>
@@ -214,9 +217,6 @@
<!-- The amount to offset when animating into an affiliate group. -->
<dimen name="recents_task_view_affiliate_group_enter_offset">64dp</dimen>
- <!-- The alpha to apply to a task thumbnail. -->
- <item name="recents_task_view_thumbnail_alpha" format="float" type="dimen">0.9</item>
-
<!-- The height of a task view bar. -->
<dimen name="recents_task_bar_height">56dp</dimen>
@@ -261,7 +261,7 @@
<!-- bottom_stack_peek_amount + notification_min_height
+ notification_collapse_second_card_padding -->
- <dimen name="min_stack_height">84dp</dimen>
+ <dimen name="min_stack_height">104dp</dimen>
<!-- The height of the area before the bottom stack in which the notifications slow down -->
<dimen name="bottom_stack_slow_down_length">12dp</dimen>
@@ -279,7 +279,7 @@
<dimen name="notification_padding_dimmed">0dp</dimen>
<!-- The padding between the individual notification cards. -->
- <dimen name="notification_padding">4dp</dimen>
+ <dimen name="notification_padding">2dp</dimen>
<!-- The minimum amount of top overscroll to go to the quick settings. -->
<dimen name="min_top_overscroll_to_qs">36dp</dimen>
@@ -299,7 +299,7 @@
<!-- Falsing threshold used when dismissing notifications from the lockscreen. -->
<dimen name="swipe_helper_falsing_threshold">70dp</dimen>
- <dimen name="notifications_top_padding">8dp</dimen>
+ <dimen name="notifications_top_padding">4dp</dimen>
<!-- Minimum distance the user has to drag down to go to the full shade. -->
<dimen name="keyguard_drag_down_min_distance">100dp</dimen>
@@ -351,9 +351,6 @@
<!-- radius of the corners of the material rounded rect background but negative-->
<dimen name="notification_material_rounded_rect_radius_negative">-2dp</dimen>
- <!-- height of notification header view if present -->
- <dimen name="notification_header_height">32dp</dimen>
-
<!-- The padding between notification children -->
<dimen name="notification_children_padding">2dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 04233ba..d6a361c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -753,9 +753,6 @@
<!-- Text for overflow card on Keyguard when there is not enough space for all notifications on Keyguard. [CHAR LIMIT=1] -->
<string name="keyguard_more_overflow_text">+<xliff:g id="number_of_notifications" example="5">%d</xliff:g></string>
- <!-- The divider symbol between different parts of the notification header. not translatable [CHAR LIMIT=1] -->
- <string name="notification_header_divider_symbol" translatable="false">•</string>
-
<!-- An explanation for the visual speed bump in the notifications, which will appear when you click on it. [CHAR LIMIT=50] -->
<string name="speed_bump_explanation">Less urgent notifications below</string>
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index d9f7a46..051921a 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -509,7 +509,7 @@
if (canBeExpanded) {
if (DEBUG) Log.d(TAG, "working on an expandable child");
mNaturalHeight = mScaler.getNaturalHeight();
- mSmallSize = v.getMinHeight();
+ mSmallSize = v.getMinExpandHeight();
} else {
if (DEBUG) Log.d(TAG, "working on a non-expandable child");
mNaturalHeight = mOldHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
index eddf2b1..5b8d3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
@@ -26,6 +26,8 @@
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import java.util.ArrayList;
+
/**
* Helper to invert the colors of views and fade between the states.
*/
@@ -33,14 +35,24 @@
private final Paint mDarkPaint = new Paint();
private final Interpolator mLinearOutSlowInInterpolator;
- private final View mTarget;
+ private final ArrayList<View> mTargets;
private final ColorMatrix mMatrix = new ColorMatrix();
private final ColorMatrix mGrayscaleMatrix = new ColorMatrix();
private final long mFadeDuration;
public ViewInvertHelper(View target, long fadeDuration) {
- mTarget = target;
- mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(mTarget.getContext(),
+ this(constructArray(target), fadeDuration);
+ }
+
+ private static ArrayList<View> constructArray(View target) {
+ final ArrayList<View> views = new ArrayList<>();
+ views.add(target);
+ return views;
+ }
+
+ public ViewInvertHelper(ArrayList<View> targets, long fadeDuration) {
+ mTargets = targets;
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(mTargets.get(0).getContext(),
android.R.interpolator.linear_out_slow_in);
mFadeDuration = fadeDuration;
}
@@ -53,14 +65,18 @@
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateInvertPaint((Float) animation.getAnimatedValue());
- mTarget.setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ for (int i = 0; i < mTargets.size(); i++) {
+ mTargets.get(i).setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ }
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (!invert) {
- mTarget.setLayerType(View.LAYER_TYPE_NONE, null);
+ for (int i = 0; i < mTargets.size(); i++) {
+ mTargets.get(i).setLayerType(View.LAYER_TYPE_NONE, null);
+ }
}
}
});
@@ -73,16 +89,16 @@
public void update(boolean invert) {
if (invert) {
updateInvertPaint(1f);
- mTarget.setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ for (int i = 0; i < mTargets.size(); i++) {
+ mTargets.get(i).setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ }
} else {
- mTarget.setLayerType(View.LAYER_TYPE_NONE, null);
+ for (int i = 0; i < mTargets.size(); i++) {
+ mTargets.get(i).setLayerType(View.LAYER_TYPE_NONE, null);
+ }
}
}
- public View getTarget() {
- return mTarget;
- }
-
private void updateInvertPaint(float intensity) {
float components = 1 - 2 * intensity;
final float[] invert = {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 96ee397..d931856 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -180,7 +180,7 @@
mProfileManager = bluetoothManager.getProfileManager();
bluetoothManager.getEventManager().registerCallback(new BluetoothCallbackHandler());
- InputManager im = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
+ InputManager im = context.getSystemService(InputManager.class);
im.registerOnTabletModeChangedListener(this, mHandler);
mInTabletMode = im.isInTabletMode();
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index a12a3f1..19b65f7 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -36,7 +36,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
-import android.view.View;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -162,9 +161,6 @@
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
final Notification n = nb.build();
- if (n.headsUpContentView != null) {
- n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
- }
mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
}
@@ -200,9 +196,6 @@
mPlaySound = false;
}
final Notification n = nb.build();
- if (n.headsUpContentView != null) {
- n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
- }
mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index f32cfdc..4cc2b8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -222,7 +222,6 @@
final ImageView icon = new ImageView(mContext);
icon.setId(android.R.id.icon);
icon.setScaleType(ScaleType.CENTER_INSIDE);
- icon.setImageTintList(ColorStateList.valueOf(getContext().getColor(android.R.color.white)));
return icon;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
index b0d885a..d26e8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
@@ -23,6 +23,7 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
import android.os.IBinder;
import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
@@ -132,7 +133,9 @@
@Override
protected void handleUpdateState(State state, Object arg) {
state.visible = true;
- state.icon = new DrawableIcon(mTile.getIcon().loadDrawable(mContext));
+ Drawable drawable = mTile.getIcon().loadDrawable(mContext);
+ drawable.setTint(mContext.getColor(android.R.color.white));
+ state.icon = new DrawableIcon(drawable);
state.label = mTile.getLabel();
if (mTile.getContentDescription() != null) {
state.contentDescription = mTile.getContentDescription();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 94c45a4..a0c481a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -724,18 +724,6 @@
getResizeTaskDebugDialog().showResizeTaskDialog(event.task, mRecentsView);
}
- public final void onBusEvent(DragStartEvent event) {
- // Lock the orientation while dragging
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
-
- // TODO: docking requires custom accessibility actions
- }
-
- public final void onBusEvent(DragEndEvent event) {
- // Unlock the orientation when dragging completes
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_BEHIND);
- }
-
public final void onBusEvent(LaunchTaskSucceededEvent event) {
MetricsLogger.histogram(this, "overview_task_launch_index", event.taskIndexFromStackFront);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 8dd9e47..43db666 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -82,7 +82,11 @@
if (launchedFromHome) {
return numTasks - 1;
} else {
- return numTasks - 2;
+ if (flags.isFastToggleRecentsEnabled()) {
+ return numTasks - 1;
+ } else {
+ return numTasks - 2;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 440ed6b1..cdfad18 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -69,7 +69,6 @@
public final int smallestWidth;
/** Misc **/
- public boolean hasDockedTasks;
public boolean useHardwareLayers;
public boolean fakeShadows;
public int svelteLevel;
@@ -112,7 +111,6 @@
// settings or via multi window
lockToAppEnabled = !ssp.hasFreeformWorkspaceSupport() &&
ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0;
- hasDockedTasks = ssp.hasDockedTask();
// Recompute some values based on the given state, since we can not rely on the resource
// system to get certain values.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
index 957da94..3deeb47 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
@@ -19,7 +19,6 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.DragView;
import com.android.systemui.recents.views.DropTarget;
import com.android.systemui.recents.views.TaskView;
@@ -30,15 +29,13 @@
public final Task task;
public final TaskView taskView;
- public final DragView dragView;
public final DropTarget dropTarget;
public final ReferenceCountedTrigger postAnimationTrigger;
- public DragEndEvent(Task task, TaskView taskView, DragView dragView, DropTarget dropTarget,
+ public DragEndEvent(Task task, TaskView taskView, DropTarget dropTarget,
ReferenceCountedTrigger postAnimationTrigger) {
this.task = task;
this.taskView = taskView;
- this.dragView = dragView;
this.dropTarget = dropTarget;
this.postAnimationTrigger = postAnimationTrigger;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
index 2d42a0e..b81c10c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
@@ -16,9 +16,9 @@
package com.android.systemui.recents.events.ui.dragndrop;
+import android.graphics.Point;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.DragView;
import com.android.systemui.recents.views.TaskView;
/**
@@ -28,11 +28,11 @@
public final Task task;
public final TaskView taskView;
- public final DragView dragView;
+ public final Point tlOffset;
- public DragStartEvent(Task task, TaskView taskView, DragView dragView) {
+ public DragStartEvent(Task task, TaskView taskView, Point tlOffset) {
this.task = task;
this.taskView = taskView;
- this.dragView = dragView;
+ this.tlOffset = tlOffset;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
index f48883f..9d3a99f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
@@ -29,6 +29,8 @@
import android.widget.LinearLayout;
import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.model.TaskStack;
@@ -43,6 +45,7 @@
private RecyclerView mRecyclerView;
private RecentsHistoryAdapter mAdapter;
private boolean mIsVisible;
+ private Rect mSystemInsets = new Rect();
private Interpolator mFastOutSlowInInterpolator;
private Interpolator mFastOutLinearInInterpolator;
@@ -123,7 +126,8 @@
* Updates the system insets of this history view to the provided values.
*/
public void setSystemInsets(Rect systemInsets) {
- setPadding(systemInsets.left, systemInsets.top, systemInsets.right, systemInsets.bottom);
+ mSystemInsets.set(systemInsets.left, systemInsets.top, systemInsets.right, systemInsets.bottom);
+ requestLayout();
}
/**
@@ -142,6 +146,26 @@
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ RecentsConfiguration config = Recents.getConfiguration();
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ // Pad the view to align the history with the stack layout
+ Rect taskStackBounds = new Rect();
+ config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
+ mSystemInsets.right, new Rect() /* searchBarSpaceBounds */, taskStackBounds);
+ int stackWidthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
+ int stackHeightPadding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.recents_stack_top_padding);
+ mRecyclerView.setPadding(stackWidthPadding + mSystemInsets.left,
+ stackHeightPadding + mSystemInsets.top,
+ stackWidthPadding + mSystemInsets.right, mSystemInsets.bottom);
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
setSystemInsets(insets.getSystemWindowInsets());
return insets;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index de40a37..2b20c07 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -56,6 +56,7 @@
import android.util.MutableBoolean;
import android.util.Pair;
import android.view.Display;
+import android.view.IDockDividerVisibilityListener;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
@@ -409,7 +410,7 @@
return thumbnail;
}
- Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId);
+ Bitmap thumbnail = getThumbnail(taskId);
if (thumbnail != null) {
thumbnail.setHasAlpha(false);
// We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
@@ -429,8 +430,12 @@
/**
* Returns a task thumbnail from the activity manager
*/
- public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) {
- ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId);
+ public Bitmap getThumbnail(int taskId) {
+ if (mAm == null) {
+ return null;
+ }
+
+ ActivityManager.TaskThumbnail taskThumbnail = mAm.getTaskThumbnail(taskId);
if (taskThumbnail == null) return null;
Bitmap thumbnail = taskThumbnail.mainThumbnail;
@@ -847,4 +852,15 @@
e.printStackTrace();
}
}
+
+ public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ if (mWm == null) return;
+
+ try {
+ WindowManagerGlobal.getWindowManagerService().registerDockDividerVisibilityListener(
+ listener);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 5921d13..3bb89a3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -128,7 +128,7 @@
boolean isStackTask = true;
if (debugFlags.isHistoryEnabled()) {
boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
- isStackTask = !isFreeformTask && (!isHistoricalTask(t) ||
+ isStackTask = isFreeformTask || (!isHistoricalTask(t) ||
(t.lastActiveTime >= lastStackActiveTime &&
i >= (taskCount - MIN_NUM_TASKS)));
if (isStackTask && newLastStackActiveTime < 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index 945fdc1..271a2a0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -16,7 +16,6 @@
package com.android.systemui.recents.views;
-import android.animation.ObjectAnimator;
import android.graphics.Outline;
import android.graphics.Rect;
import android.util.IntProperty;
@@ -65,6 +64,15 @@
mCornerRadius = cornerRadius;
}
+ /**
+ * Resets the right and bottom clip for this view.
+ */
+ public void reset() {
+ mClipRect.setEmpty();
+ mSourceView.invalidateOutline();
+ updateClipBounds();
+ }
+
@Override
public void getOutline(View view, Outline outline) {
outline.setAlpha(mMinAlpha + mAlpha / (1f - mMinAlpha));
@@ -82,15 +90,6 @@
}
}
- /**
- * Animates the bottom clip.
- */
- public void animateClipBottom(int bottom) {
- ObjectAnimator animator = ObjectAnimator.ofInt(this, CLIP_BOTTOM, getClipBottom(), bottom);
- animator.setDuration(150);
- animator.start();
- }
-
/** Sets the bottom clip. */
public void setClipBottom(int bottom, boolean force) {
if (bottom != mClipRect.bottom || force) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java b/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
deleted file mode 100644
index 96dfaac..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.widget.ImageView;
-
-public class DragView extends ImageView {
-
- // The offset from the top-left of the dragBitmap
- Point mTopLeftOffset;
-
- public DragView(Context context, Bitmap dragBitmap, Point tlOffset) {
- super(context);
-
- mTopLeftOffset = tlOffset;
- setImageBitmap(dragBitmap);
- }
-
- public Point getTopLeftOffset() {
- return mTopLeftOffset;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index 90d62c1..a70b66d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -16,6 +16,7 @@
package com.android.systemui.recents.views;
+import android.graphics.Rect;
import android.util.Log;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
@@ -52,8 +53,8 @@
int numFreeformTasks = stackLayout.mNumFreeformTasks;
if (!freeformTasks.isEmpty()) {
// Calculate the cell width/height depending on the number of freeform tasks
- mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
- mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks /
+ mFreeformCellXCount = Math.max(1, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
+ mFreeformCellYCount = Math.max(1, (int) Math.ceil((float) numFreeformTasks /
mFreeformCellXCount));
// For now, make the cells square
mFreeformCellWidth = Math.min(stackLayout.mFreeformRect.width() / mFreeformCellXCount,
@@ -94,15 +95,21 @@
public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
TaskStackLayoutAlgorithm stackLayout) {
if (mTaskIndexMap.containsKey(task.key)) {
- // This is a freeform task, so lay it out in the freeform workspace
+ Rect taskRect = stackLayout.mTaskRect;
int taskIndex = mTaskIndexMap.get(task.key);
- int topOffset = (stackLayout.mFreeformRect.top - stackLayout.mTaskRect.top);
+ int topOffset = (stackLayout.mFreeformRect.top - taskRect.top);
int x = taskIndex % mFreeformCellXCount;
int y = taskIndex / mFreeformCellXCount;
- float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width();
- int scaleXOffset = (int) (((1f - scale) * stackLayout.mTaskRect.width()) / 2);
- int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2);
- transformOut.scale = scale * 0.9f;
+
+ int bitmapWidth = task.thumbnail.getWidth();
+ int bitmapHeight = task.thumbnail.getHeight();
+ float thumbnailScale = Math.min((float) mFreeformCellWidth / bitmapWidth,
+ (float) mFreeformCellHeight / bitmapHeight);
+ float thumbnailWidth = bitmapWidth * thumbnailScale;
+ float thumbnailHeight = bitmapHeight * thumbnailScale;
+ int scaleXOffset = (int) (((1f - thumbnailScale) * thumbnailWidth) / 2);
+ int scaleYOffset = (int) (((1f - thumbnailScale) * thumbnailHeight) / 2);
+ transformOut.scale = thumbnailScale * 0.9f;
transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
transformOut.translationY = topOffset + y * mFreeformCellHeight - scaleYOffset;
transformOut.translationZ = stackLayout.mMaxTranslationZ;
@@ -115,7 +122,6 @@
if (DEBUG) {
Log.d(TAG, "getTransform: " + task.key + ", " + transformOut);
}
-
return transformOut;
}
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 0557f65..551f067 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -81,7 +81,6 @@
RecentsTransitionHelper mTransitionHelper;
RecentsViewTouchHandler mTouchHandler;
- DragView mDragView;
TaskStack.DockState[] mVisibleDockStates = {
TaskStack.DockState.LEFT,
TaskStack.DockState.TOP,
@@ -341,13 +340,6 @@
mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
}
- if (mDragView != null) {
- Rect taskRect = mTaskStackView.mLayoutAlgorithm.mTaskRect;
- mDragView.measure(
- MeasureSpec.makeMeasureSpec(taskRect.width(), MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(taskRect.height(), MeasureSpec.AT_MOST));
- }
-
// Measure the history button with the full space above the stack, but width-constrained
// to the stack
Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
@@ -379,11 +371,6 @@
mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
}
- if (mDragView != null) {
- mDragView.layout(left, top, left + mDragView.getMeasuredWidth(),
- top + mDragView.getMeasuredHeight());
- }
-
// Layout the history button left-aligned with the stack, but offset from the top of the
// view
Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
@@ -461,10 +448,6 @@
/**** EventBus Events ****/
public final void onBusEvent(DragStartEvent event) {
- // Add the drag view
- mDragView = event.dragView;
- addView(mDragView);
-
updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
TaskStack.DockState.NONE.viewState.dockAreaAlpha);
}
@@ -480,65 +463,36 @@
}
public final void onBusEvent(final DragEndEvent event) {
- final Runnable cleanUpRunnable = new Runnable() {
- @Override
- public void run() {
- // Remove the drag view
- removeView(mDragView);
- mDragView = null;
- }
- };
-
// Animate the overlay alpha back to 0
updateVisibleDockRegions(null, -1);
- if (event.dropTarget == null) {
- // No drop targets for hit, so just animate the task back to its place
- event.postAnimationTrigger.increment();
- event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
- @Override
- public void run() {
- cleanUpRunnable.run();
- }
- });
- // Animate the task back to where it was before then clean up afterwards
- TaskViewTransform taskTransform = new TaskViewTransform();
- TaskStackLayoutAlgorithm layoutAlgorithm = mTaskStackView.getStackAlgorithm();
- layoutAlgorithm.getStackTransform(event.task,
- mTaskStackView.getScroller().getStackScroll(), taskTransform, null);
- event.dragView.animate()
- .scaleX(taskTransform.scale)
- .scaleY(taskTransform.scale)
- .translationX((layoutAlgorithm.mTaskRect.left - event.dragView.getLeft())
- + taskTransform.translationX)
- .translationY((layoutAlgorithm.mTaskRect.top - event.dragView.getTop())
- + taskTransform.translationY)
- .setDuration(175)
- .setInterpolator(mFastOutSlowInInterpolator)
- .withEndAction(event.postAnimationTrigger.decrementAsRunnable())
- .start();
-
- } else if (event.dropTarget instanceof TaskStack.DockState) {
+ // Handle the case where we drop onto a dock region
+ if (event.dropTarget instanceof TaskStack.DockState) {
final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
- // For now, just remove the drag view and the original task
- // TODO: Animate the task to the drop target rect before launching it above
- cleanUpRunnable.run();
+ // Remove the task after it is docked
+ if (event.taskView.isFocusedTask()) {
+ mTaskStackView.resetFocusedTask();
+ }
+ event.taskView.animate()
+ .alpha(0f)
+ .setDuration(150)
+ .setInterpolator(mFastOutLinearInInterpolator)
+ .setUpdateListener(null)
+ .setListener(null)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mTaskStackView.getStack().removeTask(event.task);
+ }
+ })
+ .withLayer()
+ .start();
// Dock the task and launch it
SystemServicesProxy ssp = Recents.getSystemServices();
ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
launchTask(event.task, null, INVALID_STACK_ID);
-
- } else {
- // We dropped on another drop target, so just add the cleanup to the post animation
- // trigger
- event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
- @Override
- public void run() {
- cleanUpRunnable.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 c7edc92..2920295 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -27,6 +27,7 @@
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
@@ -64,8 +65,8 @@
private Task mDragTask;
private TaskView mTaskView;
- private DragView mDragView;
+ private Point mTaskViewOffset = new Point();
private Point mDownPos = new Point();
private boolean mDragging;
@@ -111,20 +112,24 @@
/**** Events ****/
public final void onBusEvent(DragStartEvent event) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
mRv.getParent().requestDisallowInterceptTouchEvent(true);
mDragging = true;
mDragTask = event.task;
mTaskView = event.taskView;
- mDragView = event.dragView;
mDropTargets.clear();
- float x = mDownPos.x - mDragView.getTopLeftOffset().x;
- float y = mDownPos.y - mDragView.getTopLeftOffset().y;
- mDragView.setTranslationX(x);
- mDragView.setTranslationY(y);
+ int[] recentsViewLocation = new int[2];
+ mRv.getLocationInWindow(recentsViewLocation);
+ mTaskViewOffset.set(mTaskView.getLeft() - recentsViewLocation[0] + event.tlOffset.x,
+ mTaskView.getTop() - recentsViewLocation[1] + event.tlOffset.y);
+ float x = mDownPos.x - mTaskViewOffset.x;
+ float y = mDownPos.y - mTaskViewOffset.y;
+ mTaskView.setTranslationX(x);
+ mTaskView.setTranslationY(y);
RecentsConfiguration config = Recents.getConfiguration();
- if (!config.hasDockedTasks) {
+ if (!ssp.hasDockedTask()) {
// Add the dock state drop targets (these take priority)
TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
for (TaskStack.DockState dockState : dockStates) {
@@ -140,7 +145,6 @@
mDragging = false;
mDragTask = null;
mTaskView = null;
- mDragView = null;
mLastDropTarget = null;
}
@@ -160,8 +164,8 @@
int height = mRv.getMeasuredHeight();
float evX = ev.getX();
float evY = ev.getY();
- float x = evX - mDragView.getTopLeftOffset().x;
- float y = evY - mDragView.getTopLeftOffset().y;
+ float x = evX - mTaskViewOffset.x;
+ float y = evY - mTaskViewOffset.y;
DropTarget currentDropTarget = null;
for (DropTarget target : mDropTargets) {
@@ -176,8 +180,8 @@
currentDropTarget));
}
- mDragView.setTranslationX(x);
- mDragView.setTranslationY(y);
+ mTaskView.setTranslationX(x);
+ mTaskView.setTranslationY(y);
}
break;
}
@@ -187,7 +191,7 @@
ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(
mRv.getContext(), null, null, null);
postAnimationTrigger.increment();
- EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView, mDragView,
+ EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView,
mLastDropTarget, postAnimationTrigger));
postAnimationTrigger.decrement();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 30efd5f..0395e99 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -16,6 +16,10 @@
package com.android.systemui.recents.views;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.content.Context;
@@ -1420,7 +1424,12 @@
}
}
+ private AnimatorSet mDropAnimation;
+
public final void onBusEvent(DragStartEvent event) {
+ // Cancel the existing drop animation
+ Utilities.cancelAnimationWithoutCallbacks(mDropAnimation);
+
if (event.task.isFreeformTask()) {
// Animate to the front of the stack
mStackScroller.animateScroll(mStackScroller.getStackScroll(),
@@ -1441,56 +1450,84 @@
}
public final void onBusEvent(final DragEndEvent event) {
- if (event.dropTarget != mFreeformWorkspaceDropTarget &&
- event.dropTarget != mStackDropTarget) {
- return;
- }
- if (event.task.isFreeformTask() && event.dropTarget == mFreeformWorkspaceDropTarget) {
- // TODO: Animate back into view
- return;
- }
- if (!event.task.isFreeformTask() && event.dropTarget == mStackDropTarget) {
- // TODO: Animate back into view
+ // We don't handle drops on the dock regions
+ if (event.dropTarget instanceof TaskStack.DockState) {
return;
}
- // Move the task to the right position in the stack (ie. the front of the stack if freeform
- // or the front of the stack if fullscreen). Note, we MUST move the tasks before we update
- // their stack ids, otherwise, the keys will have changed.
- if (event.dropTarget == mFreeformWorkspaceDropTarget) {
- mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
- updateLayout(true);
- } else if (event.dropTarget == mStackDropTarget) {
- mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
- updateLayout(true);
+ boolean isFreeformTask = event.task.isFreeformTask();
+ boolean hasChangedStacks =
+ (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
+ (isFreeformTask && event.dropTarget == mStackDropTarget);
+ if (hasChangedStacks) {
+ ArrayList<Animator> animations = new ArrayList<>();
+
+ // Move the task to the right position in the stack (ie. the front of the stack if
+ // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
+ // before we update their stack ids, otherwise, the keys will have changed.
+ if (event.dropTarget == mFreeformWorkspaceDropTarget) {
+ mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
+ updateLayout(true);
+
+ // Update the clipping to match the scaled bitmap rect
+ TaskViewThumbnail thumbnailView = event.taskView.mThumbnailView;
+ float thumbnailScale = thumbnailView.computeThumbnailScale(true);
+ RectF bitmapRect = thumbnailView.getScaledBitmapRect(thumbnailScale);
+ AnimateableViewBounds viewBounds = event.taskView.getViewBounds();
+ int clipRight = (int) (thumbnailView.getMeasuredWidth() - bitmapRect.width());
+ int clipBottom = (int) (thumbnailView.getMeasuredHeight() - bitmapRect.height());
+ animations.add(ObjectAnimator.ofFloat(thumbnailView, TaskViewThumbnail.BITMAP_SCALE,
+ thumbnailView.getBitmapScale(), thumbnailScale));
+ animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_BOTTOM,
+ viewBounds.getClipBottom(), clipBottom));
+ animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_RIGHT,
+ viewBounds.getClipRight(), clipRight));
+ } else if (event.dropTarget == mStackDropTarget) {
+ mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
+ updateLayout(true);
+
+ // Reset the clipping when animating to the stack
+ TaskViewThumbnail thumbnailView = event.taskView.mThumbnailView;
+ float thumbnailScale = thumbnailView.computeThumbnailScale(false);
+ AnimateableViewBounds viewBounds = event.taskView.getViewBounds();
+ animations.add(ObjectAnimator.ofFloat(thumbnailView, TaskViewThumbnail.BITMAP_SCALE,
+ thumbnailView.getBitmapScale(), thumbnailScale));
+ animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_BOTTOM,
+ viewBounds.getClipBottom(), 0));
+ animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_RIGHT,
+ viewBounds.getClipRight(), 0));
+ }
+
+ // Move the task to the new stack in the system after the animation completes
+ event.postAnimationTrigger.increment();
+ event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+ @Override
+ public void run() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
+ }
+ });
+
+ // Animate the normal properties of the view
+ mDropAnimation = new AnimatorSet();
+ mDropAnimation.playTogether(animations);
+ mDropAnimation.setDuration(250);
+ mDropAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ mDropAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ event.postAnimationTrigger.decrement();
+ }
+ });
+ mDropAnimation.start();
}
event.postAnimationTrigger.increment();
- event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
- @Override
- public void run() {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
- }
- });
+ event.taskView.animate()
+ .withEndAction(event.postAnimationTrigger.decrementAsRunnable());
- // Animate the drag view to the new position
- mLayoutAlgorithm.getStackTransform(event.task, mStackScroller.getStackScroll(),
- mTmpTransform, null);
- event.dragView.animate()
- .scaleX(mTmpTransform.scale)
- .scaleY(mTmpTransform.scale)
- .translationX((mLayoutAlgorithm.mTaskRect.left - event.dragView.getLeft())
- + mTmpTransform.translationX)
- .translationY((mLayoutAlgorithm.mTaskRect.top - event.dragView.getTop())
- + mTmpTransform.translationY)
- .setDuration(175)
- .setInterpolator(mFastOutSlowInInterpolator)
- .withEndAction(event.postAnimationTrigger.decrementAsRunnable())
- .start();
-
- // Animate the other views into place
- requestSynchronizeStackViewsWithModel(175);
+ // Animate the tack view back into position
+ requestSynchronizeStackViewsWithModel(250);
}
public final void onBusEvent(StackViewScrolledEvent event) {
@@ -1522,7 +1559,13 @@
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
- tv.animate().alpha(0f).setDuration(200).start();
+ tv.animate()
+ .alpha(0f)
+ .setDuration(200)
+ .setUpdateListener(null)
+ .setListener(null)
+ .withLayer()
+ .start();
}
}
@@ -1531,7 +1574,13 @@
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
- tv.animate().alpha(1f).setDuration(200).start();
+ tv.animate()
+ .alpha(1f)
+ .setDuration(200)
+ .setUpdateListener(null)
+ .setListener(null)
+ .withLayer()
+ .start();
}
}
@@ -1543,7 +1592,9 @@
Task task = tv.getTask();
// Reset the previously focused task before it is removed from the stack
- resetFocusedTask();
+ if (tv.isFocusedTask()) {
+ resetFocusedTask();
+ }
// Announce for accessibility
tv.announceForAccessibility(getContext().getString(
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 6db2eb9..f2c89e6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -22,8 +22,6 @@
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
@@ -244,6 +242,8 @@
void resetViewProperties() {
setDim(0);
setLayerType(View.LAYER_TYPE_NONE, null);
+ setVisibility(View.VISIBLE);
+ getViewBounds().reset();
TaskViewTransform.reset(this);
if (mActionButtonView != null) {
mActionButtonView.setScaleX(1f);
@@ -251,18 +251,6 @@
mActionButtonView.setAlpha(1f);
mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
}
- setVisibility(View.VISIBLE);
- }
-
- /**
- * When we are un/filtering, this method will set up the transform that we are animating to,
- * in order to hide the task.
- */
- void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
- // Fade the view out and slide it away
- toTransform.alpha = 0f;
- toTransform.translationY += 200;
- toTransform.translationZ = 0;
}
/** Prepares this task view for the enter-recents animations. This is called earlier in the
@@ -296,8 +284,6 @@
}
// Apply the current dim
setDim(initialDim);
- // Prepare the thumbnail view alpha
- mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask);
}
/** Animates this task view as it enters recents */
@@ -409,6 +395,7 @@
.translationY(ctx.offscreenTranslationY)
.setStartDelay(0)
.setUpdateListener(null)
+ .setListener(null)
.setInterpolator(mFastOutLinearInInterpolator)
.setDuration(taskViewExitToHomeDuration)
.withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
@@ -425,9 +412,6 @@
R.dimen.recents_task_view_affiliate_group_enter_offset);
if (isLaunchingTask) {
- // Animate the thumbnail alpha back into full opacity for the window animation out
- mThumbnailView.startLaunchTaskAnimation(postAnimRunnable);
-
// Animate the dim
if (mDimAlpha > 0) {
ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
@@ -448,10 +432,11 @@
.setStartDelay(0)
.setDuration(taskViewExitToAppDuration)
.setInterpolator(mFastOutLinearInInterpolator)
+ .withEndAction(postAnimRunnable)
.start();
} else {
// Hide the dismiss button
- mHeaderView.startLaunchTaskDismissAnimation();
+ mHeaderView.startLaunchTaskDismissAnimation(postAnimRunnable);
// If this is another view in the task grouping and is in front of the launch task,
// animate it away first
if (occludesLaunchTarget) {
@@ -459,6 +444,7 @@
.translationY(getTranslationY() + taskViewAffiliateGroupEnterOffset)
.setStartDelay(0)
.setUpdateListener(null)
+ .setListener(null)
.setInterpolator(mFastOutLinearInInterpolator)
.setDuration(taskViewExitToAppDuration)
.start();
@@ -480,6 +466,7 @@
.alpha(0f)
.setStartDelay(delay)
.setUpdateListener(null)
+ .setListener(null)
.setInterpolator(mFastOutSlowInInterpolator)
.setDuration(taskViewRemoveAnimDuration)
.withEndAction(new Runnable() {
@@ -637,7 +624,6 @@
mIsFocused = isFocused;
mIsFocusAnimated = animated;
mHeaderView.onTaskViewFocusChanged(isFocused, animated);
- mThumbnailView.onFocusChanged(isFocused);
if (isFocused) {
if (requestViewFocus && !isFocused()) {
requestFocus();
@@ -688,21 +674,13 @@
@Override
public void onTaskDataLoaded() {
SystemServicesProxy ssp = Recents.getSystemServices();
- RecentsConfiguration config = Recents.getConfiguration();
if (mThumbnailView != null && mHeaderView != null) {
// Bind each of the views to the new task data
mThumbnailView.rebindToTask(mTask);
mHeaderView.rebindToTask(mTask);
// Rebind any listeners
mActionButtonView.setOnClickListener(this);
-
- // Only enable long-click if we have a freeform workspace to drag to/from, or if we
- // aren't already docked
- if (ssp.hasFreeformWorkspaceSupport() || !config.hasDockedTasks) {
- setOnLongClickListener(this);
- } else {
- setOnLongClickListener(null);
- }
+ setOnLongClickListener(this);
}
mTaskDataLoaded = true;
}
@@ -742,58 +720,27 @@
@Override
public boolean onLongClick(View v) {
- if (v == this) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (v == this && !ssp.hasDockedTask()) {
// Start listening for drag events
setClipViewInStack(false);
+ // Enlarge the view slightly
final float finalScale = getScaleX() * 1.05f;
- final int width = getWidth();
- final int height = getHeight();
- Bitmap dragBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas c = new Canvas(dragBitmap);
- mThumbnailView.draw(c);
- mHeaderView.draw(c);
- c.setBitmap(null);
+ animate()
+ .scaleX(finalScale)
+ .scaleY(finalScale)
+ .setDuration(175)
+ .setUpdateListener(null)
+ .setListener(null)
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .start();
- // The downTouchPos is relative to the currently transformed TaskView, but we will be
- // dragging a copy of the full task view, which makes it easier for us to animate them
- // when the user drops
- mDownTouchPos.x += ((1f - getScaleX()) * width) / 2;
- mDownTouchPos.y += ((1f - getScaleY()) * height) / 2;
+ mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2;
+ mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2;
- // Initiate the drag
- final DragView dragView = new DragView(getContext(), dragBitmap, mDownTouchPos);
- dragView.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRect(0, 0, width, height);
- }
- });
- dragView.setScaleX(getScaleX());
- dragView.setScaleY(getScaleY());
- dragView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- // Hide this task view after the drag view is attached
- setVisibility(View.INVISIBLE);
- // Animate the alpha slightly to indicate dragging
- dragView.setElevation(getElevation());
- dragView.setTranslationZ(getTranslationZ());
- dragView.animate()
- .scaleX(finalScale)
- .scaleY(finalScale)
- .setDuration(175)
- .setInterpolator(mFastOutSlowInInterpolator)
- .start();
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- // Do nothing
- }
- });
EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
- EventBus.getDefault().send(new DragStartEvent(mTask, this, dragView));
+ EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos));
return true;
}
return false;
@@ -806,9 +753,6 @@
event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
- // Show this task view
- setVisibility(View.VISIBLE);
-
// Animate the drag view back from where it is, to the view location, then after
// it returns, update the clip state
setClipViewInStack(true);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 76c6691..85b4b9b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -284,7 +284,7 @@
}
/** Animates this task bar dismiss button when launching a task. */
- void startLaunchTaskDismissAnimation() {
+ void startLaunchTaskDismissAnimation(final Runnable postAnimationRunanble) {
if (mDismissButton.getVisibility() == View.VISIBLE) {
int taskViewExitToAppDuration = mContext.getResources().getInteger(
R.integer.recents_task_exit_to_app_duration);
@@ -294,6 +294,7 @@
.setStartDelay(0)
.setInterpolator(mFastOutSlowInInterpolator)
.setDuration(taskViewExitToAppDuration)
+ .withEndAction(postAnimationRunanble)
.start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index b3d263e..c288afb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -16,9 +16,6 @@
package com.android.systemui.recents.views;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
@@ -31,11 +28,12 @@
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.Property;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.android.systemui.R;
-import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
@@ -45,6 +43,19 @@
*/
public class TaskViewThumbnail extends View {
+ public static final Property<TaskViewThumbnail, Float> BITMAP_SCALE =
+ new FloatProperty<TaskViewThumbnail>("bitmapScale") {
+ @Override
+ public void setValue(TaskViewThumbnail object, float scale) {
+ object.setBitmapScale(scale);
+ }
+
+ @Override
+ public Float get(TaskViewThumbnail object) {
+ return object.getBitmapScale();
+ }
+ };
+
private Task mTask;
// Drawing
@@ -60,18 +71,6 @@
Interpolator mFastOutSlowInInterpolator;
- // Thumbnail alpha
- float mThumbnailAlpha;
- ValueAnimator mThumbnailAlphaAnimator;
- ValueAnimator.AnimatorUpdateListener mThumbnailAlphaUpdateListener
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mThumbnailAlpha = (float) animation.getAnimatedValue();
- updateThumbnailPaintFilter();
- }
- };
-
// Task bar clipping, the top of this thumbnail can be clipped against the opaque header
// bar that overlaps this thumbnail
View mTaskBar;
@@ -105,17 +104,11 @@
}
@Override
- protected void onFinishInflate() {
- mThumbnailAlpha = getResources().getFloat(R.dimen.recents_task_view_thumbnail_alpha);
- updateThumbnailPaintFilter();
- }
-
- @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
mLayoutRect.set(0, 0, getWidth(), getHeight());
- updateThumbnailScale();
+ setBitmapScale(computeThumbnailScale(mTask != null ? mTask.isFreeformTask() : false));
}
}
@@ -137,12 +130,15 @@
Shader.TileMode.CLAMP);
mDrawPaint.setShader(mBitmapShader);
mBitmapRect.set(0, 0, bm.getWidth(), bm.getHeight());
- updateThumbnailScale();
} else {
mBitmapShader = null;
mDrawPaint.setShader(null);
}
- updateThumbnailPaintFilter();
+ if (mTask != null) {
+ setBitmapScale(computeThumbnailScale(mTask != null ? mTask.isFreeformTask() : false));
+ } else {
+ setBitmapScale(1f);
+ }
}
/** Updates the paint to draw the thumbnail. */
@@ -150,36 +146,61 @@
if (mInvisible) {
return;
}
- int mul = (int) ((1.0f - mDimAlpha) * mThumbnailAlpha * 255);
- int add = (int) ((1.0f - mDimAlpha) * (1 - mThumbnailAlpha) * 255);
+ int mul = (int) ((1.0f - mDimAlpha) * 255);
if (mBitmapShader != null) {
mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul));
- mLightingColorFilter.setColorAdd(Color.argb(0, add, add, add));
mDrawPaint.setColorFilter(mLightingColorFilter);
mDrawPaint.setColor(0xffffffff);
} else {
- int grey = mul + add;
+ int grey = mul;
mDrawPaint.setColorFilter(null);
mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
}
invalidate();
}
- /** Updates the thumbnail shader's scale transform. */
- void updateThumbnailScale() {
- if (mBitmapShader != null) {
- if (mTask.isFreeformTask()) {
- // For freeform tasks, we scale the bitmap rect to fit in the layout rect
- mBitmapScale = Math.min(mLayoutRect.width() / mBitmapRect.width(),
- mLayoutRect.height() / mBitmapRect.height());
- } else {
- // For stack tasks, we scale the bitmap to fit the width
- mBitmapScale = Math.max(1f, mLayoutRect.width() / mBitmapRect.width());
- }
+ /**
+ * Returns the scale to apply to a thumbnail bitmap relative to this view rect.
+ */
+ public float computeThumbnailScale(boolean isFreeformTask) {
+ if (isFreeformTask) {
+ // For freeform tasks, we scale the bitmap rect to fit in the layout rect
+ return Math.min(mLayoutRect.width() / mBitmapRect.width(),
+ mLayoutRect.height() / mBitmapRect.height());
+ } else {
+ // For stack tasks, we scale the bitmap to fit the width
+ return Math.max(1f, mLayoutRect.width() / mBitmapRect.width());
+ }
+ }
+ /**
+ * Returns the scaled bitmap rect.
+ */
+ public RectF getScaledBitmapRect(float scale) {
+ RectF scaledBitmapRect = new RectF(mBitmapRect);
+ scaledBitmapRect.left *= scale;
+ scaledBitmapRect.top *= scale;
+ scaledBitmapRect.right *= scale;
+ scaledBitmapRect.bottom *= scale;
+ return scaledBitmapRect;
+ }
+
+ /**
+ * Sets the scale of the bitmap relative to this view.
+ */
+ public void setBitmapScale(float scale) {
+ if (mBitmapShader != null) {
+ mBitmapScale = scale;
mScaleMatrix.setScale(mBitmapScale, mBitmapScale);
mBitmapShader.setLocalMatrix(mScaleMatrix);
}
+ if (!mInvisible) {
+ invalidate();
+ }
+ }
+
+ public float getBitmapScale() {
+ return mBitmapScale;
}
/** Updates the clip rect based on the given task bar. */
@@ -227,67 +248,4 @@
mTask = null;
setThumbnail(null);
}
-
- /** Handles focus changes. */
- void onFocusChanged(boolean focused) {
- if (focused) {
- if (Float.compare(getAlpha(), 1f) != 0) {
- startFadeAnimation(1f, 150, null);
- }
- } else {
- float taskViewThumbnailAlpha = getResources().getFloat(
- R.dimen.recents_task_view_thumbnail_alpha);
- if (Float.compare(getAlpha(), taskViewThumbnailAlpha) != 0) {
- startFadeAnimation(taskViewThumbnailAlpha, 150, null);
- }
- }
- }
-
- /**
- * Prepares for the enter recents animation, this gets called before the the view
- * is first visible and will be followed by a startEnterRecentsAnimation() call.
- */
- void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask) {
- if (isTaskViewLaunchTargetTask) {
- mThumbnailAlpha = 1f;
- } else {
- mThumbnailAlpha = getResources().getFloat(
- R.dimen.recents_task_view_thumbnail_alpha);
- }
- updateThumbnailPaintFilter();
- }
-
- /** Animates this task thumbnail as it enters Recents. */
- void startEnterRecentsAnimation(Runnable postAnimRunnable) {
- float taskViewThumbnailAlpha = getResources().getFloat(
- R.dimen.recents_task_view_thumbnail_alpha);
- startFadeAnimation(taskViewThumbnailAlpha,
- getResources().getInteger(R.integer.recents_task_enter_from_app_duration),
- postAnimRunnable);
- }
-
- /** Animates this task thumbnail as it exits Recents. */
- void startLaunchTaskAnimation(Runnable postAnimRunnable) {
- int taskViewExitToAppDuration = mContext.getResources().getInteger(
- R.integer.recents_task_exit_to_app_duration);
- startFadeAnimation(1f, taskViewExitToAppDuration, postAnimRunnable);
- }
-
- /** Starts a new thumbnail alpha animation. */
- void startFadeAnimation(float finalAlpha, int duration, final Runnable postAnimRunnable) {
- Utilities.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);
- mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);
- mThumbnailAlphaAnimator.setDuration(duration);
- mThumbnailAlphaAnimator.setInterpolator(mFastOutSlowInInterpolator);
- mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);
- if (postAnimRunnable != null) {
- mThumbnailAlphaAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- postAnimRunnable.run();
- }
- });
- }
- mThumbnailAlphaAnimator.start();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 174ff33..6d43f9c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -122,6 +122,7 @@
} else {
anim.setUpdateListener(null);
}
+ anim.setListener(null);
anim.setStartDelay(startDelay)
.setDuration(duration)
.setInterpolator(interp)
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 50e010f..6ff7a3e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -17,10 +17,14 @@
package com.android.systemui.stackdivider;
import android.content.res.Configuration;
+import android.view.IDockDividerVisibilityListener;
import android.view.LayoutInflater;
+import android.view.View;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -33,6 +37,8 @@
private int mDividerWindowWidth;
private DividerWindowManager mWindowManager;
private DividerView mView;
+ private DockDividerVisibilityListener mDockDividerVisibilityListener;
+ private boolean mVisible = false;
@Override
public void start() {
@@ -41,6 +47,9 @@
com.android.internal.R.dimen.docked_stack_divider_thickness);
update(mContext.getResources().getConfiguration());
putComponent(Divider.class, this);
+ mDockDividerVisibilityListener = new DockDividerVisibilityListener();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.registerDockDividerVisibilityListener(mDockDividerVisibilityListener);
}
@Override
@@ -56,6 +65,7 @@
private void addDivider(Configuration configuration) {
mView = (DividerView)
LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
+ mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
final int width = landscape ? mDividerWindowWidth : MATCH_PARENT;
final int height = landscape ? MATCH_PARENT : mDividerWindowWidth;
@@ -71,4 +81,23 @@
removeDivider();
addDivider(configuration);
}
+
+ private void updateVisibility(final boolean visible) {
+ mView.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mVisible != visible) {
+ mVisible = visible;
+ mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+ });
+ }
+
+ class DockDividerVisibilityListener extends IDockDividerVisibilityListener.Stub {
+ @Override
+ public void onDockDividerVisibilityChanged(boolean visible) {
+ updateVisibility(visible);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 723989a..5d4c64e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -40,7 +40,6 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
-import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
@@ -193,6 +192,7 @@
protected IDreamManager mDreamManager;
PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ protected int mRowMinHeightLegacy;
protected int mRowMinHeight;
protected int mRowMaxHeight;
@@ -887,15 +887,6 @@
entry.row.setShowingLegacyBackground(true);
entry.legacy = true;
}
- } else {
- // Using platform templates
- final int color = sbn.getNotification().color;
- if (isMediaNotification(entry)) {
- entry.row.setTintColor(color == Notification.COLOR_DEFAULT
- ? mContext.getColor(
- R.color.notification_material_background_media_default_color)
- : color);
- }
}
if (entry.icon != null) {
@@ -1299,6 +1290,17 @@
}
}
+ /**
+ * Called when the panel was layouted expanded for the first time after being collapsed.
+ */
+ public void onPanelExpandedAndLayouted() {
+ if (mState == StatusBarState.KEYGUARD) {
+ // Since the number of notifications is determined based on the height of the view, we
+ // need to update them.
+ updateRowStates();
+ }
+ }
+
protected class H extends Handler {
public void handleMessage(Message m) {
switch (m.what) {
@@ -1337,7 +1339,6 @@
PackageManager pmUser = getPackageManagerForUser(mContext,
entry.notification.getUser().getIdentifier());
- int maxHeight = mRowMaxHeight;
final StatusBarNotification sbn = entry.notification;
entry.cacheContentViews(mContext, null);
@@ -1498,18 +1499,6 @@
Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic);
icon.setImageDrawable(iconDrawable);
- if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP
- || mNotificationColorUtil.isGrayscaleIcon(iconDrawable)) {
- icon.setBackgroundResource(
- com.android.internal.R.drawable.notification_icon_legacy_bg);
- int padding = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_large_icon_circle_padding);
- icon.setPadding(padding, padding, padding, padding);
- if (sbn.getNotification().color != Notification.COLOR_DEFAULT) {
- icon.getBackground().setColorFilter(
- sbn.getNotification().color, PorterDuff.Mode.SRC_ATOP);
- }
- }
if (profileBadge != null) {
Drawable profileDrawable = mContext.getPackageManager().getUserBadgeForDensity(
@@ -1536,11 +1525,6 @@
R.style.TextAppearance_Material_Notification_Parenthetical);
}
- int topPadding = Notification.Builder.calculateTopPadding(mContext,
- false /* hasThreeLines */,
- mContext.getResources().getConfiguration().fontScale);
- title.setPadding(0, topPadding, 0, 0);
-
contentContainerPublic.setContractedChild(publicViewLocal);
entry.autoRedacted = true;
}
@@ -1553,7 +1537,7 @@
}
}
entry.row = row;
- entry.row.setHeightRange(mRowMinHeight, maxHeight);
+ updateNotificationHeightRange(entry);
entry.row.setOnActivatedListener(this);
entry.row.setExpandable(bigContentViewLocal != null);
@@ -1566,11 +1550,19 @@
row.setUserExpanded(userExpanded);
}
row.setUserLocked(userLocked);
- row.setEntry(entry);
+ row.updateStatusBarNotification(entry.notification);
applyRemoteInput(entry);
return true;
}
+ private void updateNotificationHeightRange(Entry entry) {
+ boolean customView = entry.getContentView().getId()
+ != com.android.internal.R.id.status_bar_latest_event_content;
+ boolean beforeN = entry.targetSdk < Build.VERSION_CODES.N;
+ int minHeight = customView && beforeN ? mRowMinHeightLegacy : mRowMinHeight;
+ entry.row.setHeightRange(minHeight, mRowMaxHeight);
+ }
+
/**
* Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
* via first-class API.
@@ -1982,15 +1974,15 @@
}
/**
+ * @param recompute wheter the number should be recomputed
* @return The number of notifications we show on Keyguard.
*/
- protected abstract int getMaxKeyguardNotifications();
+ protected abstract int getMaxKeyguardNotifications(boolean recompute);
/**
* Updates expanded, dimmed and locked states of notification rows.
*/
protected void updateRowStates() {
- int maxKeyguardNotifications = getMaxKeyguardNotifications();
mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
@@ -1998,6 +1990,10 @@
int visibleNotifications = 0;
boolean onKeyguard = mState == StatusBarState.KEYGUARD;
+ int maxNotifications = 0;
+ if (onKeyguard) {
+ maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+ }
for (int i = 0; i < N; i++) {
NotificationData.Entry entry = activeNotifications.get(i);
if (onKeyguard) {
@@ -2013,7 +2009,7 @@
== View.VISIBLE;
boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) ||
- (onKeyguard && (visibleNotifications >= maxKeyguardNotifications
+ (onKeyguard && (visibleNotifications >= maxNotifications
&& !childWithVisibleSummary
|| !showOnKeyguard))) {
entry.row.setVisibility(View.GONE);
@@ -2188,8 +2184,7 @@
// update the contentIntent
mNotificationClicker.register(entry.row, sbn);
- entry.row.setEntry(entry);
- entry.row.notifyContentUpdated();
+ entry.row.updateStatusBarNotification(entry.notification);
entry.row.resetHeight();
applyRemoteInput(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 14051916..8570198 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -120,12 +120,12 @@
if (mLastHeight > mMinDragDistance) {
if (!mDraggedFarEnough) {
mDraggedFarEnough = true;
- mDragDownCallback.onThresholdReached();
+ mDragDownCallback.onCrossedThreshold(true);
}
} else {
if (mDraggedFarEnough) {
mDraggedFarEnough = false;
- mDragDownCallback.onDragDownReset();
+ mDragDownCallback.onCrossedThreshold(false);
}
}
return true;
@@ -236,7 +236,12 @@
*/
boolean onDraggedDown(View startingChild, int dragLengthY);
void onDragDownReset();
- void onThresholdReached();
+
+ /**
+ * The user has dragged either above or below the threshold
+ * @param above whether he dragged above it
+ */
+ void onCrossedThreshold(boolean above);
void onTouchSlopExceeded();
void setEmptyDragAmount(float amount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 3603900..81e20aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -25,16 +25,17 @@
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewStub;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.LinearInterpolator;
import android.widget.Chronometer;
import android.widget.ImageView;
+import android.widget.RemoteViews;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.statusbar.notification.NotificationHeaderView;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
import com.android.systemui.statusbar.stack.StackScrollState;
@@ -89,22 +90,31 @@
private boolean mIsHeadsUp;
private boolean mLastChronometerRunning = true;
private NotificationHeaderView mNotificationHeader;
- private ViewStub mNotificationHeaderStub;
private ViewStub mChildrenContainerStub;
private NotificationGroupManager mGroupManager;
private boolean mChildrenExpanded;
private boolean mIsSummaryWithChildren;
private NotificationChildrenContainer mChildrenContainer;
private ViewStub mGutsStub;
- private boolean mHasNotificationHeader;
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
private FalsingManager mFalsingManager;
private boolean mJustClicked;
- private NotificationData.Entry mEntry;
+ private boolean mIconAnimationRunning;
private boolean mShowNoBackground;
private ExpandableNotificationRow mNotificationParent;
+ private OnClickListener mExpandClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
+ mGroupManager.toggleGroupExpansion(mStatusBarNotification);
+ } else {
+ setUserExpanded(!isExpanded());
+ notifyHeightChanged(true);
+ }
+ }
+ };
public NotificationContentView getPrivateLayout() {
return mPrivateLayout;
@@ -117,6 +127,16 @@
public void setIconAnimationRunning(boolean running) {
setIconAnimationRunning(running, mPublicLayout);
setIconAnimationRunning(running, mPrivateLayout);
+ setIconAnimationRunningForChild(running, mNotificationHeader);
+ if (mIsSummaryWithChildren) {
+ List<ExpandableNotificationRow> notificationChildren =
+ mChildrenContainer.getNotificationChildren();
+ for (int i = 0; i < notificationChildren.size(); i++) {
+ ExpandableNotificationRow child = notificationChildren.get(i);
+ child.setIconAnimationRunning(running);
+ }
+ }
+ mIconAnimationRunning = running;
}
private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
@@ -161,10 +181,17 @@
}
}
- private void setStatusBarNotification(StatusBarNotification statusBarNotification) {
+ public void updateStatusBarNotification(StatusBarNotification statusBarNotification) {
mStatusBarNotification = statusBarNotification;
- mPrivateLayout.setStatusBarNotification(statusBarNotification);
+ mPrivateLayout.onNotificationUpdated(statusBarNotification);
+ mPublicLayout.onNotificationUpdated(statusBarNotification);
updateVetoButton();
+ if (mIsSummaryWithChildren) {
+ recreateNotificationHeader();
+ }
+ if (mIconAnimationRunning) {
+ setIconAnimationRunning(true);
+ }
onChildrenCountChanged();
}
@@ -381,11 +408,6 @@
}
}
- public void setEntry(NotificationData.Entry entry) {
- mEntry = entry;
- setStatusBarNotification(entry.notification);
- }
-
public CharSequence getSubText() {
Notification notification = mStatusBarNotification.getNotification();
CharSequence subText = notification.extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT);
@@ -452,6 +474,8 @@
super.onFinishInflate();
mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
+ mPrivateLayout.setExpandClickListener(mExpandClickListener);
+ mPublicLayout.setExpandClickListener(mExpandClickListener);
mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
@@ -462,15 +486,6 @@
mGutsStub = null;
}
});
- mNotificationHeaderStub = (ViewStub) findViewById(R.id.notification_header_stub);
- mNotificationHeaderStub.setOnInflateListener(new ViewStub.OnInflateListener() {
- @Override
- public void onInflate(ViewStub stub, View inflated) {
- mNotificationHeader = (NotificationHeaderView) inflated;
- mNotificationHeader.setGroupManager(mGroupManager);
- mNotificationHeader.bind(mEntry);
- }
- });
mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@@ -494,6 +509,7 @@
return;
}
mChildrenContainer.setVisibility(mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
+ mNotificationHeader.setVisibility(mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
mPrivateLayout.setVisibility(!mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
}
@@ -523,6 +539,8 @@
public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
mRowMinHeight = rowMinHeight;
mMaxViewHeight = rowMaxHeight;
+ mPrivateLayout.setSmallHeight(mRowMinHeight);
+ mPublicLayout.setSmallHeight(mRowMinHeight);
}
public boolean isExpandable() {
@@ -534,6 +552,7 @@
public void setExpandable(boolean expandable) {
mExpandable = expandable;
+ mPrivateLayout.updateExpandButtons(isExpandable());
}
/**
@@ -654,8 +673,7 @@
if (mSensitive && mHideSensitiveForIntrinsicHeight) {
return mRowMinHeight;
} else if (mIsSummaryWithChildren && !mOnKeyguard) {
- return mChildrenContainer.getIntrinsicHeight()
- + mNotificationHeader.getHeight();
+ return mChildrenContainer.getIntrinsicHeight();
} else if (mIsHeadsUp) {
if (inExpansionState) {
return Math.max(mMaxExpandHeight, mHeadsUpHeight);
@@ -681,12 +699,18 @@
}
private void onChildrenCountChanged() {
- mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
+ mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
&& mGroupManager.hasGroupChildren(mStatusBarNotification);
- if (mIsSummaryWithChildren && mChildrenContainer == null) {
- mChildrenContainerStub.inflate();
+ if (mIsSummaryWithChildren) {
+ if (mChildrenContainer == null) {
+ mChildrenContainerStub.inflate();
+ }
+ if (mNotificationHeader == null) {
+ recreateNotificationHeader();
+ }
}
- updateNotificationHeader();
+ mPrivateLayout.updateExpandButtons(isExpandable());
+ updateHeaderChildCount();
updateChildrenVisibility(true);
}
@@ -771,6 +795,7 @@
animateShowingPublic(delay, duration);
}
+ mPrivateLayout.updateExpandButtons(isExpandable());
updateVetoButton();
mShowingPublicInitialized = true;
}
@@ -811,23 +836,11 @@
}
}
- public void updateNotificationHeader() {
- boolean hasHeader = hasNotificationHeader();
- if (hasHeader != mHasNotificationHeader) {
- if (hasHeader) {
- if (mNotificationHeader == null) {
- mNotificationHeaderStub.inflate();
- }
- mNotificationHeader.setVisibility(View.VISIBLE);
- } else if (mNotificationHeader != null) {
- mNotificationHeader.setVisibility(View.GONE);
- }
- notifyHeightChanged(true /* needsAnimation */);
+ public void updateHeaderChildCount() {
+ if (mIsSummaryWithChildren) {
+ mNotificationHeader.setChildCount(
+ mChildrenContainer.getNotificationChildren().size());
}
- if (hasHeader) {
- mNotificationHeader.bind(mEntry);
- }
- mHasNotificationHeader = hasHeader;
}
public static void applyTint(View v, int color) {
@@ -876,8 +889,7 @@
@Override
public int getMaxContentHeight() {
if (mIsSummaryWithChildren && !mShowingPublic) {
- return mChildrenContainer.getMaxContentHeight()
- + mNotificationHeader.getHeight();
+ return mChildrenContainer.getMaxContentHeight();
}
NotificationContentView showingLayout = getShowingLayout();
return showingLayout.getMaxHeight();
@@ -885,15 +897,19 @@
@Override
public int getMinHeight() {
- if (mIsSummaryWithChildren && !mOnKeyguard) {
- return mChildrenContainer.getMinHeight()
- + mNotificationHeader.getHeight();
- }
NotificationContentView showingLayout = getShowingLayout();
return showingLayout.getMinHeight();
}
@Override
+ public int getMinExpandHeight() {
+ if (mIsSummaryWithChildren && !mOnKeyguard) {
+ return mChildrenContainer.getMinHeight();
+ }
+ return getMinHeight();
+ }
+
+ @Override
protected boolean shouldLimitViewHeight() {
return !mIsSummaryWithChildren;
}
@@ -908,9 +924,21 @@
}
}
- public void notifyContentUpdated() {
- mPublicLayout.notifyContentUpdated();
- mPrivateLayout.notifyContentUpdated();
+ private void recreateNotificationHeader() {
+ final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
+ getStatusBarNotification().getNotification());
+ final RemoteViews header = builder.makeNotificationHeader();
+ if (mNotificationHeader == null) {
+ mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
+ final View expandButton = mNotificationHeader.findViewById(
+ com.android.internal.R.id.expand_button);
+ expandButton.setVisibility(VISIBLE);
+ mNotificationHeader.setOnClickListener(mExpandClickListener);
+ addView(mNotificationHeader);
+ } else {
+ header.reapply(getContext(), mNotificationHeader);
+ }
+ updateHeaderChildCount();
}
public boolean isMaxExpandHeightInitialized() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index af59ac7..51602e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -195,6 +195,15 @@
}
/**
+ * @return The minimum height this child chan be expanded to. Note that this might be different
+ * than {@link #getMinHeight()} because some elements can't be collapsed by an expand gesture
+ * to it's absolute minimal height
+ */
+ public int getMinExpandHeight() {
+ return getHeight();
+ }
+
+ /**
* Sets the notification as dimmed. The default implementation does nothing.
*
* @param dimmed Whether the notification should be dimmed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 6d13947..71904fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -48,8 +48,8 @@
*/
public class KeyguardIndicationController {
- private static final String TAG = "KeyguardIndicationController";
- private static final boolean DEBUG_CHARGING_CURRENT = false;
+ private static final String TAG = "KeyguardIndication";
+ private static final boolean DEBUG_CHARGING_SPEED = false;
private static final int MSG_HIDE_TRANSIENT = 1;
private static final int MSG_CLEAR_FP_MSG = 2;
@@ -72,7 +72,7 @@
private boolean mPowerPluggedIn;
private boolean mPowerCharged;
private int mChargingSpeed;
- private int mChargingCurrent;
+ private int mChargingWattage;
private String mMessageToShowOnScreenOn;
public KeyguardIndicationController(Context context, KeyguardIndicationTextView textView,
@@ -173,8 +173,8 @@
}
if (mPowerPluggedIn) {
String indication = computePowerIndication();
- if (DEBUG_CHARGING_CURRENT) {
- indication += ", " + (mChargingCurrent / 1000) + " mA";
+ if (DEBUG_CHARGING_SPEED) {
+ indication += ", " + (mChargingWattage / 1000) + " mW";
}
return indication;
}
@@ -231,7 +231,7 @@
|| status.status == BatteryManager.BATTERY_STATUS_FULL;
mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull;
mPowerCharged = status.isCharged();
- mChargingCurrent = status.maxChargingCurrent;
+ mChargingWattage = status.maxChargingWattage;
mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold);
updateIndication();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java
deleted file mode 100644
index 91e5404..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java
+++ /dev/null
@@ -1,36 +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.systemui.statusbar;
-
-import android.content.Context;
-import android.view.View;
-
-/**
- * Wraps a big media narrow notification template layout.
- */
-public class NotificationBigMediaNarrowViewWrapper extends NotificationMediaViewWrapper {
-
- protected NotificationBigMediaNarrowViewWrapper(Context ctx,
- View view) {
- super(ctx, view);
- }
-
- @Override
- public boolean needsRoundRectClipping() {
- return true;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java
deleted file mode 100644
index ffe0cd1..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.view.View;
-
-/**
- * Wraps a notification view inflated from a big picture style template.
- */
-public class NotificationBigPictureViewWrapper extends NotificationTemplateViewWrapper {
-
- protected NotificationBigPictureViewWrapper(Context ctx, View view) {
- super(ctx, view);
- }
-
- @Override
- public boolean needsRoundRectClipping() {
- return true;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 5aedaf1..fb8086c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -24,6 +24,7 @@
import android.graphics.Rect;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
+import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
@@ -52,7 +53,6 @@
private final Rect mClipBounds = new Rect();
private final int mSingleLineHeight;
- private final int mSmallHeight;
private final int mHeadsUpHeight;
private final int mRoundRectRadius;
private final Interpolator mLinearInterpolator = new LinearInterpolator();
@@ -77,7 +77,9 @@
private boolean mIsHeadsUp;
private boolean mShowingLegacyBackground;
private boolean mIsChildInGroup;
+ private int mSmallHeight;
private StatusBarNotification mStatusBarNotification;
+ private NotificationGroupManager mGroupManager;
private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
= new ViewTreeObserver.OnPreDrawListener() {
@@ -96,7 +98,7 @@
mRoundRectRadius);
}
};
- private NotificationGroupManager mGroupManager;
+ private OnClickListener mExpandClickListener;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -104,7 +106,6 @@
mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
mSingleLineHeight = getResources().getDimensionPixelSize(
R.dimen.notification_single_line_height);
- mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
mHeadsUpHeight = getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
mRoundRectRadius = getResources().getDimensionPixelSize(
R.dimen.notification_material_rounded_rect_radius);
@@ -114,6 +115,10 @@
setOutlineProvider(mOutlineProvider);
}
+ public void setSmallHeight(int smallHeight) {
+ mSmallHeight = smallHeight;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
@@ -426,6 +431,11 @@
*/
private int calculateVisibleType() {
boolean noExpandedChild = mExpandedChild == null;
+
+ if (!noExpandedChild && mContentHeight == mExpandedChild.getHeight()) {
+ return VISIBLE_TYPE_EXPANDED;
+ }
+
if (mIsHeadsUp && mHeadsUpChild != null) {
if (mContentHeight <= mHeadsUpChild.getHeight() || noExpandedChild) {
return VISIBLE_TYPE_HEADSUP;
@@ -443,19 +453,6 @@
}
}
- public void notifyContentUpdated() {
- updateSingleLineView();
- selectLayout(false /* animate */, true /* force */);
- if (mContractedChild != null) {
- mContractedWrapper.notifyContentUpdated();
- mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
- }
- if (mExpandedChild != null) {
- mExpandedWrapper.notifyContentUpdated();
- }
- updateRoundRectClipping();
- }
-
public boolean isContentExpandable() {
return mExpandedChild != null;
}
@@ -488,9 +485,21 @@
updateSingleLineView();
}
- public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
+ public void onNotificationUpdated(StatusBarNotification statusBarNotification) {
mStatusBarNotification = statusBarNotification;
updateSingleLineView();
+ selectLayout(false /* animate */, true /* force */);
+ if (mContractedChild != null) {
+ mContractedWrapper.notifyContentUpdated();
+ mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+ }
+ if (mExpandedChild != null) {
+ mExpandedWrapper.notifyContentUpdated();
+ }
+ if (mHeadsUpChild != null) {
+ mHeadsUpWrapper.notifyContentUpdated();
+ }
+ updateRoundRectClipping();
}
private void updateSingleLineView() {
@@ -515,4 +524,20 @@
public void setGroupManager(NotificationGroupManager groupManager) {
mGroupManager = groupManager;
}
+
+ public void setExpandClickListener(OnClickListener expandClickListener) {
+ mExpandClickListener = expandClickListener;
+ }
+
+ public void updateExpandButtons(boolean expandable) {
+ if (mExpandedChild != null) {
+ mExpandedWrapper.updateExpandability(expandable, mExpandClickListener);
+ }
+ if (mContractedChild != null) {
+ mContractedWrapper.updateExpandability(expandable, mExpandClickListener);
+ }
+ if (mHeadsUpChild != null) {
+ mHeadsUpWrapper.updateExpandability(expandable, mExpandClickListener);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 83dbde5..89edae3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -59,6 +59,7 @@
public RemoteViews cachedBigContentView;
public RemoteViews cachedHeadsUpContentView;
public RemoteViews cachedPublicContentView;
+ public CharSequence remoteInputText;
public Entry(StatusBarNotification n, StatusBarIconView ic) {
this.key = n.getKey();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java
deleted file mode 100644
index 953c373..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.view.View;
-
-/**
- * Wraps a media notification.
- */
-public class NotificationMediaViewWrapper extends NotificationTemplateViewWrapper {
-
- protected NotificationMediaViewWrapper(Context ctx, View view) {
- super(ctx, view);
- }
-
- @Override
- public void setDark(boolean dark, boolean fade, long delay) {
-
- // Only update the large icon, because the rest is already inverted.
- setPictureGrayscale(dark, fade, delay);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
index af6ccd8..f20ccd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
@@ -20,24 +20,32 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ImageView;
+import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.phone.NotificationPanelView;
+import java.util.ArrayList;
+
/**
* Wraps a notification view inflated from a template.
*/
@@ -47,69 +55,64 @@
private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
0, PorterDuff.Mode.SRC_ATOP);
private final int mIconDarkAlpha;
- private final int mIconBackgroundDarkColor;
+ private final int mIconDarkColor = 0xffffffff;
+ private final int mDarkProgressTint = 0xffffffff;
private final Interpolator mLinearOutSlowInInterpolator;
- private int mIconBackgroundColor;
+ private int mColor;
private ViewInvertHelper mInvertHelper;
private ImageView mIcon;
protected ImageView mPicture;
- /** Whether the icon needs to be forced grayscale when in dark mode. */
- private boolean mIconForceGraysaleWhenDark;
private TextView mSubText;
- private TextView mInfoText;
- private View mProfileBadge;
- private View mThirdLineDivider;
- private View mThirdLine;
+ private View mSubTextDivider;
+ private ImageView mExpandButton;
+ private ViewGroup mNotificationHeader;
+ private ProgressBar mProgressBar;
protected NotificationTemplateViewWrapper(Context ctx, View view) {
super(view);
mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
- mIconBackgroundDarkColor =
- ctx.getColor(R.color.doze_small_icon_background_color);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
android.R.interpolator.linear_out_slow_in);
+
resolveViews();
}
private void resolveViews() {
View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
- mInvertHelper = mainColumn != null
- ? new ViewInvertHelper(mainColumn, NotificationPanelView.DOZE_ANIMATION_DURATION)
- : null;
- ImageView largeIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
- ImageView rightIcon = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
- mIcon = resolveIcon(largeIcon, rightIcon);
- mPicture = resolvePicture(largeIcon);
- mIconBackgroundColor = resolveBackgroundColor(mIcon);
- mSubText = (TextView) mView.findViewById(com.android.internal.R.id.text);
- mInfoText = (TextView) mView.findViewById(com.android.internal.R.id.info);
- mProfileBadge = mView.findViewById(com.android.internal.R.id.profile_badge_line3);
- mThirdLineDivider = mView.findViewById(com.android.internal.R.id.overflow_divider);
- mThirdLine = mView.findViewById(com.android.internal.R.id.line3);
-
- // If the icon already has a color filter, we assume that we already forced the icon to be
- // white when we created the notification.
- final Drawable iconDrawable = mIcon != null ? mIcon.getDrawable() : null;
- mIconForceGraysaleWhenDark = iconDrawable != null && iconDrawable.getColorFilter() != null;
+ mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
+ mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
+ mSubText = (TextView) mView.findViewById(com.android.internal.R.id.header_sub_text);
+ mSubTextDivider = mView.findViewById(com.android.internal.R.id.sub_text_divider);
+ mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button);
+ mColor = resolveColor(mExpandButton);
+ final View progress = mView.findViewById(com.android.internal.R.id.progress);
+ if (progress instanceof ProgressBar) {
+ mProgressBar = (ProgressBar) progress;
+ } else {
+ // It's still a viewstub
+ mProgressBar = null;
+ }
+ mNotificationHeader = (ViewGroup) mView.findViewById(
+ com.android.internal.R.id.notification_header);
+ ArrayList<View> viewsToInvert = new ArrayList<>();
+ if (mainColumn != null) {
+ viewsToInvert.add(mainColumn);
+ }
+ for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+ View child = mNotificationHeader.getChildAt(i);
+ if (child != mIcon) {
+ viewsToInvert.add(child);
+ }
+ }
+ mInvertHelper = new ViewInvertHelper(viewsToInvert,
+ NotificationPanelView.DOZE_ANIMATION_DURATION);
}
- private ImageView resolveIcon(ImageView largeIcon, ImageView rightIcon) {
- return largeIcon != null && largeIcon.getBackground() != null ? largeIcon
- : rightIcon != null && rightIcon.getVisibility() == View.VISIBLE ? rightIcon
- : null;
- }
-
- private ImageView resolvePicture(ImageView largeIcon) {
- return largeIcon != null && largeIcon.getBackground() == null
- ? largeIcon
- : null;
- }
-
- private int resolveBackgroundColor(ImageView icon) {
- if (icon != null && icon.getBackground() != null) {
- ColorFilter filter = icon.getBackground().getColorFilter();
+ private int resolveColor(ImageView icon) {
+ if (icon != null && icon.getDrawable() != null) {
+ ColorFilter filter = icon.getDrawable().getColorFilter();
if (filter instanceof PorterDuffColorFilter) {
return ((PorterDuffColorFilter) filter).getColor();
}
@@ -138,18 +141,43 @@
if (fade) {
fadeIconColorFilter(mIcon, dark, delay);
fadeIconAlpha(mIcon, dark, delay);
- if (!mIconForceGraysaleWhenDark) {
- fadeGrayscale(mIcon, dark, delay);
- }
} else {
updateIconColorFilter(mIcon, dark);
updateIconAlpha(mIcon, dark);
- if (!mIconForceGraysaleWhenDark) {
- updateGrayscale(mIcon, dark);
- }
}
}
setPictureGrayscale(dark, fade, delay);
+ setProgressBarDark(dark, fade, delay);
+ }
+
+ private void setProgressBarDark(boolean dark, boolean fade, long delay) {
+ if (mProgressBar != null) {
+ if (fade) {
+ fadeProgressDark(mProgressBar, dark, delay);
+ } else {
+ updateProgressDark(mProgressBar, dark);
+ }
+ }
+ }
+
+ private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (float) animation.getAnimatedValue();
+ updateProgressDark(target, t);
+ }
+ }, dark, delay, null /* listener */);
+ }
+
+ private void updateProgressDark(ProgressBar target, float intensity) {
+ int color = interpolateColor(mColor, mDarkProgressTint, intensity);
+ target.getIndeterminateDrawable().mutate().setTint(color);
+ target.getProgressDrawable().mutate().setTint(color);
+ }
+
+ private void updateProgressDark(ProgressBar target, boolean dark) {
+ updateProgressDark(target, dark ? 1f : 0f);
}
protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
@@ -218,14 +246,14 @@
}
private void updateIconColorFilter(ImageView target, float intensity) {
- int color = interpolateColor(mIconBackgroundColor, mIconBackgroundDarkColor, intensity);
+ int color = interpolateColor(mColor, mIconDarkColor, intensity);
mIconColorFilter.setColor(color);
- Drawable background = target.getBackground();
+ Drawable iconDrawable = target.getDrawable();
- // The background might be null for legacy notifications. Also, the notification might have
- // been modified during the animation, so background might be null here.
- if (background != null) {
- background.mutate().setColorFilter(mIconColorFilter);
+ // Also, the notification might have been modified during the animation, so background
+ // might be null here.
+ if (iconDrawable != null) {
+ iconDrawable.mutate().setColorFilter(mIconColorFilter);
}
}
@@ -250,33 +278,17 @@
boolean subTextAvailable = !TextUtils.isEmpty(mSubText.getText());
if (visible && subTextAvailable) {
mSubText.setVisibility(View.VISIBLE);
+ mSubTextDivider.setVisibility(View.VISIBLE);
} else {
mSubText.setVisibility(View.GONE);
+ mSubTextDivider.setVisibility(View.GONE);
}
- // TODO: figure out what to do with the number (same place as contentInfo)
- // work profile badge. For now we hide it since it looks nicer.
- boolean infoAvailable = !TextUtils.isEmpty(mInfoText.getText());
- if (visible && infoAvailable) {
- mInfoText.setVisibility(View.VISIBLE);
- } else {
- mInfoText.setVisibility(View.GONE);
- }
- boolean showThirdLine = (visible && (infoAvailable || subTextAvailable))
- || mProfileBadge.getVisibility() == View.VISIBLE;
- if (mThirdLineDivider != null) {
- if (showThirdLine) {
- mThirdLineDivider.setVisibility(View.VISIBLE);
- } else {
- mThirdLineDivider.setVisibility(View.GONE);
- }
- }
- if (mThirdLine != null) {
- if (showThirdLine) {
- mThirdLine.setVisibility(View.VISIBLE);
- } else {
- mThirdLine.setVisibility(View.GONE);
- }
- }
+ }
+
+ @Override
+ public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
+ mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
+ mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
private void updateGrayscaleMatrix(float intensity) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
index 9bce548..e83ecb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
@@ -34,15 +34,7 @@
public static NotificationViewWrapper wrap(Context ctx, View v) {
if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
- if (TAG_BIG_MEDIA_NARROW.equals(v.getTag())) {
- return new NotificationBigMediaNarrowViewWrapper(ctx, v);
- } else if (TAG_MEDIA.equals(v.getTag())) {
- return new NotificationMediaViewWrapper(ctx, v);
- } else if (TAG_BIG_PICTURE.equals(v.getTag())) {
- return new NotificationBigMediaNarrowViewWrapper(ctx, v);
- } else {
- return new NotificationTemplateViewWrapper(ctx, v);
- }
+ return new NotificationTemplateViewWrapper(ctx, v);
} else {
return new NotificationCustomViewWrapper(v);
}
@@ -83,4 +75,12 @@
public void setSubTextVisible(boolean visible) {
mSubTextVisible = visible;
}
+
+ /**
+ * Update the appearance of the expand button.
+ *
+ * @param expandable should this view be expandable
+ * @param onClickListener the listener to invoke when the expand affordance is clicked on
+ */
+ public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderView.java
deleted file mode 100644
index ec26cc4..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderView.java
+++ /dev/null
@@ -1,209 +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.systemui.statusbar.notification;
-
-import android.annotation.Nullable;
-import android.app.Notification;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.util.NotificationColorUtil;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.List;
-
-/**
- * A header for a notification view
- */
-public class NotificationHeaderView extends FrameLayout {
-
- private static final int DEFAULT_ICON_TINT_COLOR = 0xff616161;
- private final NotificationColorUtil mNotificationColorUtil;
- private NotificationData.Entry mNotificationEntry;
- private ImageView mIconView;
- private TextView mAppName;
- private TextView mPostTime;
- private TextView mChildCount;
- private TextView mSubTextDivider;
- private TextView mSubText;
- private NotificationGroupManager mGroupManager;
- private ImageButton mExpandButton;
-
- public NotificationHeaderView(Context context) {
- this(context, null);
- }
-
- public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mNotificationColorUtil = NotificationColorUtil.getInstance(context);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mIconView = (ImageView) findViewById(R.id.header_notification_icon);
- mAppName = (TextView) findViewById(R.id.app_name_text);
- mSubTextDivider = (TextView) findViewById(R.id.app_title_sub_text_divider);
- mSubText = (TextView) findViewById(R.id.title_sub_text);
- mPostTime = (TextView) findViewById(R.id.post_time);
- mChildCount = (TextView) findViewById(R.id.number_of_children);
- mExpandButton = (ImageButton) findViewById(R.id.notification_expand_button);
- mExpandButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mGroupManager.toggleGroupExpansion(mNotificationEntry.notification);
- }
- });
- }
-
- public void bind(NotificationData.Entry notificationEntry) {
- mNotificationEntry = notificationEntry;
- StatusBarNotification sbn = notificationEntry.notification;
- int notificationColor = getNotificationColor(sbn);
- bindIcon(notificationColor);
- bindNumber(notificationColor);
- bindAppName(sbn);
- bindSubText();
- bindTime(sbn);
- bindExpandButton(sbn);
- }
-
- private void bindExpandButton(StatusBarNotification sbn) {
- boolean summaryOfGroup = mGroupManager.isSummaryOfGroup(sbn);
- mExpandButton.setVisibility(summaryOfGroup ? VISIBLE : GONE);
- }
-
- private void bindSubText() {
- List<ExpandableNotificationRow> notificationChildren =
- mNotificationEntry.row.getNotificationChildren();
- CharSequence subText = null;
- if (notificationChildren != null) {
- for (int i = 0; i < notificationChildren.size(); i++) {
- ExpandableNotificationRow row = notificationChildren.get(i);
- CharSequence rowSubText = row.getSubText();
- if (TextUtils.isEmpty(rowSubText)
- || (subText != null && !subText.equals(rowSubText))) {
- // The children don't have a common subText
- subText = null;
- break;
- } else if (subText == null) {
- subText = rowSubText;
- }
- }
- };
- setSubText(subText);
- }
-
- private void setSubText(CharSequence subText) {
- boolean goneInHeader = TextUtils.isEmpty(subText);
- if (goneInHeader) {
- mSubText.setVisibility(GONE);
- mSubTextDivider.setVisibility(GONE);
- } else {
- mSubText.setVisibility(VISIBLE);
- mSubText.setText(subText);
- mSubTextDivider.setVisibility(VISIBLE);
- }
- List<ExpandableNotificationRow> notificationChildren =
- mNotificationEntry.row.getNotificationChildren();
- if (notificationChildren != null) {
- for (int i = 0; i < notificationChildren.size(); i++) {
- ExpandableNotificationRow row = notificationChildren.get(i);
- row.setContentSubTextVisible(goneInHeader);
- }
- }
- }
-
- private int getNotificationColor(StatusBarNotification sbn) {
- int color = sbn.getNotification().color;
- if (color == Notification.COLOR_DEFAULT) {
- return DEFAULT_ICON_TINT_COLOR;
- }
- return color;
- }
-
- private void bindNumber(int notificationColor) {
- int numberOfNotificationChildren = mNotificationEntry.row.getNumberOfNotificationChildren();
- boolean visible = numberOfNotificationChildren > 0;
- if (visible) {
- mChildCount.setText("(" + numberOfNotificationChildren + ")");
- mChildCount.setTextColor(notificationColor);
- mChildCount.setVisibility(VISIBLE);
- } else {
- mChildCount.setVisibility(GONE);
- }
- }
-
- private void bindTime(StatusBarNotification sbn) {
-
- }
-
- private void bindIcon(int notificationColor) {
- Drawable icon = mNotificationEntry.icon.getDrawable().getConstantState()
- .newDrawable(getResources()).mutate();
- mIconView.setImageDrawable(icon);
- if (NotificationUtils.isGrayscale(mIconView, mNotificationColorUtil)) {
- icon.setTint(notificationColor);
- }
- }
-
- private void bindAppName(StatusBarNotification sbn) {
- PackageManager pmUser = BaseStatusBar.getPackageManagerForUser(getContext(),
- sbn.getUser().getIdentifier());
- final String pkg = sbn.getPackageName();
- String appname = pkg;
- try {
- final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
- PackageManager.GET_UNINSTALLED_PACKAGES
- | PackageManager.GET_DISABLED_COMPONENTS);
- if (info != null) {
- appname = String.valueOf(pmUser.getApplicationLabel(info));
-
- }
- } catch (PackageManager.NameNotFoundException e) {
- // app is gone, just show package name
- }
- mAppName.setText(appname);
- }
-
- public void setGroupManager(NotificationGroupManager groupManager) {
- mGroupManager = groupManager;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index a15d35e..784cb48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -96,6 +96,11 @@
mEmptyDragAmount = emptyDragAmount;
}
+ public float getMinStackScrollerPadding(int height, int keyguardStatusHeight) {
+ return mClockYFractionMin * height + keyguardStatusHeight / 2
+ + mClockNotificationsMarginMin;
+ }
+
public void run(Result result) {
int y = getClockY() - mKeyguardStatusHeight / 2;
float clockAdjustment = getClockYExpansionAdjustment();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index e1aec6f..cddb1fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -143,7 +143,7 @@
private final OnClickListener mImeSwitcherClickListener = new OnClickListener() {
@Override
public void onClick(View view) {
- ((InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE))
+ mContext.getSystemService(InputMethodManager.class)
.showInputMethodPicker(true /* showAuxiliarySubtypes */);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index fbe9730..08da0d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -92,8 +92,6 @@
if (group.children.isEmpty()) {
if (group.summary == null) {
mGroupMap.remove(groupKey);
- } else if (!group.expanded) {
- group.summary.row.updateNotificationHeader();
}
}
}
@@ -109,9 +107,6 @@
}
if (notif.isGroupChild()) {
group.children.add(added);
- if (group.summary != null && group.children.size() == 1 && !group.expanded) {
- group.summary.row.updateNotificationHeader();
- }
} else {
group.summary = added;
group.expanded = added.row.areChildrenExpanded();
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 79701ed..c0e3ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -44,6 +44,7 @@
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
+
import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.DejankUtils;
@@ -448,6 +449,36 @@
requestScrollerTopPaddingUpdate(animate);
}
+ /**
+ * @param maximum the maximum to return at most
+ * @return the maximum keyguard notifications that can fit on the screen
+ */
+ public int computeMaxKeyguardNotifications(int maximum) {
+ float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding(getHeight(),
+ mKeyguardStatusView.getHeight());
+ int keyguardPadding = getResources().getDimensionPixelSize(
+ R.dimen.notification_padding_dimmed);
+ final int overflowheight = getResources().getDimensionPixelSize(
+ R.dimen.notification_summary_height);
+ float bottomStackSize = mNotificationStackScroller.getKeyguardBottomStackSize();
+ float availableSpace = mNotificationStackScroller.getHeight() - minPadding - overflowheight
+ - bottomStackSize;
+ int count = 0;
+ for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
+ ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ availableSpace -= child.getMinHeight() + keyguardPadding;
+ if (availableSpace >= 0 && count < maximum) {
+ count++;
+ } else {
+ return count;
+ }
+ }
+ return count;
+ }
+
private void startClockAnimation(int y) {
if (mClockAnimationTarget == y) {
return;
@@ -1663,7 +1694,7 @@
}
private float getFadeoutAlpha() {
- float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
+ float alpha = (getNotificationsTopY() + mNotificationStackScroller.getFirstItemMinHeight())
/ (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
- mNotificationStackScroller.getCollapseSecondCardPadding());
alpha = Math.max(0, Math.min(alpha, 1));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index fafedc3..21d803d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -36,9 +36,7 @@
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
-import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.classifier.HumanInteractionClassifier;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
@@ -891,6 +889,7 @@
if (mStatusBar.getStatusBarWindow().getHeight()
!= mStatusBar.getStatusBarHeight()) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mStatusBar.onPanelExpandedAndLayouted();
if (animate) {
mBar.startOpeningPanel(PanelView.this);
notifyExpandingStarted();
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 6d8e650..05660ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -110,8 +110,8 @@
import com.android.systemui.EventLogTags;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -171,7 +171,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
@@ -338,7 +337,7 @@
private long mKeyguardFadingAwayDelay;
private long mKeyguardFadingAwayDuration;
- int mKeyguardMaxNotificationCount;
+ int mMaxAllowedKeyguardNotifications;
boolean mExpandedVisible;
@@ -428,6 +427,7 @@
private boolean mAutohideSuspended;
private int mStatusBarMode;
private int mNavigationBarMode;
+ private int mMaxKeyguardNotifications;
private ViewMediatorCallback mKeyguardViewMediatorCallback;
private ScrimController mScrimController;
@@ -3129,10 +3129,11 @@
mNaturalBarHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
+ mRowMinHeightLegacy = res.getDimensionPixelSize(R.dimen.notification_min_height_legacy);
mRowMinHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
mRowMaxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
- mKeyguardMaxNotificationCount = res.getInteger(R.integer.keyguard_max_notification_count);
+ mMaxAllowedKeyguardNotifications = res.getInteger(R.integer.keyguard_max_notification_count);
if (DEBUG) Log.v(TAG, "updateResources");
}
@@ -3913,8 +3914,18 @@
}
@Override
- protected int getMaxKeyguardNotifications() {
- return mKeyguardMaxNotificationCount;
+ protected int getMaxKeyguardNotifications(boolean recompute) {
+ if (recompute) {
+ mMaxKeyguardNotifications = Math.max(1,
+ mNotificationPanel.computeMaxKeyguardNotifications(
+ mMaxAllowedKeyguardNotifications));
+ return mMaxKeyguardNotifications;
+ }
+ return mMaxKeyguardNotifications;
+ }
+
+ public int getMaxKeyguardNotifications() {
+ return getMaxKeyguardNotifications(false /* recompute */);
}
public NavigationBarView getNavigationBarView() {
@@ -3944,11 +3955,12 @@
@Override
public void onDragDownReset() {
mStackScroller.setDimmed(true /* dimmed */, true /* animated */);
+ mStackScroller.resetScrollPosition();
}
@Override
- public void onThresholdReached() {
- mStackScroller.setDimmed(false /* dimmed */, true /* animate */);
+ public void onCrossedThreshold(boolean above) {
+ mStackScroller.setDimmed(!above /* dimmed */, true /* animate */);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 93a8fd8..0917528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -110,7 +110,7 @@
// When enabling location, a user consent dialog will pop up, and the
// setting won't be fully enabled until the user accepts the agreement.
int mode = enabled
- ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY : Settings.Secure.LOCATION_MODE_OFF;
+ ? Settings.Secure.LOCATION_MODE_PREVIOUS : Settings.Secure.LOCATION_MODE_OFF;
// QuickSettings always runs as the owner, so specifically set the settings
// for the current foreground user.
return Settings.Secure
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 acfe54d..22c0cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -148,6 +148,7 @@
public void onDefocus() {
mController.removeRemoteInput(mEntry);
+ mEntry.remoteInputText = mEditText.getText();
setVisibility(INVISIBLE);
}
@@ -171,6 +172,8 @@
mEditText.setInnerFocusable(true);
mController.addRemoteInput(mEntry);
mEditText.mShowImeOnInputConnection = true;
+ mEditText.setText(mEntry.remoteInputText);
+ mEditText.setSelection(mEditText.getText().length());
mEditText.requestFocus();
}
@@ -216,8 +219,11 @@
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
defocusIfNeeded();
+ final InputMethodManager imm = InputMethodManager.getInstance();
+ imm.hideSoftInputFromWindow(getWindowToken(), 0);
+ return true;
}
return super.onKeyPreIme(keyCode, event);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index c9ebc84..77a9871 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -78,7 +78,7 @@
mNotificationAppearDistance = getResources().getDimensionPixelSize(
R.dimen.notification_appear_distance);
mNotificationHeaderHeight = getResources().getDimensionPixelSize(
- R.dimen.notification_header_height);
+ com.android.internal.R.dimen.notification_header_height);
mHeaderTopPaddingSubstraction = 2 * getResources().getDisplayMetrics().density;
mCollapsedBottompadding = 10 * getResources().getDisplayMetrics().density;
mHybridViewManager = new HybridNotificationViewManager(getContext(), this);
@@ -246,7 +246,7 @@
* in @param maxAllowedVisibleChildren
*/
private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
- int intrinsicHeight = 0;
+ int intrinsicHeight = mNotificationHeaderHeight;;
int visibleChildren = 0;
int childCount = mChildren.size();
for (int i = 0; i < childCount; i++) {
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 aeca97c..ae6b729 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -272,7 +272,7 @@
@Override
protected void onDraw(Canvas canvas) {
if (DEBUG) {
- int y = mCollapsedSize;
+ int y = mTopPadding;
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
y = (int) (getLayoutHeight() - mBottomStackPeekSize
- mBottomStackSlowDownHeight);
@@ -550,8 +550,9 @@
return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
}
- public int getItemHeight() {
- return mCollapsedSize;
+ public int getFirstItemMinHeight() {
+ final ExpandableView firstChild = getFirstChildNotGone();
+ return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
}
public int getBottomStackPeekSize() {
@@ -1321,14 +1322,14 @@
ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
if (firstChild != null) {
int contentHeight = getContentHeight();
- int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
+ mBottomStackSlowDownHeight);
if (scrollRange > 0) {
- View lastChild = getLastChildNotGone();
+ int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
// We want to at least be able collapse the first item and not ending in a weird
// end state.
- scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
+ scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight
+ - firstChild.getMinHeight());
}
}
return scrollRange;
@@ -1337,12 +1338,12 @@
/**
* @return the first child which has visibility unequal to GONE
*/
- private View getFirstChildNotGone() {
+ private ExpandableView getFirstChildNotGone() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
- return child;
+ return (ExpandableView) child;
}
}
return null;
@@ -1504,7 +1505,10 @@
}
public int getMinStackHeight() {
- return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding;
+ final ExpandableView firstChild = getFirstChildNotGone();
+ final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
+ : mCollapsedSize;
+ return firstChildMinHeight + mBottomStackPeekSize + mCollapseSecondCardPadding;
}
public float getTopPaddingOverflow() {
@@ -1512,7 +1516,10 @@
}
public int getPeekHeight() {
- return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
+ final ExpandableView firstChild = getFirstChildNotGone();
+ final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
+ : mCollapsedSize;
+ return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
+ mCollapseSecondCardPadding;
}
@@ -1860,7 +1867,7 @@
}
mNeedsAnimation = true;
}
- if (isHeadsUp(child)) {
+ if (isHeadsUp(child) && !mChangePositionInProgress) {
mAddedHeadsUpChildren.add(child);
mChildrenToAddAnimated.remove(child);
}
@@ -2266,6 +2273,11 @@
return Math.max(emptyMargin, 0);
}
+ public float getKeyguardBottomStackSize() {
+ return mBottomStackPeekSize + getResources().getDimensionPixelSize(
+ R.dimen.bottom_stack_slow_down_length);
+ }
+
public void onExpansionStarted() {
mIsExpansionChanging = true;
mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 65ca95b..953f287 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -42,7 +42,7 @@
private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
private static final int MAX_ITEMS_IN_TOP_STACK = 3;
- public static final float DIMMED_SCALE = 0.95f;
+ public static final float DIMMED_SCALE = 0.98f;
private int mPaddingBetweenElements;
private int mCollapsedSize;
@@ -72,6 +72,7 @@
private int mMaxNotificationHeight;
private boolean mScaleDimmed;
private HeadsUpManager mHeadsUpManager;
+ private int mFirstChildMinHeight;
public StackScrollAlgorithm(Context context) {
initConstants(context);
@@ -155,7 +156,7 @@
// Due to the overScroller, the stackscroller can have negative scroll state. This is
// already accounted for by the top padding and doesn't need an additional adaption
scrollY = Math.max(0, scrollY);
- algorithmState.scrollY = (int) (scrollY + mCollapsedSize + bottomOverScroll);
+ algorithmState.scrollY = (int) (scrollY + mFirstChildMinHeight + bottomOverScroll);
updateVisibleChildren(resultState, algorithmState);
@@ -424,7 +425,8 @@
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
- float scrollOffset = yPositionInScrollView - algorithmState.scrollY + mCollapsedSize;
+ float scrollOffset = yPositionInScrollView - algorithmState.scrollY +
+ mFirstChildMinHeight;
if (i == algorithmState.lastTopStackIndex + 1) {
// Normally the position of this child is the position in the regular scrollview,
@@ -451,10 +453,10 @@
>= bottomStackStart && !mIsExpansionChanging && i != 0 && mIsSmallScreen) {
// we just collapse this element slightly
int newSize = (int) Math.max(bottomStackStart - mPaddingBetweenElements -
- childViewState.yTranslation, mCollapsedSize);
+ childViewState.yTranslation, child.getMinHeight());
childViewState.height = newSize;
updateStateForChildTransitioningInBottom(algorithmState, bottomStackStart,
- bottomPeekStart, childViewState.yTranslation, childViewState,
+ child, childViewState.yTranslation, childViewState,
childHeight);
}
clampPositionToBottomStackStart(childViewState, childViewState.height,
@@ -471,7 +473,7 @@
// According to the regular scroll view we are currently translating out of /
// into the bottom of the screen
updateStateForChildTransitioningInBottom(algorithmState,
- bottomStackStart, bottomPeekStart, currentYPosition,
+ bottomStackStart, child, currentYPosition,
childViewState, childHeight);
}
} else {
@@ -484,12 +486,13 @@
// The first card is always rendered.
if (i == 0) {
childViewState.alpha = 1.0f;
- childViewState.yTranslation = Math.max(mCollapsedSize - algorithmState.scrollY, 0);
+ childViewState.yTranslation = Math.max(
+ mFirstChildMinHeight - algorithmState.scrollY, 0);
if (childViewState.yTranslation + childViewState.height
> bottomPeekStart - mCollapseSecondCardPadding) {
childViewState.height = (int) Math.max(
bottomPeekStart - mCollapseSecondCardPadding
- - childViewState.yTranslation, mCollapsedSize);
+ - childViewState.yTranslation, mFirstChildMinHeight);
}
childViewState.location = StackViewState.LOCATION_FIRST_CARD;
}
@@ -501,7 +504,8 @@
if (ambientState.isShadeExpanded() && topHeadsUpEntry != null
&& child != topHeadsUpEntry) {
- childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() - mCollapsedSize;
+ childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() -
+ mFirstChildMinHeight;
}
childViewState.yTranslation += ambientState.getTopPadding()
+ ambientState.getStackTranslation();
@@ -528,7 +532,7 @@
boolean isTopEntry = topHeadsUpEntry == row;
if (mIsExpanded) {
if (isTopEntry) {
- childState.height += row.getHeadsUpHeight() - mCollapsedSize;
+ childState.height += row.getHeadsUpHeight() - mFirstChildMinHeight;
}
childState.height = Math.max(childState.height, row.getHeadsUpHeight());
// Ensure that the heads up is always visible even when scrolled off from the bottom
@@ -588,7 +592,7 @@
private void clampPositionToTopStackEnd(StackViewState childViewState,
int childHeight) {
childViewState.yTranslation = Math.max(childViewState.yTranslation,
- mCollapsedSize - childHeight);
+ mFirstChildMinHeight - childHeight);
}
private int getMaxAllowedChildHeight(View child, AmbientState ambientState) {
@@ -597,7 +601,7 @@
if (ambientState == null && row.isHeadsUp()
|| ambientState != null && ambientState.getTopHeadsUpEntry() == child) {
int extraSize = row.getIntrinsicHeight() - row.getHeadsUpHeight();
- return mCollapsedSize + extraSize;
+ return mFirstChildMinHeight + extraSize;
}
return row.getIntrinsicHeight();
} else if (child instanceof ExpandableView) {
@@ -608,7 +612,7 @@
}
private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
- float transitioningPositionStart, float bottomPeakStart, float currentYPosition,
+ float transitioningPositionStart, ExpandableView child, float currentYPosition,
StackViewState childViewState, int childHeight) {
// This is the transitioning element on top of bottom stack, calculate how far we are in.
@@ -620,9 +624,10 @@
float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom);
algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
int newHeight = childHeight;
- if (childHeight > mCollapsedSize && mIsSmallScreen) {
+ if (childHeight > child.getMinHeight() && mIsSmallScreen) {
newHeight = (int) Math.max(Math.min(transitioningPositionStart + offset -
- mPaddingBetweenElements - currentYPosition, childHeight), mCollapsedSize);
+ mPaddingBetweenElements - currentYPosition, childHeight),
+ child.getMinHeight());
childViewState.height = newHeight;
}
childViewState.yTranslation = transitioningPositionStart + offset - newHeight
@@ -689,7 +694,7 @@
numItemsBefore = algorithmState.itemsInTopStack - i;
}
// The end position of the current child
- float currentChildEndY = mCollapsedSize + mTopStackTotalSize
+ float currentChildEndY = mFirstChildMinHeight + mTopStackTotalSize
- mTopStackIndentationFunctor.getValue(numItemsBefore);
childViewState.yTranslation = currentChildEndY - childHeight;
}
@@ -701,7 +706,7 @@
// We are hidden behind the top card and faded out, so we can hide ourselves.
childViewState.alpha = 0.0f;
}
- childViewState.yTranslation = mCollapsedSize - childHeight;
+ childViewState.yTranslation = mFirstChildMinHeight - childHeight;
childViewState.location = StackViewState.LOCATION_TOP_STACK_HIDDEN;
}
@@ -730,7 +735,7 @@
+ childHeight
+ mPaddingBetweenElements;
if (yPositionInScrollView < algorithmState.scrollY) {
- if (i == 0 && algorithmState.scrollY <= mCollapsedSize) {
+ if (i == 0 && algorithmState.scrollY <= mFirstChildMinHeight) {
// The starting position of the bottom stack peek
int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
@@ -740,14 +745,14 @@
? mFirstChildMaxHeight
: childHeight;
childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
- mCollapsedSize);
+ mFirstChildMinHeight);
algorithmState.itemsInTopStack = 1.0f;
} else if (yPositionInScrollViewAfterElement < algorithmState.scrollY) {
// According to the regular scroll view we are fully off screen
algorithmState.itemsInTopStack += 1.0f;
if (i == 0) {
- childViewState.height = mCollapsedSize;
+ childViewState.height = child.getMinHeight();
}
} else {
// According to the regular scroll view we are partially off screen
@@ -766,8 +771,8 @@
// If it is expanded we have to collapse it to a new size
float newSize = yPositionInScrollViewAfterElement
- mPaddingBetweenElements
- - algorithmState.scrollY + mCollapsedSize;
- newSize = Math.max(mCollapsedSize, newSize);
+ - algorithmState.scrollY + mFirstChildMinHeight;
+ newSize = Math.max(mFirstChildMinHeight, newSize);
algorithmState.itemsInTopStack = 1.0f;
childViewState.height = (int) newSize;
}
@@ -809,7 +814,7 @@
// Interpolate the index from 0 to 2 while the second item is
// translating in.
stackIndex -= 1.0f;
- if (algorithmState.scrollY > mCollapsedSize) {
+ if (algorithmState.scrollY > mFirstChildMinHeight) {
// Since there is a shadow treshhold, we cant just interpolate from 0 to
// 2 but we interpolate from 0.1f to 2.0f when scrolled in. The jump in
@@ -863,7 +868,7 @@
ExpandableNotificationRow row =
(ExpandableNotificationRow) mFirstChildWhileExpanding;
if (row.isHeadsUp()) {
- mFirstChildMaxHeight += mCollapsedSize - row.getHeadsUpHeight();
+ mFirstChildMaxHeight += mFirstChildMinHeight - row.getHeadsUpHeight();
}
}
} else {
@@ -927,7 +932,11 @@
this.mIsExpanded = isExpanded;
}
- public void notifyChildrenChanged(final ViewGroup hostView) {
+ public void notifyChildrenChanged(final NotificationStackScrollLayout hostView) {
+ int firstItemMinHeight = hostView.getFirstItemMinHeight();
+ if (firstItemMinHeight != mFirstChildMinHeight) {
+ mFirstChildMinHeight = firstItemMinHeight;
+ }
if (mIsExpansionChanging) {
hostView.post(new Runnable() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index bbe5dd9..39a2986 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -130,7 +130,7 @@
}
@Override
- protected int getMaxKeyguardNotifications() {
+ protected int getMaxKeyguardNotifications(boolean recompute) {
return 0;
}
diff --git a/preloaded-classes b/preloaded-classes
index 79c0957..2301c41 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -1765,6 +1765,7 @@
android.util.SuperNotCalledException
android.util.TypedValue
android.util.Xml
+android.util.jar.StrictJarFile
android.view.AbsSavedState
android.view.AbsSavedState$1
android.view.AbsSavedState$2
@@ -3361,9 +3362,6 @@
java.util.jar.JarEntry
java.util.jar.JarFile
java.util.jar.JarFile$JarFileEnumerator
-java.util.jar.Manifest
-java.util.jar.ManifestReader
-java.util.jar.StrictJarFile
java.util.logging.ConsoleHandler
java.util.logging.ErrorManager
java.util.logging.Filter
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3d358ec..11fdbb5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -433,7 +433,6 @@
AccessibilityEvent.obtain(event)).sendToTarget();
}
event.recycle();
- getUserStateLocked(resolvedUserId).mHandledFeedbackTypes = 0;
}
return (OWN_PROCESS_ID != Binder.getCallingPid());
}
@@ -1051,9 +1050,7 @@
Service service = state.mBoundServices.get(i);
if (service.mIsDefault == isDefault) {
- if (canDispatchEventToServiceLocked(service, event,
- state.mHandledFeedbackTypes)) {
- state.mHandledFeedbackTypes |= service.mFeedbackType;
+ if (canDispatchEventToServiceLocked(service, event)) {
service.notifyAccessibilityEvent(event);
}
}
@@ -1088,19 +1085,14 @@
/**
* Determines if given event can be dispatched to a service based on the package of the
- * event source and already notified services for that event type. Specifically, a
- * service is notified if it is interested in events from the package and no other service
- * providing the same feedback type has been notified. Exception are services the
- * provide generic feedback (feedback type left as a safety net for unforeseen feedback
- * types) which are always notified.
+ * event source. Specifically, a service is notified if it is interested in events from the
+ * package.
*
* @param service The potential receiver.
* @param event The event.
- * @param handledFeedbackTypes The feedback types for which services have been notified.
* @return True if the listener should be notified, false otherwise.
*/
- private boolean canDispatchEventToServiceLocked(Service service, AccessibilityEvent event,
- int handledFeedbackTypes) {
+ private boolean canDispatchEventToServiceLocked(Service service, AccessibilityEvent event) {
if (!service.canReceiveEventsLocked()) {
return false;
@@ -1121,15 +1113,7 @@
String packageName = (event.getPackageName() != null)
? event.getPackageName().toString() : null;
- if (packageNames.isEmpty() || packageNames.contains(packageName)) {
- int feedbackType = service.mFeedbackType;
- if ((handledFeedbackTypes & feedbackType) != feedbackType
- || feedbackType == AccessibilityServiceInfo.FEEDBACK_GENERIC) {
- return true;
- }
- }
-
- return false;
+ return (packageNames.isEmpty() || packageNames.contains(packageName));
}
private void unbindAllServicesLocked(UserState userState) {
@@ -3886,8 +3870,6 @@
public final Set<ComponentName> mTouchExplorationGrantedServices =
new HashSet<>();
- public int mHandledFeedbackTypes = 0;
-
public int mLastSentClientState = -1;
public boolean mIsAccessibilityEnabled;
@@ -3950,7 +3932,6 @@
mBindingServices.clear();
// Clear event management state.
- mHandledFeedbackTypes = 0;
mLastSentClientState = -1;
// Clear state persisted in settings.
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 78a4e35..42dd9a8 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -70,6 +70,7 @@
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
+import android.hardware.input.InputManagerInternal;
import android.inputmethodservice.InputMethodService;
import android.net.Uri;
import android.os.Binder;
@@ -1930,6 +1931,16 @@
}
}
+ private void notifyInputMethodSubtypeChanged(final int userId,
+ @Nullable final InputMethodInfo inputMethodInfo,
+ @Nullable final InputMethodSubtype subtype) {
+ final InputManagerInternal inputManagerInternal =
+ LocalServices.getService(InputManagerInternal.class);
+ if (inputManagerInternal != null) {
+ inputManagerInternal.onInputMethodSubtypeChanged(userId, inputMethodInfo, subtype);
+ }
+ }
+
/* package */ void setInputMethodLocked(String id, int subtypeId) {
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
@@ -1972,8 +1983,10 @@
mCurMethod.changeInputMethodSubtype(newSubtype);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call changeInputMethodSubtype");
+ return;
}
}
+ notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info, newSubtype);
}
return;
}
@@ -1999,6 +2012,9 @@
} finally {
Binder.restoreCallingIdentity(ident);
}
+
+ notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info,
+ getCurrentInputMethodSubtypeLocked());
}
@Override
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index da81528..d6c6f13 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -16,6 +16,8 @@
package com.android.server;
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
import android.app.backup.BackupManager;
import android.app.trust.IStrongAuthTracker;
@@ -388,6 +390,13 @@
}
}
+ private void unlockUser(int userId, byte[] token) {
+ try {
+ ActivityManagerNative.getDefault().unlockUser(userId, token);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
private byte[] getCurrentHandle(int userId) {
CredentialHash credential;
@@ -612,6 +621,7 @@
byte[] hash = credentialUtil.toHash(credential, userId);
if (Arrays.equals(hash, storedHash.hash)) {
unlockKeystore(credentialUtil.adjustForKeystore(credential), userId);
+ unlockUser(userId, null);
// migrate credential to GateKeeper
credentialUtil.setCredential(credential, null, userId);
if (!hasChallenge) {
@@ -664,6 +674,7 @@
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
// credential has matched
unlockKeystore(credential, userId);
+ unlockUser(userId, null);
if (shouldReEnroll) {
credentialUtil.setCredential(credential, credential, userId);
}
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index a32bb2f..bd43a71 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -781,6 +781,7 @@
}
private void handleSystemReady() {
+ initIfReadyAndConnected();
resetIfReadyAndConnected();
// Start scheduling nominally-daily fstrim operations
@@ -828,6 +829,22 @@
mVolumes.put(internal.id, internal);
}
+ private void initIfReadyAndConnected() {
+ Slog.d(TAG, "Thinking about init, mSystemReady=" + mSystemReady
+ + ", mDaemonConnected=" + mDaemonConnected);
+ if (mSystemReady && mDaemonConnected && StorageManager.isFileBasedEncryptionEnabled()) {
+ final List<UserInfo> users = mContext.getSystemService(UserManager.class)
+ .getUsers();
+ for (UserInfo user : users) {
+ try {
+ mCryptConnector.execute("cryptfs", "lock_user_key", user.id);
+ } catch (NativeDaemonConnectorException e) {
+ Slog.w(TAG, "Failed to init vold", e);
+ }
+ }
+ }
+ }
+
private void resetIfReadyAndConnected() {
Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
+ ", mDaemonConnected=" + mDaemonConnected);
@@ -928,6 +945,7 @@
}
private void handleDaemonConnected() {
+ initIfReadyAndConnected();
resetIfReadyAndConnected();
/*
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index fc3a322..fcd56eb7 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -446,8 +446,7 @@
String candidateLocale = null;
if (hashCode == 0) {
// Spell checker language settings == "auto"
- final InputMethodManager imm =
- (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+ final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
if (imm != null) {
final InputMethodSubtype currentInputMethodSubtype =
imm.getCurrentInputMethodSubtype();
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 9eb66dd..2924cef 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -223,7 +223,7 @@
}
public void systemReady() {
- mIm = (InputManager)mContext.getSystemService(Context.INPUT_SERVICE);
+ mIm = mContext.getSystemService(InputManager.class);
mSettingObserver = new SettingsObserver(mH);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 17b3d2a..2742c65 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -30,10 +30,14 @@
import android.app.ActivityThread;
import android.app.AppOpsManager;
+import android.content.IIntentSender;
+import android.content.IntentSender;
import android.os.Build;
+import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteCallback;
import android.os.SystemProperties;
import android.os.TransactionTooLargeException;
import android.util.ArrayMap;
@@ -300,7 +304,7 @@
}
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
- int callingPid, int callingUid, String callingPackage, int userId)
+ int callingPid, int callingUid, String callingPackage, final int userId)
throws TransactionTooLargeException {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
+ " type=" + resolvedType + " args=" + service.getExtras());
@@ -340,6 +344,18 @@
NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked(
callingUid, r.packageName, service, service.getFlags(), null, r.userId);
+
+ // If permissions need a review before any of the app components can run,
+ // we do not start the service and launch a review activity if the calling app
+ // is in the foreground passing it a pending intent to start the service when
+ // review is completed.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (!requestStartTargetPermissionsReviewIfNeededLocked(r, callingPackage,
+ callingUid, service, callerFg, userId)) {
+ return null;
+ }
+ }
+
if (unscheduleServiceRestartLocked(r, callingUid, false)) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "START SERVICE WHILE RESTART PENDING: " + r);
}
@@ -417,6 +433,50 @@
return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}
+ private boolean requestStartTargetPermissionsReviewIfNeededLocked(ServiceRecord r,
+ String callingPackage, int callingUid, Intent service, boolean callerFg,
+ final int userId) {
+ if (mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+ r.packageName, r.userId)) {
+
+ // Show a permission review UI only for starting from a foreground app
+ if (!callerFg) {
+ Slog.w(TAG, "u" + r.userId + " Starting a service in package"
+ + r.packageName + " requires a permissions review");
+ return false;
+ }
+
+ IIntentSender target = mAm.getIntentSenderLocked(
+ ActivityManager.INTENT_SENDER_SERVICE, callingPackage,
+ callingUid, userId, null, null, 0, new Intent[]{service},
+ new String[]{service.resolveType(mAm.mContext.getContentResolver())},
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_IMMUTABLE, null);
+
+ final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, r.packageName);
+ intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
+
+ if (DEBUG_PERMISSIONS_REVIEW) {
+ Slog.i(TAG, "u" + r.userId + " Launching permission review for package "
+ + r.packageName);
+ }
+
+ mAm.mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAm.mContext.startActivityAsUser(intent, new UserHandle(userId));
+ }
+ });
+
+ return false;
+ }
+
+ return true;
+ }
+
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
ProcessStats.ServiceState stracker = r.getTracker();
@@ -427,7 +487,7 @@
synchronized (r.stats.getBatteryStats()) {
r.stats.startRunningLocked();
}
- String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
+ String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
if (error != null) {
return new ComponentName("!!", error);
}
@@ -721,8 +781,8 @@
}
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
- String resolvedType, IServiceConnection connection, int flags,
- String callingPackage, int userId) throws TransactionTooLargeException {
+ String resolvedType, final IServiceConnection connection, int flags,
+ String callingPackage, final int userId) throws TransactionTooLargeException {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
+ " type=" + resolvedType + " conn=" + connection.asBinder()
+ " flags=0x" + Integer.toHexString(flags));
@@ -783,6 +843,83 @@
}
ServiceRecord s = res.record;
+ boolean permissionsReviewRequired = false;
+
+ // If permissions need a review before any of the app components can run,
+ // we schedule binding to the service but do not start its process, then
+ // we launch a review activity to which is passed a callback to invoke
+ // when done to start the bound service's process to completing the binding.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+ s.packageName, s.userId)) {
+
+ permissionsReviewRequired = true;
+
+ // Show a permission review UI only for binding from a foreground app
+ if (!callerFg) {
+ Slog.w(TAG, "u" + s.userId + " Binding to a service in package"
+ + s.packageName + " requires a permissions review");
+ return 0;
+ }
+
+ final ServiceRecord serviceRecord = s;
+ final Intent serviceIntent = service;
+
+ RemoteCallback callback = new RemoteCallback(
+ new RemoteCallback.OnResultListener() {
+ @Override
+ public void onResult(Bundle result) {
+ synchronized(mAm) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (!mPendingServices.contains(serviceRecord)) {
+ return;
+ }
+ // If there is still a pending record, then the service
+ // binding request is still valid, so hook them up. We
+ // proceed only if the caller cleared the review requirement
+ // otherwise we unbind because the user didn't approve.
+ if (!mAm.getPackageManagerInternalLocked()
+ .isPermissionsReviewRequired(
+ serviceRecord.packageName,
+ serviceRecord.userId)) {
+ try {
+ bringUpServiceLocked(serviceRecord,
+ serviceIntent.getFlags(),
+ callerFg, false, false);
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ }
+ } else {
+ unbindServiceLocked(connection);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+ });
+
+ final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, s.packageName);
+ intent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback);
+
+ if (DEBUG_PERMISSIONS_REVIEW) {
+ Slog.i(TAG, "u" + s.userId + " Launching permission review for package "
+ + s.packageName);
+ }
+
+ mAm.mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAm.mContext.startActivityAsUser(intent, new UserHandle(userId));
+ }
+ });
+ }
+ }
+
final long origId = Binder.clearCallingIdentity();
try {
@@ -840,7 +977,8 @@
if ((flags&Context.BIND_AUTO_CREATE) != 0) {
s.lastActivity = SystemClock.uptimeMillis();
- if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {
+ if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
+ permissionsReviewRequired) != null) {
return 0;
}
}
@@ -890,6 +1028,10 @@
return 1;
}
+ private void foo() {
+
+ }
+
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
final long origId = Binder.clearCallingIdentity();
try {
@@ -1366,7 +1508,7 @@
return;
}
try {
- bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
+ bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true, false);
} catch (TransactionTooLargeException e) {
// Ignore, it's been logged and nothing upstack cares.
}
@@ -1410,8 +1552,9 @@
}
}
- private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
- boolean whileRestarting) throws TransactionTooLargeException {
+ private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
+ boolean whileRestarting, boolean permissionsReviewRequired)
+ throws TransactionTooLargeException {
//Slog.i(TAG, "Bring up service:");
//r.dump(" ");
@@ -1497,7 +1640,7 @@
// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
- if (app == null) {
+ if (app == null && !permissionsReviewRequired) {
if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
"service", r.name, false, isolated, false)) == null) {
String msg = "Unable to launch app "
@@ -1920,6 +2063,9 @@
}
}
+ // If unbound while waiting to start, remove the pending service
+ mPendingServices.remove(s);
+
if ((c.flags&Context.BIND_AUTO_CREATE) != 0) {
boolean hasAutoCreate = s.hasAutoCreateConnections();
if (!hasAutoCreate) {
@@ -2962,5 +3108,4 @@
}
}
}
-
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 16c959f..4f0d4d9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -88,6 +88,7 @@
static final boolean DEBUG_VISIBILITY = DEBUG_ALL || false;
static final boolean DEBUG_VISIBLE_BEHIND = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false;
+ static final boolean DEBUG_PERMISSIONS_REVIEW = DEBUG_ALL || false;
static final String POSTFIX_ADD_REMOVE = (APPEND_CATEGORY_NAME) ? "_AddRemove" : "";
static final String POSTFIX_APP = (APPEND_CATEGORY_NAME) ? "_App" : "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 557b386..bbf8652 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,9 +16,65 @@
package com.android.server.am;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
+import static com.android.server.am.ActivityManagerDebugConfig.*;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.app.ApplicationThreadNative;
+import android.app.BroadcastOptions;
+import android.app.IActivityContainer;
+import android.app.IActivityContainerCallback;
+import android.app.IAppTask;
+import android.app.ITaskStackListener;
+import android.app.ProfilerInfo;
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
+import android.appwidget.AppWidgetManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+import android.os.PowerManager;
+import android.os.Trace;
+import android.os.TransactionTooLargeException;
+import android.os.WorkSource;
+import android.os.storage.IMountService;
+import android.os.storage.MountServiceInternal;
+import android.os.storage.StorageManager;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VoiceInteractionSession;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.DebugUtils;
+import android.util.SparseIntArray;
+import android.view.Display;
+
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.AssistUtils;
@@ -1457,6 +1513,8 @@
final MainHandler mHandler;
final UiHandler mUiHandler;
+ PackageManagerInternal mPackageManagerInt;
+
final class UiHandler extends Handler {
public UiHandler() {
super(com.android.server.UiThread.get().getLooper(), null, true);
@@ -3356,10 +3414,8 @@
try {
try {
- if (AppGlobals.getPackageManager().isPackageFrozen(app.info.packageName)) {
- // This is caught below as if we had failed to fork zygote
- throw new RuntimeException("Package " + app.info.packageName + " is frozen!");
- }
+ final int userId = UserHandle.getUserId(app.uid);
+ AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -4399,8 +4455,10 @@
if (r == null) {
return;
}
- if (r.task != null && r.task.mResizeable) {
- // Fixed screen orientation isn't supported with resizeable activities.
+ TaskRecord task = r.task;
+ if (task != null && (!task.mFullscreen || !task.stack.mFullscreen)) {
+ // Fixed screen orientation isn't supported when activities aren't in full screen
+ // mode.
return;
}
final long origId = Binder.clearCallingIdentity();
@@ -7148,22 +7206,35 @@
@Override
public void getProcessStatesFromPids(/*in*/ int[] pids, /*out*/ int[] states) {
- mActivityManagerService.getProcessStatesForPIDs(/*in*/ pids, /*out*/ states);
+ mActivityManagerService.getProcessStatesAndOomScoresForPIDs(
+ /*in*/ pids, /*out*/ states, null);
+ }
+
+ @Override
+ public void getProcessStatesAndOomScoresFromPids(
+ /*in*/ int[] pids, /*out*/ int[] states, /*out*/ int[] scores) {
+ mActivityManagerService.getProcessStatesAndOomScoresForPIDs(
+ /*in*/ pids, /*out*/ states, /*out*/ scores);
}
}
/**
* For each PID in the given input array, write the current process state
- * for that process into the output array, or -1 to indicate that no
- * process with the given PID exists.
+ * for that process into the states array, or -1 to indicate that no
+ * process with the given PID exists. If scores array is provided, write
+ * the oom score for the process into the scores array, with INVALID_ADJ
+ * indicating the PID doesn't exist.
*/
- public void getProcessStatesForPIDs(/*in*/ int[] pids, /*out*/ int[] states) {
+ public void getProcessStatesAndOomScoresForPIDs(
+ /*in*/ int[] pids, /*out*/ int[] states, /*out*/ int[] scores) {
if (pids == null) {
throw new NullPointerException("pids");
} else if (states == null) {
throw new NullPointerException("states");
} else if (pids.length != states.length) {
- throw new IllegalArgumentException("input and output arrays have different lengths!");
+ throw new IllegalArgumentException("pids and states arrays have different lengths!");
+ } else if (scores != null && pids.length != scores.length) {
+ throw new IllegalArgumentException("pids and scores arrays have different lengths!");
}
synchronized (mPidsSelfLocked) {
@@ -7171,6 +7242,9 @@
ProcessRecord pr = mPidsSelfLocked.get(pids[i]);
states[i] = (pr == null) ? ActivityManager.PROCESS_STATE_NONEXISTENT :
pr.curProcState;
+ if (scores != null) {
+ scores[i] = (pr == null) ? ProcessList.INVALID_ADJ : pr.curAdj;
+ }
}
}
}
@@ -9944,14 +10018,6 @@
}
if (cpr.proc != null) {
- if (false) {
- if (cpr.name.flattenToShortString().equals(
- "com.android.providers.calendar/.CalendarProvider2")) {
- Slog.v(TAG, "****************** KILLING "
- + cpr.name.flattenToShortString());
- Process.killProcess(cpr.proc.pid);
- }
- }
checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
boolean success = updateOomAdjLocked(cpr.proc);
maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name);
@@ -10044,6 +10110,16 @@
final boolean firstClass = cpr == null;
if (firstClass) {
final long ident = Binder.clearCallingIdentity();
+
+ // If permissions need a review before any of the app components can run,
+ // we return no provider and launch a review activity if the calling app
+ // is in the foreground.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (!requestTargetProviderPermissionsReviewIfNeededLocked(cpi, r, userId)) {
+ return null;
+ }
+ }
+
try {
checkTime(startTime, "getContentProviderImpl: before getApplicationInfo");
ApplicationInfo ai =
@@ -10196,6 +10272,52 @@
return cpr != null ? cpr.newHolder(conn) : null;
}
+ private boolean requestTargetProviderPermissionsReviewIfNeededLocked(ProviderInfo cpi,
+ ProcessRecord r, final int userId) {
+ if (getPackageManagerInternalLocked().isPermissionsReviewRequired(
+ cpi.packageName, r.userId)) {
+
+ final boolean callerForeground = r != null ? r.setSchedGroup
+ != Process.THREAD_GROUP_BG_NONINTERACTIVE : true;
+
+ // Show a permission review UI only for starting from a foreground app
+ if (!callerForeground) {
+ Slog.w(TAG, "u" + r.userId + " Instantiating a provider in package"
+ + cpi.packageName + " requires a permissions review");
+ return false;
+ }
+
+ final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, cpi.packageName);
+
+ if (DEBUG_PERMISSIONS_REVIEW) {
+ Slog.i(TAG, "u" + r.userId + " Launching permission review "
+ + "for package " + cpi.packageName);
+ }
+
+ final UserHandle userHandle = new UserHandle(userId);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mContext.startActivityAsUser(intent, userHandle);
+ }
+ });
+
+ return false;
+ }
+
+ return true;
+ }
+
+ PackageManagerInternal getPackageManagerInternalLocked() {
+ if (mPackageManagerInt == null) {
+ mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
+ }
+ return mPackageManagerInt;
+ }
+
@Override
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String name, int userId, boolean stable) {
@@ -10899,7 +11021,7 @@
public void stopAppSwitches() {
if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)
!= PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires permission "
+ throw new SecurityException("viewquires permission "
+ android.Manifest.permission.STOP_APP_SWITCHES);
}
@@ -17761,7 +17883,7 @@
* @param userId is only used when persistent parameter is set to true to persist configuration
* for that particular user
*/
- boolean updateConfigurationLocked(Configuration values,
+ private boolean updateConfigurationLocked(Configuration values,
ActivityRecord starting, boolean initLocale, boolean persistent, int userId) {
int changes = 0;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c3bd982..e9e02c1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -76,6 +76,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
@@ -88,6 +89,7 @@
import android.hardware.input.InputManagerInternal;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
@@ -184,7 +186,6 @@
static final int CONTAINER_CALLBACK_TASK_LIST_EMPTY = FIRST_SUPERVISOR_STACK_MSG + 11;
static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12;
static final int SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG = FIRST_SUPERVISOR_STACK_MSG + 13;
- static final int SHOW_NON_RESIZEABLE_DOCK_TOAST = FIRST_SUPERVISOR_STACK_MSG + 14;
private static final String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay";
@@ -1708,6 +1709,46 @@
return ActivityManager.START_SUCCESS;
}
+ // If permissions need a review before any of the app components can run, we
+ // launch the review activity and pass a pending intent to start the activity
+ // we are to launching now after the review is completed.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED && aInfo != null) {
+ if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+ aInfo.packageName, userId)) {
+ IIntentSender target = mService.getIntentSenderLocked(
+ ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+ callingUid, userId, null, null, 0, new Intent[]{intent},
+ new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT
+ | PendingIntent.FLAG_ONE_SHOT, null);
+
+ Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+ newIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
+ newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
+ if (resultRecord != null) {
+ newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true);
+ }
+ newIntent.setFlags(intent.getFlags());
+ intent = newIntent;
+
+ resolvedType = null;
+ callingUid = realCallingUid;
+ callingPid = realCallingPid;
+
+ aInfo = resolveActivity(intent, null, PackageManager.MATCH_DEFAULT_ONLY
+ | ActivityManagerService.STOCK_PM_FLAGS, null, userId);
+
+ if (DEBUG_PERMISSIONS_REVIEW) {
+ Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true,
+ true, false) + "} from uid " + callingUid + " on display "
+ + (container == null ? (mFocusedStack == null ?
+ Display.DEFAULT_DISPLAY : mFocusedStack.mDisplayId) :
+ (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY :
+ container.mActivityDisplay.mDisplayId)));
+ }
+ }
+ }
+
ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
requestCode, componentSpecified, voiceSession != null, this, container, options);
@@ -2592,6 +2633,11 @@
mService.setFocusedActivityLocked(r, "startedActivity");
}
updateUserStackLocked(r.userId, targetStack);
+
+ if (!r.task.mResizeable && isStackDockedInEffect(targetStack.mStackId)) {
+ showNonResizeableDockToast(r.task.taskId);
+ }
+
return ActivityManager.START_SUCCESS;
}
@@ -3114,7 +3160,7 @@
for (int i = 0; i < count; i++) {
moveTaskToStackLocked(tasks.get(i).taskId,
FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS, "resizeStack",
- true /* animate */);
+ false /* animate */);
}
// stack shouldn't contain anymore activities, so nothing to resume.
@@ -3376,6 +3422,10 @@
final ActivityStack stack = moveTaskToStackUncheckedLocked(
task, stackId, toTop, forceFocus, "moveTaskToStack:" + reason);
+ if (!animate) {
+ stack.mNoAnimActivities.add(topActivity);
+ }
+
// Make sure the task has the appropriate bounds/size for the stack it is in.
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
@@ -3393,7 +3443,7 @@
resumeTopActivitiesLocked();
if (!task.mResizeable && isStackDockedInEffect(stackId)) {
- showNonResizeableDockToast();
+ showNonResizeableDockToast(taskId);
}
}
@@ -4327,8 +4377,8 @@
}
}
- void showNonResizeableDockToast() {
- mHandler.sendEmptyMessage(SHOW_NON_RESIZEABLE_DOCK_TOAST);
+ private void showNonResizeableDockToast(int taskId) {
+ mWindowManager.scheduleShowNonResizeableDockToast(taskId);
}
void showLockTaskToast() {
@@ -4643,13 +4693,7 @@
}
}
} break;
- case SHOW_NON_RESIZEABLE_DOCK_TOAST: {
- final Toast toast = Toast.makeText(
- mService.mContext,
- mService.mContext.getString(R.string.dock_non_resizeble_text),
- Toast.LENGTH_LONG);
- toast.show();
- } break;
+
}
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index fb37eda..b160981 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -27,12 +27,16 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.IIntentReceiver;
+import android.content.IIntentSender;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -46,6 +50,7 @@
import android.util.Slog;
import android.util.TimeUtils;
import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
import static com.android.server.am.ActivityManagerDebugConfig.*;
@@ -181,7 +186,7 @@
} break;
}
}
- };
+ }
private final class AppNotResponding implements Runnable {
private final ProcessRecord mApp;
@@ -580,6 +585,17 @@
}
if (!skip) {
+ // If permissions need a review before any of the app components can run, we drop
+ // the broadcast and if the calling app is in the foreground and the broadcast is
+ // explicit we launch the review UI passing it a pending intent to send the skipped
+ // broadcast.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
+ filter.owningUserId)) {
+ return;
+ }
+ }
+
// If this is not being sent as an ordered broadcast, then we
// don't want to touch the fields that keep track of the current
// state of ordered broadcasts.
@@ -622,6 +638,54 @@
}
}
+ private boolean requestStartTargetPermissionsReviewIfNeededLocked(
+ BroadcastRecord receiverRecord, String receivingPackageName,
+ final int receivingUserId) {
+ if (!mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+ receivingPackageName, receivingUserId)) {
+ return true;
+ }
+
+ final boolean callerForeground = receiverRecord.callerApp != null
+ ? receiverRecord.callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE
+ : true;
+
+ // Show a permission review UI only for explicit broadcast from a foreground app
+ if (callerForeground && receiverRecord.intent.getComponent() != null) {
+ IIntentSender target = mService.getIntentSenderLocked(
+ ActivityManager.INTENT_SENDER_BROADCAST, receiverRecord.callerPackage,
+ receiverRecord.callingUid, receiverRecord.userId, null, null, 0,
+ new Intent[]{receiverRecord.intent},
+ new String[]{receiverRecord.intent.resolveType(mService.mContext
+ .getContentResolver())},
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_IMMUTABLE, null);
+
+ final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName);
+ intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
+
+ if (DEBUG_PERMISSIONS_REVIEW) {
+ Slog.i(TAG, "u" + receivingUserId + " Launching permission review for package "
+ + receivingPackageName);
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mService.mContext.startActivityAsUser(intent, new UserHandle(receivingUserId));
+ }
+ });
+ } else {
+ Slog.w(TAG, "u" + receivingUserId + " Receiving a broadcast in package"
+ + receivingPackageName + " requires a permissions review");
+ }
+
+ return false;
+ }
+
final void scheduleTempWhitelistLocked(int uid, long duration, BroadcastRecord r) {
if (duration > Integer.MAX_VALUE) {
duration = Integer.MAX_VALUE;
@@ -1007,6 +1071,18 @@
}
}
+ // If permissions need a review before any of the app components can run, we drop
+ // the broadcast and if the calling app is in the foreground and the broadcast is
+ // explicit we launch the review UI passing it a pending intent to send the skipped
+ // broadcast.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED && !skip) {
+ if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
+ info.activityInfo.packageName, UserHandle.getUserId(
+ info.activityInfo.applicationInfo.uid))) {
+ skip = true;
+ }
+ }
+
if (skip) {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Skipping delivery of ordered [" + mQueueName + "] "
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b30905e..9c29149 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -624,6 +624,23 @@
throw new SecurityException(msg);
}
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ return unlockUserCleared(userId, token);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+ boolean unlockUserCleared(final int userId, byte[] token) {
+ synchronized (mService) {
+ final UserState uss = mStartedUsers.get(userId);
+ if (uss.unlocked) {
+ // Bail early when already unlocked
+ return true;
+ }
+ }
+
final UserInfo userInfo = getUserInfo(userId);
final IMountService mountService = IMountService.Stub
.asInterface(ServiceManager.getService("mount"));
@@ -631,7 +648,7 @@
mountService.unlockUserKey(userId, userInfo.serialNumber, token);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to unlock: " + e.getMessage());
- throw e.rethrowAsRuntimeException();
+ return false;
}
synchronized (mService) {
@@ -639,6 +656,11 @@
updateUserUnlockedState(uss);
}
+ final Intent intent = new Intent(Intent.ACTION_USER_UNLOCKED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ mService.broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
+ AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID, userId);
+
return true;
}
@@ -1011,7 +1033,7 @@
if ((flags & ActivityManager.FLAG_OR_STOPPED) != 0) {
return true;
}
- if ((flags & ActivityManager.FLAG_WITH_AMNESIA) != 0) {
+ if ((flags & ActivityManager.FLAG_AND_LOCKED) != 0) {
// If user is currently locked, we fall through to default "running"
// behavior below
if (state.unlocked) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index d44d89d..278d70b 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -387,8 +387,11 @@
/** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
- Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId + " req=" + focusChangeHint +
- "flags=0x" + Integer.toHexString(flags));
+ Log.i(TAG, " AudioFocus requestAudioFocus() from uid/pid " + Binder.getCallingUid()
+ + "/" + Binder.getCallingPid()
+ + " clientId=" + clientId
+ + " req=" + focusChangeHint
+ + " flags=0x" + Integer.toHexString(flags));
// we need a valid binder callback for clients
if (!cb.pingBinder()) {
Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
@@ -481,7 +484,9 @@
* */
protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) {
// AudioAttributes are currently ignored, to be used for zones
- Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId);
+ Log.i(TAG, " AudioFocus abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
+ + "/" + Binder.getCallingPid()
+ + " clientId=" + clientId);
try {
// this will take care of notifying the new focus owner if needed
synchronized(mAudioFocusLock) {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 6687412..3b43633 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -33,7 +33,9 @@
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
+import android.net.NetworkRequest;
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.os.Binder;
@@ -213,6 +215,7 @@
checkDunRequired();
}
+ @Override
public void interfaceStatusChanged(String iface, boolean up) {
if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
boolean found = false;
@@ -248,6 +251,7 @@
}
}
+ @Override
public void interfaceLinkStateChanged(String iface, boolean up) {
if (VDBG) Log.d(TAG, "interfaceLinkStateChanged " + iface + ", " + up);
interfaceStatusChanged(iface, up);
@@ -280,6 +284,7 @@
}
}
+ @Override
public void interfaceAdded(String iface) {
if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
boolean found = false;
@@ -311,6 +316,7 @@
}
}
+ @Override
public void interfaceRemoved(String iface) {
if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
synchronized (mPublicSync) {
@@ -638,7 +644,7 @@
return values;
}
- public void checkDunRequired() {
+ private void checkDunRequired() {
int secureSetting = 2;
TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null) {
@@ -1135,10 +1141,8 @@
static final int CMD_TETHER_MODE_UNREQUESTED = 2;
// upstream connection change - do the right thing
static final int CMD_UPSTREAM_CHANGED = 3;
- // we received notice that the cellular DUN connection is up
- static final int CMD_CELL_CONNECTION_RENEW = 4;
// we don't have a valid upstream conn, check again after a delay
- static final int CMD_RETRY_UPSTREAM = 5;
+ static final int CMD_RETRY_UPSTREAM = 4;
// This indicates what a timeout event relates to. A state that
// sends itself a delayed timeout event and handles incoming timeout events
@@ -1157,13 +1161,12 @@
private ArrayList<TetherInterfaceSM> mNotifyList;
- private int mCurrentConnectionSequence;
private int mMobileApnReserved = ConnectivityManager.TYPE_NONE;
+ private ConnectivityManager.NetworkCallback mMobileUpstreamCallback;
private String mUpstreamIfaceName = null;
private static final int UPSTREAM_SETTLE_TIME_MS = 10000;
- private static final int CELL_CONNECTION_RENEW_MS = 40000;
TetherMasterSM(String name, Looper looper) {
super(name, looper);
@@ -1190,58 +1193,69 @@
}
class TetherMasterUtilState extends State {
- protected final static boolean TRY_TO_SETUP_MOBILE_CONNECTION = true;
protected final static boolean WAIT_FOR_NETWORK_TO_SETTLE = false;
@Override
public boolean processMessage(Message m) {
return false;
}
- protected String enableString(int apnType) {
- switch (apnType) {
- case ConnectivityManager.TYPE_MOBILE_DUN:
- return Phone.FEATURE_ENABLE_DUN_ALWAYS;
- case ConnectivityManager.TYPE_MOBILE:
- case ConnectivityManager.TYPE_MOBILE_HIPRI:
- return Phone.FEATURE_ENABLE_HIPRI;
- }
- return null;
- }
+
protected boolean turnOnUpstreamMobileConnection(int apnType) {
- boolean retValue = true;
- if (apnType == ConnectivityManager.TYPE_NONE) return false;
- if (apnType != mMobileApnReserved) turnOffUpstreamMobileConnection();
- int result = PhoneConstants.APN_REQUEST_FAILED;
- String enableString = enableString(apnType);
- if (enableString == null) return false;
- result = getConnectivityManager().startUsingNetworkFeature(
- ConnectivityManager.TYPE_MOBILE, enableString);
- switch (result) {
- case PhoneConstants.APN_ALREADY_ACTIVE:
- case PhoneConstants.APN_REQUEST_STARTED:
- mMobileApnReserved = apnType;
- Message m = obtainMessage(CMD_CELL_CONNECTION_RENEW);
- m.arg1 = ++mCurrentConnectionSequence;
- sendMessageDelayed(m, CELL_CONNECTION_RENEW_MS);
- break;
- case PhoneConstants.APN_REQUEST_FAILED:
- default:
- retValue = false;
- break;
+ if (apnType == ConnectivityManager.TYPE_NONE) { return false; }
+
+ if (apnType != mMobileApnReserved) {
+ // Unregister any previous mobile upstream callback because
+ // this request, if any, will be different.
+ turnOffUpstreamMobileConnection();
}
- return retValue;
- }
- protected boolean turnOffUpstreamMobileConnection() {
- // ignore pending renewal requests
- ++mCurrentConnectionSequence;
- if (mMobileApnReserved != ConnectivityManager.TYPE_NONE) {
- getConnectivityManager().stopUsingNetworkFeature(
- ConnectivityManager.TYPE_MOBILE, enableString(mMobileApnReserved));
- mMobileApnReserved = ConnectivityManager.TYPE_NONE;
+ if (mMobileUpstreamCallback != null) {
+ // Looks like we already filed a request for this apnType.
+ return true;
}
+
+ switch (apnType) {
+ case ConnectivityManager.TYPE_MOBILE_DUN:
+ case ConnectivityManager.TYPE_MOBILE:
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ mMobileApnReserved = apnType;
+ break;
+ default:
+ return false;
+ }
+
+ NetworkRequest.Builder builder = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ if (apnType == ConnectivityManager.TYPE_MOBILE_DUN) {
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ } else {
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ }
+ NetworkRequest mobileUpstreamRequest = builder.build();
+ // Other mechanisms notice network and interface changes and act upon them.
+ // TODO, imminently: replace with a proper NetworkCallback-based scheme.
+ //
+ // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
+ // moderate callback time (once timeout callbacks are implemented). This might
+ // be useful for updating some UI. Additionally, we should definitely log a
+ // message to aid in any subsequent debugging.
+ mMobileUpstreamCallback = new ConnectivityManager.NetworkCallback();
+ if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
+ getConnectivityManager().requestNetwork(
+ mobileUpstreamRequest, mMobileUpstreamCallback, 0, apnType);
+
return true;
}
+
+ protected void turnOffUpstreamMobileConnection() {
+ if (mMobileUpstreamCallback != null) {
+ getConnectivityManager().unregisterNetworkCallback(mMobileUpstreamCallback);
+ mMobileUpstreamCallback = null;
+ }
+ mMobileApnReserved = ConnectivityManager.TYPE_NONE;
+ }
+
protected boolean turnOnMasterTetherSettings() {
try {
mNMService.setIpForwardingEnabled(true);
@@ -1304,35 +1318,39 @@
}
if (DBG) {
- Log.d(TAG, "chooseUpstreamType(" + tryCell + "), preferredApn ="
- + mPreferredUpstreamMobileApn + ", got type=" + upType);
+ Log.d(TAG, "chooseUpstreamType(" + tryCell + "),"
+ + " preferredApn="
+ + ConnectivityManager.getNetworkTypeName(mPreferredUpstreamMobileApn)
+ + ", got type="
+ + ConnectivityManager.getNetworkTypeName(upType));
}
- // if we're on DUN, put our own grab on it
- if (upType == ConnectivityManager.TYPE_MOBILE_DUN ||
- upType == ConnectivityManager.TYPE_MOBILE_HIPRI) {
- turnOnUpstreamMobileConnection(upType);
- } else if (upType != ConnectivityManager.TYPE_NONE) {
- /* If we've found an active upstream connection that's not DUN/HIPRI
- * we should stop any outstanding DUN/HIPRI start requests.
- *
- * If we found NONE we don't want to do this as we want any previous
- * requests to keep trying to bring up something we can use.
- */
- turnOffUpstreamMobileConnection();
+ switch (upType) {
+ case ConnectivityManager.TYPE_MOBILE_DUN:
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ // If we're on DUN, put our own grab on it.
+ turnOnUpstreamMobileConnection(upType);
+ break;
+ case ConnectivityManager.TYPE_NONE:
+ if (tryCell &&
+ turnOnUpstreamMobileConnection(mPreferredUpstreamMobileApn)) {
+ // We think mobile should be coming up; don't set a retry.
+ } else {
+ sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
+ }
+ break;
+ default:
+ /* If we've found an active upstream connection that's not DUN/HIPRI
+ * we should stop any outstanding DUN/HIPRI start requests.
+ *
+ * If we found NONE we don't want to do this as we want any previous
+ * requests to keep trying to bring up something we can use.
+ */
+ turnOffUpstreamMobileConnection();
+ break;
}
- if (upType == ConnectivityManager.TYPE_NONE) {
- boolean tryAgainLater = true;
- if ((tryCell == TRY_TO_SETUP_MOBILE_CONNECTION) &&
- (turnOnUpstreamMobileConnection(mPreferredUpstreamMobileApn) == true)) {
- // we think mobile should be coming up - don't set a retry
- tryAgainLater = false;
- }
- if (tryAgainLater) {
- sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
- }
- } else {
+ if (upType != ConnectivityManager.TYPE_NONE) {
LinkProperties linkProperties =
getConnectivityManager().getLinkProperties(upType);
if (linkProperties != null) {
@@ -1579,17 +1597,6 @@
chooseUpstreamType(mTryCell);
mTryCell = !mTryCell;
break;
- case CMD_CELL_CONNECTION_RENEW:
- // make sure we're still using a requested connection - may have found
- // wifi or something since then.
- if (mCurrentConnectionSequence == message.arg1) {
- if (VDBG) {
- Log.d(TAG, "renewing mobile connection - requeuing for another " +
- CELL_CONNECTION_RENEW_MS + "ms");
- }
- turnOnUpstreamMobileConnection(mMobileApnReserved);
- }
- break;
case CMD_RETRY_UPSTREAM:
chooseUpstreamType(mTryCell);
mTryCell = !mTryCell;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 81ae8ac..ae8fca8 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.input;
+import android.annotation.Nullable;
import android.view.Display;
import com.android.internal.os.SomeArgs;
import com.android.internal.R;
@@ -83,6 +84,9 @@
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
import android.widget.Toast;
import java.io.File;
@@ -116,6 +120,7 @@
private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6;
+ private static final int MSG_INPUT_METHOD_SUBTYPE_CHANGED = 7;
// Pointer to native input manager service object.
private final long mPtr;
@@ -1206,6 +1211,15 @@
}
}
+ // Must be called on handler.
+ private void handleSwitchInputMethodSubtype(int userId,
+ @Nullable InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype subtype) {
+ if (DEBUG) {
+ Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
+ + " ime=" + inputMethodInfo + " subtype=" + subtype);
+ }
+ }
+
public void switchKeyboardLayout(int deviceId, int direction) {
mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
}
@@ -1757,12 +1771,22 @@
case MSG_RELOAD_DEVICE_ALIASES:
reloadDeviceAliases();
break;
- case MSG_DELIVER_TABLET_MODE_CHANGED:
+ case MSG_DELIVER_TABLET_MODE_CHANGED: {
SomeArgs args = (SomeArgs) msg.obj;
long whenNanos = (args.argi1 & 0xFFFFFFFFl) | ((long) args.argi2 << 32);
boolean inTabletMode = (boolean) args.arg1;
deliverTabletModeChanged(whenNanos, inTabletMode);
break;
+ }
+ case MSG_INPUT_METHOD_SUBTYPE_CHANGED: {
+ final int userId = msg.arg1;
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final InputMethodInfo inputMethodInfo = (InputMethodInfo) args.arg1;
+ final InputMethodSubtype subtype = (InputMethodSubtype) args.arg2;
+ args.recycle();
+ handleSwitchInputMethodSubtype(userId, inputMethodInfo, subtype);
+ break;
+ }
}
}
}
@@ -1920,5 +1944,15 @@
public void setInteractive(boolean interactive) {
nativeSetInteractive(mPtr, interactive);
}
+
+ @Override
+ public void onInputMethodSubtypeChanged(int userId,
+ @Nullable InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype subtype) {
+ final SomeArgs someArgs = SomeArgs.obtain();
+ someArgs.arg1 = inputMethodInfo;
+ someArgs.arg2 = subtype;
+ mHandler.obtainMessage(MSG_INPUT_METHOD_SUBTYPE_CHANGED, userId, 0, someArgs)
+ .sendToTarget();
+ }
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index f92f631..246da2e 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -63,7 +63,7 @@
*/
public class MediaSessionRecord implements IBinder.DeathRecipient {
private static final String TAG = "MediaSessionRecord";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/**
* The length of time a session will still be considered active after
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 90dd10ea..d5c3113 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -287,6 +287,9 @@
* 6. We need to tell the session to do any final cleanup (onDestroy)
*/
private void destroySessionLocked(MediaSessionRecord session) {
+ if (DEBUG) {
+ Log.d(TAG, "Destroying session : " + session.toString());
+ }
int userId = session.getUserId();
UserRecord user = mUserRecords.get(userId);
if (user != null) {
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 611718e..61c320b 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -16,13 +16,17 @@
package com.android.server.media;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.PlaybackState;
import android.media.session.MediaSession;
+import android.os.RemoteException;
import android.os.UserHandle;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* Keeps track of media sessions and their priority for notifications, media
@@ -61,6 +65,36 @@
private ArrayList<MediaSessionRecord> mCachedTransportControlList;
/**
+ * Checks if a media session is created from the most recent app.
+ *
+ * @param record A media session record to be examined.
+ * @return true if the media session's package name equals to the most recent app, false
+ * otherwise.
+ */
+ private static boolean isFromMostRecentApp(MediaSessionRecord record) {
+ if (ActivityManager.getCurrentUser() != record.getUserId()) {
+ return false;
+ }
+ try {
+ List<ActivityManager.RecentTaskInfo> tasks =
+ ActivityManagerNative.getDefault().getRecentTasks(1,
+ ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
+ ActivityManager.RECENT_IGNORE_UNAVAILABLE |
+ ActivityManager.RECENT_INCLUDE_PROFILES |
+ ActivityManager.RECENT_WITH_EXCLUDED, record.getUserId());
+ if (tasks != null && !tasks.isEmpty()) {
+ ActivityManager.RecentTaskInfo recentTask = tasks.get(0);
+ if (recentTask.baseIntent != null)
+ return recentTask.baseIntent.getComponent().getPackageName()
+ .equals(record.getPackageName());
+ }
+ } catch (RemoteException e) {
+ return false;
+ }
+ return false;
+ }
+
+ /**
* Add a record to the priority tracker.
*
* @param record The record to add.
@@ -68,7 +102,9 @@
public void addSession(MediaSessionRecord record) {
mSessions.add(record);
clearCache();
- mLastInterestingRecord = record;
+ if (isFromMostRecentApp(record)) {
+ mLastInterestingRecord = record;
+ }
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 946fbb1..38893b8 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1286,10 +1286,11 @@
Binder.getCallingUid(), incomingUserId, true, false,
"getAppActiveNotifications", pkg);
- final int N = mNotificationList.size();
- final ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>(N);
+ final ArrayList<StatusBarNotification> list
+ = new ArrayList<StatusBarNotification>(mNotificationList.size());
synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
for (int i = 0; i < N; i++) {
final StatusBarNotification sbn = mNotificationList.get(i).sbn;
if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4bc79cb..0fde27f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -26,6 +26,7 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
@@ -400,14 +401,11 @@
/** Permission grant: grant the permission as an install permission. */
private static final int GRANT_INSTALL = 2;
- /** Permission grant: grant the permission as an install permission for a legacy app. */
- private static final int GRANT_INSTALL_LEGACY = 3;
-
/** Permission grant: grant the permission as a runtime one. */
- private static final int GRANT_RUNTIME = 4;
+ private static final int GRANT_RUNTIME = 3;
/** Permission grant: grant as runtime a permission that was granted as an install time one. */
- private static final int GRANT_UPGRADE = 5;
+ private static final int GRANT_UPGRADE = 4;
/** Canonical intent used to identify what counts as a "web browser" app */
private static final Intent sBrowserIntent;
@@ -2800,15 +2798,24 @@
}
@Override
- public boolean isPackageFrozen(String packageName) {
+ public void checkPackageStartable(String packageName, int userId) {
+ final boolean userKeyUnlocked = isUserKeyUnlocked(userId);
+
synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
- if (ps != null) {
- return ps.frozen;
+ if (ps == null) {
+ throw new SecurityException("Package " + packageName + " was not found!");
+ }
+
+ if (ps.frozen) {
+ throw new SecurityException("Package " + packageName + " is currently frozen!");
+ }
+
+ if (!userKeyUnlocked && !(ps.pkg.applicationInfo.isEncryptionAware()
+ || ps.pkg.applicationInfo.isPartiallyEncryptionAware())) {
+ throw new SecurityException("Package " + packageName + " is not encryption aware!");
}
}
- Slog.w(TAG, "Package " + packageName + " is missing; assuming frozen");
- return true;
}
@Override
@@ -3143,29 +3150,36 @@
}
/**
- * Augment the given flags depending on current user running state. This is
- * purposefully done before acquiring {@link #mPackages} lock.
+ * Return if the user key is currently unlocked.
*/
- private int augmentFlagsForUser(int flags, int userId) {
+ private boolean isUserKeyUnlocked(int userId) {
if (StorageManager.isFileBasedEncryptionEnabled()) {
final IMountService mount = IMountService.Stub
.asInterface(ServiceManager.getService("mount"));
if (mount == null) {
- // We must be early in boot, so the best we can do is assume the
- // user is fully running.
- Slog.w(TAG, "Early during boot, assuming not encrypted");
- return flags;
+ Slog.w(TAG, "Early during boot, assuming locked");
+ return false;
}
final long token = Binder.clearCallingIdentity();
try {
- if (!mount.isUserKeyUnlocked(userId)) {
- flags |= PackageManager.MATCH_ENCRYPTION_AWARE_ONLY;
- }
+ return mount.isUserKeyUnlocked(userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
Binder.restoreCallingIdentity(token);
}
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Augment the given flags depending on current user running state. This is
+ * purposefully done before acquiring {@link #mPackages} lock.
+ */
+ private int augmentFlagsForUser(int flags, int userId) {
+ if (!isUserKeyUnlocked(userId)) {
+ flags |= PackageManager.MATCH_ENCRYPTION_AWARE_ONLY;
}
return flags;
}
@@ -3672,6 +3686,16 @@
enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted runtime ones since we need
+ // to keep the review required permission flag per user while an
+ // install permission's state is shared across all users.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED
+ && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+ && bp.isRuntime()) {
+ return;
+ }
+
uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
sb = (SettingBase) pkg.mExtras;
if (sb == null) {
@@ -3772,6 +3796,16 @@
enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted runtime ones since we need
+ // to keep the review required permission flag per user while an
+ // install permission's state is shared across all users.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED
+ && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+ && bp.isRuntime()) {
+ return;
+ }
+
SettingBase sb = (SettingBase) pkg.mExtras;
if (sb == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
@@ -3890,6 +3924,7 @@
flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+ flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
}
synchronized (mPackages) {
@@ -8625,6 +8660,8 @@
}
final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+ final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
+ >= Build.VERSION_CODES.M;
switch (level) {
case PermissionInfo.PROTECTION_NORMAL: {
// For all apps normal permissions are install time ones.
@@ -8632,9 +8669,13 @@
} break;
case PermissionInfo.PROTECTION_DANGEROUS: {
- if (pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted runtime ones since we need
+ // to keep the review required permission flag per user while an
+ // install permission's state is shared across all users.
+ if (!appSupportsRuntimePermissions && !Build.PERMISSIONS_REVIEW_REQUIRED) {
// For legacy apps dangerous permissions are install time ones.
- grant = GRANT_INSTALL_LEGACY;
+ grant = GRANT_INSTALL;
} else if (origPermissions.hasInstallPermission(bp.name)) {
// For legacy apps that became modern, install becomes runtime.
grant = GRANT_UPGRADE;
@@ -8681,7 +8722,7 @@
switch (grant) {
case GRANT_INSTALL: {
// Revoke this as runtime permission to handle the case of
- // a runtime permission being downgraded to an install one.
+ // a runtime permission being downgraded to an install one. Also in permission review mode we keep dangerous permissions for legacy apps
for (int userId : UserManagerService.getInstance().getUserIds()) {
if (origPermissions.getRuntimePermissionState(
bp.name, userId) != null) {
@@ -8701,20 +8742,12 @@
}
} break;
- case GRANT_INSTALL_LEGACY: {
- // Grant an install permission.
- if (permissionsState.grantInstallPermission(bp) !=
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- changedInstallPermission = true;
- }
- } break;
-
case GRANT_RUNTIME: {
// Grant previously granted runtime permissions.
for (int userId : UserManagerService.getInstance().getUserIds()) {
PermissionState permissionState = origPermissions
.getRuntimePermissionState(bp.name, userId);
- final int flags = permissionState != null
+ int flags = permissionState != null
? permissionState.getFlags() : 0;
if (origPermissions.hasRuntimePermission(bp.name, userId)) {
if (permissionsState.grantRuntimePermission(bp, userId) ==
@@ -8723,6 +8756,27 @@
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
+ // If the app supports runtime permissions no need for a review.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED
+ && appSupportsRuntimePermissions
+ && (flags & PackageManager
+ .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+ // Since we changed the flags, we have to write.
+ changedRuntimePermissionUserIds = ArrayUtils.appendInt(
+ changedRuntimePermissionUserIds, userId);
+ }
+ } else if (Build.PERMISSIONS_REVIEW_REQUIRED
+ && !appSupportsRuntimePermissions) {
+ // For legacy apps that need a permission review, every new
+ // runtime permission is granted but it is pending a review.
+ if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+ permissionsState.grantRuntimePermission(bp, userId);
+ flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
+ // We changed the permission and flags, hence have to write.
+ changedRuntimePermissionUserIds = ArrayUtils.appendInt(
+ changedRuntimePermissionUserIds, userId);
+ }
}
// Propagate the permission flags.
permissionsState.updatePermissionFlags(bp, userId, flags, flags);
@@ -11574,7 +11628,7 @@
}
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
- if (origin.staged) {
+ if (origin.staged && origin.cid != null) {
if (DEBUG_INSTALL) Slog.d(TAG, origin.cid + " already staged; skipping copy");
cid = origin.cid;
setMountPath(PackageHelper.getSdDir(cid));
@@ -13784,9 +13838,11 @@
return;
}
- final int userSettableFlags = FLAG_PERMISSION_USER_SET
+ // These are flags that can change base on user actions.
+ final int userSettableMask = FLAG_PERMISSION_USER_SET
| FLAG_PERMISSION_USER_FIXED
- | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+ | FLAG_PERMISSION_REVOKE_ON_UPGRADE
+ | FLAG_PERMISSION_REVIEW_REQUIRED;
final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED
| FLAG_PERMISSION_POLICY_FIXED;
@@ -13827,7 +13883,14 @@
// Always clear the user settable flags.
final boolean hasInstallState = permissionsState.getInstallPermissionState(
bp.name) != null;
- if (permissionsState.updatePermissionFlags(bp, userId, userSettableFlags, 0)) {
+ // If permission review is enabled and this is a legacy app, mark the
+ // permission as requiring a review as this is the initial state.
+ int flags = 0;
+ if (Build.PERMISSIONS_REVIEW_REQUIRED
+ && ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+ flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
+ }
+ if (permissionsState.updatePermissionFlags(bp, userId, userSettableMask, flags)) {
if (hasInstallState) {
writeInstallPermissions = true;
} else {
@@ -13851,7 +13914,9 @@
!= PERMISSION_OPERATION_FAILURE) {
writeRuntimePermissions = true;
}
- } else {
+ // If permission review is enabled the permissions for a legacy apps
+ // are represented as constantly granted runtime ones, so don't revoke.
+ } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
// Otherwise, reset the permission.
final int revokeResult = permissionsState.revokeRuntimePermission(bp, userId);
switch (revokeResult) {
@@ -16744,6 +16809,15 @@
void newUserCreated(final int userHandle) {
mDefaultPermissionPolicy.grantDefaultPermissions(userHandle);
+ // If permission review for legacy apps is required, we represent
+ // dagerous permissions for such apps as always granted runtime
+ // permissions to keep per user flag state whether review is needed.
+ // Hence, if a new user is added we have to propagate dangerous
+ // permission grants for these legacy apps.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
+ | UPDATE_PERMISSIONS_REPLACE_ALL);
+ }
}
@Override
@@ -17159,6 +17233,7 @@
packageName, userId);
}
}
+
@Override
public void grantDefaultPermissionsToDefaultSimCallManager(String packageName, int userId) {
synchronized (mPackages) {
@@ -17194,6 +17269,30 @@
}
}
}
+
+ @Override
+ public boolean isPermissionsReviewRequired(String packageName, int userId) {
+ synchronized (mPackages) {
+ // If we do not support permission review, done.
+ if (!Build.PERMISSIONS_REVIEW_REQUIRED) {
+ return false;
+ }
+
+ PackageSetting packageSetting = mSettings.mPackages.get(packageName);
+ if (packageSetting == null) {
+ return false;
+ }
+
+ // Permission review applies only to apps not supporting the new permission model.
+ if (packageSetting.pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+ return false;
+ }
+
+ // Legacy apps have the permission and get user consent on launch.
+ PermissionsState permissionsState = packageSetting.getPermissionsState();
+ return permissionsState.isPermissionReviewRequired(userId);
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java
index 57ef284..007b738 100644
--- a/services/core/java/com/android/server/pm/PermissionsState.java
+++ b/services/core/java/com/android/server/pm/PermissionsState.java
@@ -16,11 +16,13 @@
package com.android.server.pm;
+import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
@@ -64,6 +66,8 @@
private int[] mGlobalGids = NO_GIDS;
+ private SparseBooleanArray mPermissionReviewRequired;
+
public PermissionsState() {
/* do nothing */
}
@@ -116,6 +120,28 @@
mGlobalGids = Arrays.copyOf(other.mGlobalGids,
other.mGlobalGids.length);
}
+
+ if (mPermissionReviewRequired != null) {
+ if (other.mPermissionReviewRequired == null) {
+ mPermissionReviewRequired = null;
+ } else {
+ mPermissionReviewRequired.clear();
+ }
+ }
+ if (other.mPermissionReviewRequired != null) {
+ if (mPermissionReviewRequired == null) {
+ mPermissionReviewRequired = new SparseBooleanArray();
+ }
+ final int userCount = other.mPermissionReviewRequired.size();
+ for (int i = 0; i < userCount; i++) {
+ final boolean reviewRequired = other.mPermissionReviewRequired.valueAt(i);
+ mPermissionReviewRequired.put(i, reviewRequired);
+ }
+ }
+ }
+
+ public boolean isPermissionReviewRequired(int userId) {
+ return mPermissionReviewRequired != null && mPermissionReviewRequired.get(userId);
}
/**
@@ -357,7 +383,28 @@
permissionData = ensurePermissionData(permission);
}
- return permissionData.updateFlags(userId, flagMask, flagValues);
+ final int oldFlags = permissionData.getFlags(userId);
+
+ final boolean updated = permissionData.updateFlags(userId, flagMask, flagValues);
+ if (updated) {
+ final int newFlags = permissionData.getFlags(userId);
+ if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0
+ && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ if (mPermissionReviewRequired == null) {
+ mPermissionReviewRequired = new SparseBooleanArray();
+ }
+ mPermissionReviewRequired.put(userId, true);
+ } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0
+ && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+ if (mPermissionReviewRequired != null) {
+ mPermissionReviewRequired.delete(userId);
+ if (mPermissionReviewRequired.size() <= 0) {
+ mPermissionReviewRequired = null;
+ }
+ }
+ }
+ }
+ return updated;
}
public boolean updatePermissionFlagsForAllPermissions(
@@ -430,6 +477,7 @@
public void reset() {
mGlobalGids = NO_GIDS;
mPermissions = null;
+ mPermissionReviewRequired = null;
}
private PermissionState getPermissionState(String name, int userId) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 424b902..da10a94 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -652,6 +652,7 @@
private void initDefaultGuestRestrictions() {
synchronized (mGuestRestrictions) {
if (mGuestRestrictions.isEmpty()) {
+ mGuestRestrictions.putBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
}
@@ -1652,6 +1653,11 @@
}
updateUserIds();
Bundle restrictions = new Bundle();
+ if (isGuest) {
+ synchronized (mGuestRestrictions) {
+ restrictions.putAll(mGuestRestrictions);
+ }
+ }
synchronized (mRestrictionsLock) {
mBaseUserRestrictions.append(userId, restrictions);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ae6874f..639753a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2533,7 +2533,7 @@
}
}
} else if (win.getAttrs().type == TYPE_DOCK_DIVIDER) {
- if (transit == TRANSIT_ENTER) {
+ if (transit == TRANSIT_ENTER || transit == TRANSIT_SHOW) {
return R.anim.fade_in;
} else if (transit == TRANSIT_EXIT) {
return R.anim.fade_out;
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index b32ec2d..4861acc 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -109,7 +109,7 @@
public AppWindowAnimator(final AppWindowToken atoken) {
mAppToken = atoken;
mService = atoken.service;
- mAnimator = atoken.mAnimator;
+ mAnimator = mService.mAnimator;
}
public void setAnimation(Animation anim, int width, int height, boolean skipFirstFrame) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 425ff9b..3301c49 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -24,6 +24,7 @@
import com.android.server.input.InputApplicationHandle;
import com.android.server.wm.WindowManagerService.H;
+import android.annotation.NonNull;
import android.content.pm.ActivityInfo;
import android.os.Message;
import android.os.RemoteException;
@@ -49,9 +50,7 @@
// All of the windows and child windows that are included in this
// application token. Note this list is NOT sorted!
final WindowList allAppWindows = new WindowList();
- final AppWindowAnimator mAppAnimator;
-
- final WindowAnimator mAnimator;
+ @NonNull final AppWindowAnimator mAppAnimator;
final boolean voiceInteraction;
@@ -145,7 +144,6 @@
appToken = _token;
voiceInteraction = _voiceInteraction;
mInputApplicationHandle = new InputApplicationHandle(this);
- mAnimator = service.mAnimator;
mAppAnimator = new AppWindowAnimator(this);
}
@@ -259,7 +257,7 @@
if (win.mAttrs.type == WindowManager.LayoutParams.TYPE_BASE_APPLICATION
|| win.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
// In cases where there are multiple windows, we prefer the non-exiting window. This
- // happens for example when when replacing windows during an activity relaunch. When
+ // happens for example when replacing windows during an activity relaunch. When
// constructing the animation, we want the new window, not the exiting one.
if (win.mExiting) {
candidate = win;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e9e09ec..4bbf586 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -94,8 +94,9 @@
Region mNonResizeableRegion = new Region();
/** Save allocating when calculating rects */
- private Rect mTmpRect = new Rect();
- private Rect mTmpRect2 = new Rect();
+ private final Rect mTmpRect = new Rect();
+ private final Rect mTmpRect2 = new Rect();
+ private final Region mTmpRegion = new Region();
/** For gathering Task objects in order. */
final ArrayList<Task> mTmpTaskHistory = new ArrayList<Task>();
@@ -403,6 +404,14 @@
if (addBackFocusedTask) {
mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
}
+ final WindowState inputMethod = mService.mInputMethodWindow;
+ if (inputMethod != null && inputMethod.isVisibleLw()) {
+ // If the input method is visible and the user is typing, we don't want these touch
+ // events to be intercepted and used to change focus. This would likely cause a
+ // disappearance of the input method.
+ inputMethod.getTouchableRegion(mTmpRegion);
+ mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
+ }
if (mTapDetector != null) {
mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion, mNonResizeableRegion);
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 6b62467..df8d5d6 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -18,7 +18,11 @@
import android.content.Context;
import android.graphics.Rect;
+import android.os.RemoteException;
import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.IDockDividerVisibilityListener;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.view.WindowManager.DOCKED_BOTTOM;
@@ -40,6 +44,8 @@
private WindowState mWindow;
private final Rect mTmpRect = new Rect();
private final Rect mLastRect = new Rect();
+ private IDockDividerVisibilityListener mListener;
+ private boolean mLastVisibility = false;
DockedStackDividerController(Context context, DisplayContent displayContent) {
mDisplayContent = displayContent;
@@ -67,12 +73,21 @@
}
void reevaluateVisibility() {
- if (mWindow == null) return;
+ if (mWindow == null) {
+ return;
+ }
TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID);
- if (stack != null && stack.isVisibleLocked()) {
- mWindow.showLw(true /* doAnimation */);
- } else {
- mWindow.hideLw(true /* doAnimation */);
+ final boolean visible = stack != null && stack.isVisibleLocked();
+ if (mLastVisibility == visible) {
+ return;
+ }
+ mLastVisibility = visible;
+ if (mListener != null) {
+ try {
+ mListener.onDockDividerVisibilityChanged(visible);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "visibility call failed: " + e);
+ }
}
}
@@ -110,4 +125,11 @@
}
mLastRect.set(frame);
}
+
+ public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ if (mListener != null && listener != null) {
+ throw new IllegalStateException("Dock divider visibility listener already set!");
+ }
+ mListener = listener;
+ }
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 74e8e53..22f1d63 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -26,6 +26,11 @@
import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
+import static com.android.server.wm.WindowManagerService.H.SHOW_NON_RESIZEABLE_DOCK_TOAST;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
import android.app.ActivityManager.StackId;
import android.content.res.Configuration;
@@ -76,6 +81,11 @@
// Whether the task is resizeable
private boolean mResizeable;
+ // Whether we need to show toast about the app being non-resizeable when it becomes visible.
+ // This flag is set when a non-resizeable task is docked (or side-by-side). It's cleared
+ // after we show the toast.
+ private boolean mShowNonResizeableDockToast;
+
// Whether the task is currently being drag-resized
private boolean mDragResizing;
@@ -92,6 +102,41 @@
return mStack.getDisplayContent();
}
+ void setShowNonResizeableDockToast() {
+ mShowNonResizeableDockToast = true;
+ }
+
+ void scheduleShowNonResizeableDockToastIfNeeded() {
+ if (!mShowNonResizeableDockToast) {
+ return;
+ }
+ final DisplayContent displayContent = mStack.getDisplayContent();
+ // If docked stack is not yet visible, we don't want to show the toast yet,
+ // since we need the visible rect of the docked task to position the toast.
+ if (displayContent == null || displayContent.getDockedStackLocked() == null) {
+ return;
+ }
+
+ mShowNonResizeableDockToast = false;
+
+ final int dockSide = mStack.getDockSide();
+ int xOffset = 0;
+ int yOffset = 0;
+ if (dockSide != DOCKED_INVALID) {
+ mStack.getBounds(mTmpRect);
+ displayContent.getLogicalDisplayRect(mTmpRect2);
+
+ if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
+ xOffset = mTmpRect.centerX() - mTmpRect2.centerX();
+ } else if (dockSide == DOCKED_TOP) {
+ // We don't adjust for DOCKED_BOTTOM case since it's already at the bottom.
+ yOffset = mTmpRect2.bottom - mTmpRect.bottom;
+ }
+ mService.mH.obtainMessage(
+ SHOW_NON_RESIZEABLE_DOCK_TOAST, xOffset, yOffset).sendToTarget();
+ }
+ }
+
void addAppToken(int addPos, AppWindowToken wtoken) {
final int lastPos = mAppTokens.size();
if (addPos >= lastPos) {
@@ -517,8 +562,10 @@
}
public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("taskId="); pw.print(mTaskId);
- pw.print(prefix); pw.print("appTokens="); pw.print(mAppTokens);
- pw.print(prefix); pw.print("mdr="); pw.println(mDeferRemoval);
+ pw.print(prefix); pw.print("taskId="); pw.println(mTaskId);
+ pw.print(prefix + prefix); pw.print("mFullscreen="); pw.println(mFullscreen);
+ pw.print(prefix + prefix); pw.print("mBounds="); pw.println(mBounds.toShortString());
+ pw.print(prefix + prefix); pw.print("mdr="); pw.println(mDeferRemoval);
+ pw.print(prefix + prefix); pw.print("appTokens="); pw.println(mAppTokens);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 87deaa4..116c16e 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -602,6 +602,8 @@
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("mStackId="); pw.println(mStackId);
pw.print(prefix); pw.print("mDeferDetach="); pw.println(mDeferDetach);
+ pw.print(prefix); pw.print("mFullscreen="); pw.println(mFullscreen);
+ pw.print(prefix); pw.print("mBounds="); pw.println(mBounds.toShortString());
for (int taskNdx = 0; taskNdx < mTasks.size(); ++taskNdx) {
pw.print(prefix);
mTasks.get(taskNdx).printTo(prefix + " ", pw);
@@ -658,10 +660,11 @@
}
/**
- * For docked workspace provides information which side of the screen was the dock anchored.
+ * For docked workspace (or workspace that's side-by-side to the docked), provides
+ * information which side of the screen was the dock anchored.
*/
int getDockSide() {
- if (mStackId != DOCKED_STACK_ID) {
+ if (mStackId != DOCKED_STACK_ID && !StackId.isResizeableByDockedStack(mStackId)) {
return DOCKED_INVALID;
}
if (mDisplayContent == null) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 46fab2a..f20c4e5 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -220,8 +220,8 @@
if (appShowWhenLocked != null) {
allowWhenLocked |= appShowWhenLocked == win.mAppToken
- // Show all SHOW_WHEN_LOCKED windows while they're animating
- || (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0 && win.isAnimatingLw()
+ // Show all SHOW_WHEN_LOCKED windows if some apps are shown over lockscreen
+ || (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0
// Show error dialogs over apps that dismiss keyguard.
|| (win.mAttrs.privateFlags & PRIVATE_FLAG_SYSTEM_ERROR) != 0;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ac90daf..dab195f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -121,6 +121,7 @@
import android.view.Gravity;
import android.view.IApplicationToken;
import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.IDockDividerVisibilityListener;
import android.view.IInputFilter;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
@@ -149,8 +150,10 @@
import android.view.WindowManagerPolicy;
import android.view.WindowManagerPolicy.PointerEventListener;
import android.view.animation.Animation;
+import android.widget.Toast;
import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.R;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
@@ -2689,8 +2692,7 @@
// need to see about starting one.
final boolean notExitingOrAnimating =
!win.mExiting && !win.isAnimatingWithSavedSurface();
- result |= notExitingOrAnimating
- ? RELAYOUT_RES_SURFACE_CHANGED : 0;
+ result |= notExitingOrAnimating ? RELAYOUT_RES_SURFACE_CHANGED : 0;
if (notExitingOrAnimating) {
focusMayChange = tryStartingAnimation(win, winAnimator, isDefaultDisplay,
focusMayChange);
@@ -3075,7 +3077,7 @@
// TODO:
}
- boolean checkCallingPermission(String permission, String func) {
+ private boolean checkCallingPermission(String permission, String func) {
// Quick check: if the calling permission is me, it's all okay.
if (Binder.getCallingPid() == Process.myPid()) {
return true;
@@ -4787,6 +4789,17 @@
}
}
+ public void scheduleShowNonResizeableDockToast(int taskId) {
+ synchronized (mWindowMap) {
+ Task task = mTaskIdToTask.get(taskId);
+ if (task == null) {
+ if (DEBUG_STACK) Slog.i(TAG, "scheduleShowToast: could not find taskId=" + taskId);
+ return;
+ }
+ task.setShowNonResizeableDockToast();
+ }
+ }
+
public void getStackBounds(int stackId, Rect bounds) {
synchronized (mWindowMap) {
final TaskStack stack = mStackIdToStack.get(stackId);
@@ -7452,6 +7465,7 @@
public static final int RESIZE_TASK = 44;
public static final int TWO_FINGER_SCROLL_START = 45;
+ public static final int SHOW_NON_RESIZEABLE_DOCK_TOAST = 46;
/**
* Used to denote that an integer field in a message will not be used.
@@ -8024,6 +8038,17 @@
}
}
break;
+ case SHOW_NON_RESIZEABLE_DOCK_TOAST: {
+ final Toast toast = Toast.makeText(mContext,
+ mContext.getString(R.string.dock_non_resizeble_text),
+ Toast.LENGTH_LONG);
+ final int gravity = toast.getGravity();
+ final int xOffset = toast.getXOffset() + msg.arg1;
+ final int yOffset = toast.getYOffset() + msg.arg2;
+ toast.setGravity(gravity, xOffset, yOffset);
+ toast.show();
+ }
+ break;
}
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG, "handleMessage: exit");
@@ -10211,6 +10236,29 @@
mDestroySurface.add(win);
}
+ @Override
+ public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ if (!checkCallingPermission(android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS,
+ "registerDockDividerVisibilityListener()")) {
+ return;
+ }
+ // TODO(multi-display): The listener is registered on the default display only.
+ final DockedStackDividerController controller =
+ getDefaultDisplayContentLocked().getDockedDividerController();
+ controller.registerDockDividerVisibilityListener(listener);
+ try {
+ listener.asBinder().linkToDeath(new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ getDefaultDisplayContentLocked().getDockedDividerController()
+ .registerDockDividerVisibilityListener(null);
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ controller.registerDockDividerVisibilityListener(null);
+ }
+ }
+
private final class LocalService extends WindowManagerInternal {
@Override
public void requestTraversalFromDisplayManager() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 064b412..29cadf3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -730,7 +730,7 @@
mVisibleFrame.set(mContentFrame);
mStableFrame.set(mContentFrame);
} else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
- if (isVisibleLw()) {
+ if (isVisibleLw() || mWinAnimator.isAnimating()) {
// We don't adjust the dock divider frame for reasons other than performance. The
// real reason is that if it gets adjusted before it is shown for the first time,
// it would get size (0, 0). This causes a problem when we finally show the dock
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 93c2ff6..132b1b6 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1474,6 +1474,11 @@
}
mWin.mAppToken.updateReportedVisibilityLocked();
}
+
+ final Task task = mWin.getTask();
+ if (task != null) {
+ task.scheduleShowNonResizeableDockToastIfNeeded();
+ }
return true;
}
return false;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8199250..dbfd80d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3855,10 +3855,7 @@
synchronized (this) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(comp, userHandle);
if (admin == null) {
- try {
- result.sendResult(null);
- } catch (RemoteException e) {
- }
+ result.sendResult(null);
return;
}
Intent intent = new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLE_REQUESTED);
@@ -3868,10 +3865,7 @@
null, new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- try {
- result.sendResult(getResultExtras(false));
- } catch (RemoteException e) {
- }
+ result.sendResult(getResultExtras(false));
}
}, null, Activity.RESULT_OK, null, null);
}
@@ -3891,39 +3885,31 @@
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
- DevicePolicyData p = getUserData(userHandle);
-
validateQualityConstant(quality);
- synchronized (this) {
- if (p.mActivePasswordQuality != quality || p.mActivePasswordLength != length
- || p.mFailedPasswordAttempts != 0 || p.mActivePasswordLetters != letters
- || p.mActivePasswordUpperCase != uppercase
- || p.mActivePasswordLowerCase != lowercase
- || p.mActivePasswordNumeric != numbers
- || p.mActivePasswordSymbols != symbols
- || p.mActivePasswordNonLetter != nonletter) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
- p.mActivePasswordQuality = quality;
- p.mActivePasswordLength = length;
- p.mActivePasswordLetters = letters;
- p.mActivePasswordLowerCase = lowercase;
- p.mActivePasswordUpperCase = uppercase;
- p.mActivePasswordNumeric = numbers;
- p.mActivePasswordSymbols = symbols;
- p.mActivePasswordNonLetter = nonletter;
- p.mFailedPasswordAttempts = 0;
- saveSettingsLocked(userHandle);
- updatePasswordExpirationsLocked(userHandle);
- setExpirationAlarmCheckLocked(mContext, p);
- sendAdminCommandToSelfAndProfilesLocked(
- DeviceAdminReceiver.ACTION_PASSWORD_CHANGED,
- DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userHandle);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ DevicePolicyData policy = getUserData(userHandle);
+
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ synchronized (this) {
+ policy.mActivePasswordQuality = quality;
+ policy.mActivePasswordLength = length;
+ policy.mActivePasswordLetters = letters;
+ policy.mActivePasswordLowerCase = lowercase;
+ policy.mActivePasswordUpperCase = uppercase;
+ policy.mActivePasswordNumeric = numbers;
+ policy.mActivePasswordSymbols = symbols;
+ policy.mActivePasswordNonLetter = nonletter;
+ policy.mFailedPasswordAttempts = 0;
+ saveSettingsLocked(userHandle);
+ updatePasswordExpirationsLocked(userHandle);
+ setExpirationAlarmCheckLocked(mContext, policy);
+ sendAdminCommandToSelfAndProfilesLocked(
+ DeviceAdminReceiver.ACTION_PASSWORD_CHANGED,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userHandle);
}
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
}
}
@@ -5567,8 +5553,8 @@
// InputMethodManager fetches input methods for current user.
// So this can only be set when calling user is the current user
// or parent is current user in case of managed profiles.
- InputMethodManager inputMethodManager = (InputMethodManager) mContext
- .getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager inputMethodManager =
+ mContext.getSystemService(InputMethodManager.class);
List<InputMethodInfo> enabledImes = inputMethodManager.getEnabledInputMethodList();
if (enabledImes != null) {
@@ -5646,8 +5632,8 @@
// If we have a permitted list add all system input methods.
if (result != null) {
- InputMethodManager inputMethodManager = (InputMethodManager) mContext
- .getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager inputMethodManager =
+ mContext.getSystemService(InputMethodManager.class);
List<InputMethodInfo> imes = inputMethodManager.getInputMethodList();
long id = mInjector.binderClearCallingIdentity();
try {
@@ -6823,6 +6809,9 @@
public boolean isProvisioningAllowed(String action) {
final int callingUserId = mInjector.userHandleGetCallingUserId();
if (DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE.equals(action)) {
+ if (!hasFeatureManagedUsers()) {
+ return false;
+ }
synchronized (this) {
if (mOwners.hasDeviceOwner()) {
if (!mInjector.userManagerIsSplitSystemUser()) {
@@ -6845,13 +6834,6 @@
// Managed user cannot have a managed profile.
return false;
}
- try {
- if (!mIPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
- return false;
- }
- } catch (RemoteException e) {
- return false;
- }
final long ident = mInjector.binderClearCallingIdentity();
try {
if (!mUserManager.canAddMoreManagedProfiles(callingUserId, true)) {
@@ -6864,10 +6846,17 @@
} else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE.equals(action)) {
return isDeviceOwnerProvisioningAllowed(callingUserId);
} else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_USER.equals(action)) {
+ if (!hasFeatureManagedUsers()) {
+ return false;
+ }
if (!mInjector.userManagerIsSplitSystemUser()) {
// ACTION_PROVISION_MANAGED_USER only supported on split-user systems.
return false;
}
+ if (callingUserId == UserHandle.USER_SYSTEM) {
+ // System user cannot be a managed user.
+ return false;
+ }
if (hasUserSetupCompleted(callingUserId)) {
return false;
}
@@ -6901,6 +6890,14 @@
return true;
}
+ private boolean hasFeatureManagedUsers() {
+ try {
+ return mIPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
@Override
public String getWifiMacAddress() {
// Make sure caller has DO.
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index 0ab1657..77a47f8 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -16,6 +16,9 @@
package com.android.server.print;
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -757,6 +760,33 @@
}
@Override
+ public void setProgress(@NonNull PrintJobId printJobId,
+ @FloatRange(from=0.0, to=1.0) float progress) {
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mSpooler.setProgress(printJobId, progress);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mSpooler.setStatus(printJobId, status);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
@SuppressWarnings({"rawtypes", "unchecked"})
public void onPrintersAdded(ParceledListSlice printers) {
RemotePrintService service = mWeakService.get();
diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java
index e5370f4..c506b6f 100644
--- a/services/print/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java
@@ -16,6 +16,9 @@
package com.android.server.print;
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -229,6 +232,61 @@
return false;
}
+ /**
+ * Set progress of a print job.
+ *
+ * @param printJobId The print job to update
+ * @param progress The new progress
+ */
+ public final void setProgress(@NonNull PrintJobId printJobId,
+ @FloatRange(from=0.0, to=1.0) float progress) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().setProgress(printJobId, progress);
+ } catch (RemoteException|TimeoutException re) {
+ Slog.e(LOG_TAG, "Error setting progress.", re);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setProgress()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Set status of a print job.
+ *
+ * @param printJobId The print job to update
+ * @param status The new status
+ */
+ public final void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().setStatus(printJobId, status);
+ } catch (RemoteException|TimeoutException re) {
+ Slog.e(LOG_TAG, "Error setting status.", re);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setStatus()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
public final boolean setPrintJobTag(PrintJobId printJobId, String tag) {
throwIfCalledOnMainThread();
synchronized (mLock) {
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 97e16da..27deb72 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -591,6 +591,12 @@
public void setUp() throws Exception {
super.setUp();
+ // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
+ // http://b/25897652 .
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
mServiceContext = new MockContext(getContext());
mService = new WrappedConnectivityService(mServiceContext,
mock(INetworkManagementService.class),
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 4115756..17bd08c 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -1805,6 +1805,13 @@
public void onReject(String replyMessage) {}
/**
+ * Notifies the Connection of a request to silence the ringer.
+ *
+ * @hide
+ */
+ public void onSilence() {}
+
+ /**
* Notifies this Connection whether the user wishes to proceed with the post-dial DTMF codes.
*/
public void onPostDialContinue(boolean proceed) {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 6223495..b4a7ce0 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -102,6 +102,7 @@
private static final int MSG_MERGE_CONFERENCE = 18;
private static final int MSG_SWAP_CONFERENCE = 19;
private static final int MSG_REJECT_WITH_MESSAGE = 20;
+ private static final int MSG_SILENCE = 21;
private static Connection sNullConnection;
@@ -177,6 +178,11 @@
}
@Override
+ public void silence(String callId) {
+ mHandler.obtainMessage(MSG_SILENCE, callId).sendToTarget();
+ }
+
+ @Override
public void disconnect(String callId) {
mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
}
@@ -319,6 +325,9 @@
case MSG_DISCONNECT:
disconnect((String) msg.obj);
break;
+ case MSG_SILENCE:
+ silence((String) msg.obj);
+ break;
case MSG_HOLD:
hold((String) msg.obj);
break;
@@ -708,6 +717,11 @@
findConnectionForAction(callId, "reject").onReject(rejectWithMessage);
}
+ private void silence(String callId) {
+ Log.d(this, "silence %s", callId);
+ findConnectionForAction(callId, "silence").onSilence();
+ }
+
private void disconnect(String callId) {
Log.d(this, "disconnect %s", callId);
if (mConnectionById.containsKey(callId)) {
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index dd253cf..8a54add 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -54,6 +54,8 @@
void disconnect(String callId);
+ void silence(String callId);
+
void hold(String callId);
void unhold(String callId);
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 4821678..c967c2b 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -147,6 +147,11 @@
}
@Override
+ public SharedPreferences getSharedPreferences(File file, int mode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public FileInputStream openFileInput(String name) throws FileNotFoundException {
throw new UnsupportedOperationException();
}
diff --git a/tests/NetworkSecurityConfigTest/res/xml/override_dedup.xml b/tests/NetworkSecurityConfigTest/res/xml/override_dedup.xml
new file mode 100644
index 0000000..5ba5675
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/override_dedup.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <!-- Entry with a bad pin. Connections to this will only succeed if overridePins is set. -->
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">aaaaaaaaIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ <trust-anchors>
+ <certificates src="system" overridePins="false" />
+ </trust-anchors>
+ </domain-config>
+ <!-- override that contains all of the system CA store. This should completely override the
+ anchors in the domain config-above with ones that have overridePins set. -->
+ <debug-overrides>
+ <trust-anchors>
+ <certificates src="system" />
+ </trust-anchors>
+ </debug-overrides>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java
index 92eadc0..69b2a9d 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java
@@ -19,15 +19,29 @@
import java.util.Set;
import java.security.cert.X509Certificate;
+import com.android.org.conscrypt.TrustedCertificateIndex;
+
/** @hide */
public class TestCertificateSource implements CertificateSource {
private final Set<X509Certificate> mCertificates;
+ private final TrustedCertificateIndex mIndex = new TrustedCertificateIndex();
public TestCertificateSource(Set<X509Certificate> certificates) {
mCertificates = certificates;
+ for (X509Certificate cert : certificates) {
+ mIndex.index(cert);
+ }
}
public Set<X509Certificate> getCertificates() {
return mCertificates;
}
+
+ public X509Certificate findBySubjectAndPublicKey(X509Certificate cert) {
+ java.security.cert.TrustAnchor anchor = mIndex.findBySubjectAndPublicKey(cert);
+ if (anchor == null) {
+ return null;
+ }
+ return anchor.getTrustedCert();
+ }
}
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index c6f3680..998bb68 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -402,4 +402,22 @@
context.init(null, tms, null);
TestUtils.assertConnectionSucceeds(context, "android.com" , 443);
}
+
+ public void testDebugDedup() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_dedup, true);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ // Check android.com.
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ PinSet pinSet = config.getPins();
+ assertFalse(pinSet.pins.isEmpty());
+ // Check that all TrustAnchors come from the override pins debug source.
+ for (TrustAnchor anchor : config.getTrustAnchors()) {
+ assertTrue(anchor.overridesPins);
+ }
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ }
}
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index cb244ec..641c34b 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -33,7 +33,7 @@
".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
- ".amr", ".awb", ".wma", ".wmv"
+ ".amr", ".awb", ".wma", ".wmv", ".webm"
};
/* fwd decls, so I can write this downward */
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index e22e76d..fb0fe38 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1537,12 +1537,20 @@
std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
while (!workQueue.empty()) {
CompileResourceWorkItem& workItem = workQueue.front();
- err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags);
+ int xmlCompilationFlags = xmlFlags | XML_COMPILE_PARSE_VALUES
+ | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+ if (!workItem.needsCompiling) {
+ xmlCompilationFlags &= ~XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+ xmlCompilationFlags &= ~XML_COMPILE_PARSE_VALUES;
+ }
+ err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot,
+ workItem.file, &table, xmlCompilationFlags);
+
if (err == NO_ERROR) {
assets->addResource(workItem.resPath.getPathLeaf(),
- workItem.resPath,
- workItem.file,
- workItem.file->getResourceType());
+ workItem.resPath,
+ workItem.file,
+ workItem.file->getResourceType());
} else {
hasErrors = true;
}
@@ -1737,9 +1745,7 @@
manifestFile->getGroupEntry(),
manifestFile->getResourceType());
err = compileXmlFile(bundle, assets, String16(), manifestFile,
- outManifestFile, &table,
- XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
- | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
+ outManifestFile, &table, XML_COMPILE_STANDARD_RESOURCE & ~XML_COMPILE_STRIP_COMMENTS);
if (err < NO_ERROR) {
return err;
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index d5a09d8..0e470d9 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -88,8 +88,11 @@
root->setUTF8(true);
}
- bool hasErrors = false;
+ if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) {
+ return UNKNOWN_ERROR;
+ }
+ bool hasErrors = false;
if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
status_t err = root->assignResourceIds(assets, table);
if (err != NO_ERROR) {
@@ -97,9 +100,11 @@
}
}
- status_t err = root->parseValues(assets, table);
- if (err != NO_ERROR) {
- hasErrors = true;
+ if ((options&XML_COMPILE_PARSE_VALUES) != 0) {
+ status_t err = root->parseValues(assets, table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
}
if (hasErrors) {
@@ -114,7 +119,7 @@
printf("Input XML Resource:\n");
root->print();
}
- err = root->flatten(target,
+ status_t err = root->flatten(target,
(options&XML_COMPILE_STRIP_COMMENTS) != 0,
(options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
if (err != NO_ERROR) {
@@ -4755,9 +4760,9 @@
newConfig.sdkVersion = sdkVersionToGenerate;
sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
AaptGroupEntry(newConfig), target->getResourceType());
- String8 resPath = String8::format("res/%s/%s",
+ String8 resPath = String8::format("res/%s/%s.xml",
newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
- target->getSourceFile().getPathLeaf().string());
+ String8(resourceName).string());
resPath.convertToResPath();
// Add a resource table entry.
@@ -4784,9 +4789,11 @@
item.resourceName = resourceName;
item.resPath = resPath;
item.file = newFile;
+ item.xmlRoot = newRoot;
+ item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need
+ // to do it again.
mWorkQueue.push(item);
}
-
return NO_ERROR;
}
@@ -4825,3 +4832,226 @@
}
}
}
+
+static String16 buildNamespace(const String16& package) {
+ return String16("http://schemas.android.com/apk/res/") + package;
+}
+
+static sp<XMLNode> findOnlyChildElement(const sp<XMLNode>& parent) {
+ const Vector<sp<XMLNode> >& children = parent->getChildren();
+ sp<XMLNode> onlyChild;
+ for (size_t i = 0; i < children.size(); i++) {
+ if (children[i]->getType() != XMLNode::TYPE_CDATA) {
+ if (onlyChild != NULL) {
+ return NULL;
+ }
+ onlyChild = children[i];
+ }
+ }
+ return onlyChild;
+}
+
+/**
+ * Detects use of the `bundle' format and extracts nested resources into their own top level
+ * resources. The bundle format looks like this:
+ *
+ * <!-- res/drawable/bundle.xml -->
+ * <animated-vector xmlns:aapt="http://schemas.android.com/aapt">
+ * <aapt:attr name="android:drawable">
+ * <vector android:width="60dp"
+ * android:height="60dp">
+ * <path android:name="v"
+ * android:fillColor="#000000"
+ * android:pathData="M300,70 l 0,-70 70,..." />
+ * </vector>
+ * </aapt:attr>
+ * </animated-vector>
+ *
+ * When AAPT sees the <aapt:attr> tag, it will extract its single element and its children
+ * into a new high-level resource, assigning it a name and ID. Then value of the `name`
+ * attribute must be a resource attribute. That resource attribute is inserted into the parent
+ * with the reference to the extracted resource as the value.
+ *
+ * <!-- res/drawable/bundle.xml -->
+ * <animated-vector android:drawable="@drawable/bundle_1.xml">
+ * </animated-vector>
+ *
+ * <!-- res/drawable/bundle_1.xml -->
+ * <vector android:width="60dp"
+ * android:height="60dp">
+ * <path android:name="v"
+ * android:fillColor="#000000"
+ * android:pathData="M300,70 l 0,-70 70,..." />
+ * </vector>
+ */
+status_t ResourceTable::processBundleFormat(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& target,
+ const sp<XMLNode>& root) {
+ Vector<sp<XMLNode> > namespaces;
+ if (root->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces.push(root);
+ }
+ return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces);
+}
+
+status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& target,
+ const sp<XMLNode>& parent,
+ Vector<sp<XMLNode> >* namespaces) {
+ const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt");
+ const String16 kName16("name");
+ const String16 kAttr16("attr");
+ const String16 kAssetPackage16(mAssets->getPackage());
+
+ Vector<sp<XMLNode> >& children = parent->getChildren();
+ for (size_t i = 0; i < children.size(); i++) {
+ const sp<XMLNode>& child = children[i];
+
+ if (child->getType() == XMLNode::TYPE_CDATA) {
+ continue;
+ } else if (child->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces->push(child);
+ }
+
+ if (child->getElementNamespace() != kAaptNamespaceUri16 ||
+ child->getElementName() != kAttr16) {
+ status_t result = processBundleFormatImpl(bundle, resourceName, target, child,
+ namespaces);
+ if (result != NO_ERROR) {
+ return result;
+ }
+
+ if (child->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces->pop();
+ }
+ continue;
+ }
+
+ // This is the <aapt:attr> tag. Look for the 'name' attribute.
+ SourcePos source(child->getFilename(), child->getStartLineNumber());
+
+ sp<XMLNode> nestedRoot = findOnlyChildElement(child);
+ if (nestedRoot == NULL) {
+ source.error("<%s:%s> must have exactly one child element",
+ String8(child->getElementNamespace()).string(),
+ String8(child->getElementName()).string());
+ return UNKNOWN_ERROR;
+ }
+
+ // Find the special attribute 'parent-attr'. This attribute's value contains
+ // the resource attribute for which this element should be assigned in the parent.
+ const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16);
+ if (attr == NULL) {
+ source.error("inline resource definition must specify an attribute via 'name'");
+ return UNKNOWN_ERROR;
+ }
+
+ // Parse the attribute name.
+ const char* errorMsg = NULL;
+ String16 attrPackage, attrType, attrName;
+ bool result = ResTable::expandResourceRef(attr->string.string(),
+ attr->string.size(),
+ &attrPackage, &attrType, &attrName,
+ &kAttr16, &kAssetPackage16,
+ &errorMsg, NULL);
+ if (!result) {
+ source.error("invalid attribute name for 'name': %s", errorMsg);
+ return UNKNOWN_ERROR;
+ }
+
+ if (attrType != kAttr16) {
+ // The value of the 'name' attribute must be an attribute reference.
+ source.error("value of 'name' must be an attribute reference.");
+ return UNKNOWN_ERROR;
+ }
+
+ // Generate a name for this nested resource and try to add it to the table.
+ // We do this in a loop because the name may be taken, in which case we will
+ // increment a suffix until we succeed.
+ String8 nestedResourceName;
+ String8 nestedResourcePath;
+ int suffix = 1;
+ while (true) {
+ // This child element will be extracted into its own resource file.
+ // Generate a name and path for it from its parent.
+ nestedResourceName = String8::format("%s_%d",
+ String8(resourceName).string(), suffix++);
+ nestedResourcePath = String8::format("res/%s/%s.xml",
+ target->getGroupEntry().toDirName(target->getResourceType())
+ .string(),
+ nestedResourceName.string());
+
+ // Lookup or create the entry for this name.
+ sp<Entry> entry = getEntry(kAssetPackage16,
+ String16(target->getResourceType()),
+ String16(nestedResourceName),
+ source,
+ false,
+ &target->getGroupEntry().toParams(),
+ true);
+ if (entry == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ if (entry->getType() == Entry::TYPE_UNKNOWN) {
+ // The value for this resource has never been set,
+ // meaning we're good!
+ entry->setItem(source, String16(nestedResourcePath));
+ break;
+ }
+
+ // We failed (name already exists), so try with a different name
+ // (increment the suffix).
+ }
+
+ if (bundle->getVerbose()) {
+ source.printf("generating nested resource %s:%s/%s",
+ mAssets->getPackage().string(), target->getResourceType().string(),
+ nestedResourceName.string());
+ }
+
+ // Build the attribute reference and assign it to the parent.
+ String16 nestedResourceRef = String16(String8::format("@%s:%s/%s",
+ mAssets->getPackage().string(), target->getResourceType().string(),
+ nestedResourceName.string()));
+
+ String16 attrNs = buildNamespace(attrPackage);
+ if (parent->getAttribute(attrNs, attrName) != NULL) {
+ SourcePos(parent->getFilename(), parent->getStartLineNumber())
+ .error("parent of nested resource already defines attribute '%s:%s'",
+ String8(attrPackage).string(), String8(attrName).string());
+ return UNKNOWN_ERROR;
+ }
+
+ // Add the reference to the inline resource.
+ parent->addAttribute(attrNs, attrName, nestedResourceRef);
+
+ // Remove the <aapt:attr> child element from here.
+ children.removeAt(i);
+ i--;
+
+ // Append all namespace declarations that we've seen on this branch in the XML tree
+ // to this resource.
+ // We do this because the order of namespace declarations and prefix usage is determined
+ // by the developer and we do not want to override any decisions. Be conservative.
+ for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) {
+ const sp<XMLNode>& ns = namespaces->itemAt(nsIndex - 1);
+ sp<XMLNode> newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(),
+ ns->getNamespaceUri());
+ newNs->addChild(nestedRoot);
+ nestedRoot = newNs;
+ }
+
+ // Schedule compilation of the nested resource.
+ CompileResourceWorkItem workItem;
+ workItem.resPath = nestedResourcePath;
+ workItem.resourceName = String16(nestedResourceName);
+ workItem.xmlRoot = nestedRoot;
+ workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(),
+ target->getResourceType());
+ mWorkQueue.push(workItem);
+ }
+ return NO_ERROR;
+}
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index c4bdf09..4b7b3cd 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -23,13 +23,14 @@
enum {
XML_COMPILE_STRIP_COMMENTS = 1<<0,
XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1,
- XML_COMPILE_COMPACT_WHITESPACE = 1<<2,
- XML_COMPILE_STRIP_WHITESPACE = 1<<3,
- XML_COMPILE_STRIP_RAW_VALUES = 1<<4,
- XML_COMPILE_UTF8 = 1<<5,
+ XML_COMPILE_PARSE_VALUES = 1 << 2,
+ XML_COMPILE_COMPACT_WHITESPACE = 1<<3,
+ XML_COMPILE_STRIP_WHITESPACE = 1<<4,
+ XML_COMPILE_STRIP_RAW_VALUES = 1<<5,
+ XML_COMPILE_UTF8 = 1<<6,
XML_COMPILE_STANDARD_RESOURCE =
- XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+ XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS | XML_COMPILE_PARSE_VALUES
| XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES
};
@@ -83,6 +84,8 @@
String16 resourceName;
String8 resPath;
sp<AaptFile> file;
+ sp<XMLNode> xmlRoot;
+ bool needsCompiling = true;
};
class ResourceTable : public ResTable::Accessor
@@ -206,6 +209,12 @@
const sp<AaptFile>& file,
const sp<XMLNode>& root);
+ status_t processBundleFormat(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& file,
+ const sp<XMLNode>& parent);
+
+
sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
const bool isBase);
@@ -586,6 +595,11 @@
Res_value* outValue);
int getPublicAttributeSdkLevel(uint32_t attrId) const;
+ status_t processBundleFormatImpl(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& file,
+ const sp<XMLNode>& parent,
+ Vector<sp<XMLNode> >* namespaces);
String16 mAssetsPackage;
PackageType mPackageType;
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index dc08eb8..5b215da 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -693,6 +693,12 @@
return mChildren;
}
+
+Vector<sp<XMLNode> >& XMLNode::getChildren()
+{
+ return mChildren;
+}
+
const String8& XMLNode::getFilename() const
{
return mFilename;
@@ -717,6 +723,18 @@
return NULL;
}
+bool XMLNode::removeAttribute(const String16& ns, const String16& name)
+{
+ for (size_t i = 0; i < mAttributes.size(); i++) {
+ const attribute_entry& ae(mAttributes.itemAt(i));
+ if (ae.ns == ns && ae.name == name) {
+ removeAttribute(i);
+ return true;
+ }
+ }
+ return false;
+}
+
XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns,
const String16& name)
{
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
index b9e5cd5..749bf9f 100644
--- a/tools/aapt/XMLNode.h
+++ b/tools/aapt/XMLNode.h
@@ -55,7 +55,7 @@
sp<XMLNode> newCData(const String8& filename) {
return new XMLNode(filename);
}
-
+
enum type {
TYPE_NAMESPACE,
TYPE_ELEMENT,
@@ -70,6 +70,7 @@
const String16& getElementNamespace() const;
const String16& getElementName() const;
const Vector<sp<XMLNode> >& getChildren() const;
+ Vector<sp<XMLNode> >& getChildren();
const String8& getFilename() const;
@@ -97,6 +98,7 @@
const Vector<attribute_entry>& getAttributes() const;
const attribute_entry* getAttribute(const String16& ns, const String16& name) const;
+ bool removeAttribute(const String16& ns, const String16& name);
attribute_entry* editAttribute(const String16& ns, const String16& name);
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 17a658e..90e35d5 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -22,6 +22,7 @@
#include "compile/IdAssigner.h"
#include "compile/Png.h"
#include "compile/XmlIdCollector.h"
+#include "flatten/Archive.h"
#include "flatten/FileExportWriter.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
@@ -31,6 +32,7 @@
#include "xml/XmlDom.h"
#include "xml/XmlPullParser.h"
+#include <dirent.h>
#include <fstream>
#include <string>
@@ -90,7 +92,7 @@
}
return ResourcePathData{
- Source{ path },
+ Source(path),
util::utf8ToUtf16(dirStr),
util::utf8ToUtf16(name),
extension.toString(),
@@ -101,25 +103,79 @@
struct CompileOptions {
std::string outputPath;
+ Maybe<std::string> resDir;
Maybe<std::u16string> product;
bool verbose = false;
};
-static std::string buildIntermediateFilename(const std::string outDir,
- const ResourcePathData& data) {
+static std::string buildIntermediateFilename(const ResourcePathData& data) {
std::stringstream name;
name << data.resourceDir;
if (!data.configStr.empty()) {
name << "-" << data.configStr;
}
name << "_" << data.name << "." << data.extension << ".flat";
- std::string outPath = outDir;
- file::appendPath(&outPath, name.str());
- return outPath;
+ return name.str();
+}
+
+static bool isHidden(const StringPiece& filename) {
+ return util::stringStartsWith<char>(filename, ".");
+}
+
+/**
+ * Walks the res directory structure, looking for resource files.
+ */
+static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
+ std::vector<ResourcePathData>* outPathData) {
+ const std::string& rootDir = options.resDir.value();
+ std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir);
+ if (!d) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* entry = readdir(d.get())) {
+ if (isHidden(entry->d_name)) {
+ continue;
+ }
+
+ std::string prefixPath = rootDir;
+ file::appendPath(&prefixPath, entry->d_name);
+
+ if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
+ continue;
+ }
+
+ std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir);
+ if (!subDir) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* leafEntry = readdir(subDir.get())) {
+ if (isHidden(leafEntry->d_name)) {
+ continue;
+ }
+
+ std::string fullPath = prefixPath;
+ file::appendPath(&fullPath, leafEntry->d_name);
+
+ std::string errStr;
+ Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr);
+ if (!pathData) {
+ context->getDiagnostics()->error(DiagMessage() << errStr);
+ return false;
+ }
+
+ outPathData->push_back(std::move(pathData.value()));
+ }
+ }
+ return true;
}
static bool compileTable(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
ResourceTable table;
{
std::ifstream fin(pathData.source.path, std::ifstream::binary);
@@ -150,6 +206,7 @@
// Ensure we have the compilation package at least.
table.createPackage(context->getCompilationPackage());
+ // Assign an ID to any package that has resources.
for (auto& pkg : table.packages) {
if (!pkg->id) {
// If no package ID was set while parsing (public identifiers), auto assign an ID.
@@ -172,23 +229,24 @@
return false;
}
- // Build the output filename.
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
- // Write it to disk.
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
}
- return true;
+
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
static bool compileXml(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
std::unique_ptr<xml::XmlResource> xmlRes;
@@ -214,7 +272,7 @@
return false;
}
- xmlRes->file.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+ xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
xmlRes->file.config = pathData.config;
xmlRes->file.source = pathData.source;
@@ -230,25 +288,27 @@
fileExportWriter.finish();
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
- // Write it to disk.
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
}
- return true;
+
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
static bool compilePng(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
BigBuffer buffer(4096);
ResourceFile resFile;
- resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+ resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
resFile.config = pathData.config;
resFile.source = pathData.source;
@@ -269,24 +329,27 @@
fileExportWriter.finish();
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
}
- return true;
+
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
static bool compileFile(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
BigBuffer buffer(256);
ResourceFile resFile;
- resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+ resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
resFile.config = pathData.config;
resFile.source = pathData.source;
@@ -299,9 +362,8 @@
return false;
}
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
@@ -309,16 +371,17 @@
// the buffer the entire file.
fileExportWriter.getChunkHeader()->size =
util::hostToDevice32(buffer.size() + f.value().getDataLength());
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+
+ if (writer->writeEntry(buffer)) {
+ if (writer->writeEntry(f.value().getDataPtr(), f.value().getDataLength())) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
- }
- return true;
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
class CompileContext : public IAaptContext {
@@ -359,6 +422,7 @@
Flags flags = Flags()
.requiredFlag("-o", "Output path", &options.outputPath)
.optionalFlag("--product", "Product type to compile", &product)
+ .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
if (!flags.parse("aapt2 compile", args, &std::cerr)) {
return 1;
@@ -369,19 +433,42 @@
}
CompileContext context;
+ std::unique_ptr<IArchiveWriter> archiveWriter;
std::vector<ResourcePathData> inputData;
- inputData.reserve(flags.getArgs().size());
-
- // Collect data from the path for each input file.
- for (const std::string& arg : flags.getArgs()) {
- std::string errorStr;
- if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
- inputData.push_back(std::move(pathData.value()));
- } else {
- context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+ if (options.resDir) {
+ if (!flags.getArgs().empty()) {
+ // Can't have both files and a resource directory.
+ context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified");
+ flags.usage("aapt2 compile", &std::cerr);
return 1;
}
+
+ if (!loadInputFilesFromDir(&context, options, &inputData)) {
+ return 1;
+ }
+
+ archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
+
+ } else {
+ inputData.reserve(flags.getArgs().size());
+
+ // Collect data from the path for each input file.
+ for (const std::string& arg : flags.getArgs()) {
+ std::string errorStr;
+ if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
+ inputData.push_back(std::move(pathData.value()));
+ } else {
+ context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+ return 1;
+ }
+ }
+
+ archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath);
+ }
+
+ if (!archiveWriter) {
+ return false;
}
bool error = false;
@@ -394,32 +481,34 @@
// Overwrite the extension.
pathData.extension = "arsc";
- const std::string outputFilename = buildIntermediateFilename(
- options.outputPath, pathData);
- if (!compileTable(&context, options, pathData, outputFilename)) {
+ const std::string outputFilename = buildIntermediateFilename(pathData);
+ if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) {
error = true;
}
} else {
- const std::string outputFilename = buildIntermediateFilename(options.outputPath,
- pathData);
+ const std::string outputFilename = buildIntermediateFilename(pathData);
if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
if (*type != ResourceType::kRaw) {
if (pathData.extension == "xml") {
- if (!compileXml(&context, options, pathData, outputFilename)) {
+ if (!compileXml(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
} else if (pathData.extension == "png" || pathData.extension == "9.png") {
- if (!compilePng(&context, options, pathData, outputFilename)) {
+ if (!compilePng(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
} else {
- if (!compileFile(&context, options, pathData, outputFilename)) {
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
}
} else {
- if (!compileFile(&context, options, pathData, outputFilename)) {
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
}
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
index 6db13b8..3a244c0 100644
--- a/tools/aapt2/flatten/Archive.cpp
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -18,7 +18,7 @@
#include "util/Files.h"
#include "util/StringPiece.h"
-#include <fstream>
+#include <cstdio>
#include <memory>
#include <string>
#include <vector>
@@ -30,70 +30,85 @@
struct DirectoryWriter : public IArchiveWriter {
std::string mOutDir;
- std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
+ std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
- explicit DirectoryWriter(const StringPiece& outDir) : mOutDir(outDir.toString()) {
+ bool open(IDiagnostics* diag, const StringPiece& outDir) {
+ mOutDir = outDir.toString();
+ file::FileType type = file::getFileType(mOutDir);
+ if (type == file::FileType::kNonexistant) {
+ diag->error(DiagMessage() << "directory " << mOutDir << " does not exist");
+ return false;
+ } else if (type != file::FileType::kDirectory) {
+ diag->error(DiagMessage() << mOutDir << " is not a directory");
+ return false;
+ }
+ return true;
}
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- const BigBuffer& buffer) override {
+ bool startEntry(const StringPiece& path, uint32_t flags) override {
+ if (mFile) {
+ return false;
+ }
+
std::string fullPath = mOutDir;
file::appendPath(&fullPath, path);
file::mkdirs(file::getStem(fullPath));
- std::ofstream fout(fullPath, std::ofstream::binary);
- if (!fout) {
- return nullptr;
+ mFile = { fopen(fullPath.data(), "wb"), fclose };
+ if (!mFile) {
+ return false;
}
-
- if (!util::writeAll(fout, buffer)) {
- return nullptr;
- }
-
- mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, buffer.size()));
- return mEntries.back().get();
+ return true;
}
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
- size_t offset, size_t len) override {
- std::string fullPath = mOutDir;
- file::appendPath(&fullPath, path);
- file::mkdirs(file::getStem(fullPath));
-
- std::ofstream fout(fullPath, std::ofstream::binary);
- if (!fout) {
- return nullptr;
+ bool writeEntry(const BigBuffer& buffer) override {
+ if (!mFile) {
+ return false;
}
- if (!fout.write((const char*) fileMap->getDataPtr() + offset, len)) {
- return nullptr;
+ for (const BigBuffer::Block& b : buffer) {
+ if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) {
+ mFile.reset(nullptr);
+ return false;
+ }
}
-
- mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, len));
- return mEntries.back().get();
+ return true;
}
- virtual ~DirectoryWriter() {
+ bool writeEntry(const void* data, size_t len) override {
+ if (fwrite(data, 1, len, mFile.get()) != len) {
+ mFile.reset(nullptr);
+ return false;
+ }
+ return true;
+ }
+ bool finishEntry() override {
+ if (!mFile) {
+ return false;
+ }
+ mFile.reset(nullptr);
+ return true;
}
};
struct ZipFileWriter : public IArchiveWriter {
- FILE* mFile;
+ std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
std::unique_ptr<ZipWriter> mWriter;
- std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
- explicit ZipFileWriter(const StringPiece& path) {
- mFile = fopen(path.data(), "w+b");
- if (mFile) {
- mWriter = util::make_unique<ZipWriter>(mFile);
+ bool open(IDiagnostics* diag, const StringPiece& path) {
+ mFile = { fopen(path.data(), "w+b"), fclose };
+ if (!mFile) {
+ diag->error(DiagMessage() << "failed to open " << path << ": " << strerror(errno));
+ return false;
}
+ mWriter = util::make_unique<ZipWriter>(mFile.get());
+ return true;
}
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- const BigBuffer& buffer) override {
+ bool startEntry(const StringPiece& path, uint32_t flags) override {
if (!mWriter) {
- return nullptr;
+ return false;
}
size_t zipFlags = 0;
@@ -107,75 +122,63 @@
int32_t result = mWriter->StartEntry(path.data(), zipFlags);
if (result != 0) {
- return nullptr;
+ return false;
}
+ return true;
+ }
+ bool writeEntry(const void* data, size_t len) override {
+ int32_t result = mWriter->WriteBytes(data, len);
+ if (result != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ bool writeEntry(const BigBuffer& buffer) override {
for (const BigBuffer::Block& b : buffer) {
- result = mWriter->WriteBytes(reinterpret_cast<const uint8_t*>(b.buffer.get()), b.size);
+ int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size);
if (result != 0) {
- return nullptr;
+ return false;
}
}
-
- result = mWriter->FinishEntry();
- if (result != 0) {
- return nullptr;
- }
-
- mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, buffer.size()));
- return mEntries.back().get();
+ return true;
}
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
- size_t offset, size_t len) override {
- if (!mWriter) {
- return nullptr;
- }
-
- size_t zipFlags = 0;
- if (flags & ArchiveEntry::kCompress) {
- zipFlags |= ZipWriter::kCompress;
- }
-
- if (flags & ArchiveEntry::kAlign) {
- zipFlags |= ZipWriter::kAlign32;
- }
-
- int32_t result = mWriter->StartEntry(path.data(), zipFlags);
+ bool finishEntry() override {
+ int32_t result = mWriter->FinishEntry();
if (result != 0) {
- return nullptr;
+ return false;
}
-
- result = mWriter->WriteBytes((const char*) fileMap->getDataPtr() + offset, len);
- if (result != 0) {
- return nullptr;
- }
-
- result = mWriter->FinishEntry();
- if (result != 0) {
- return nullptr;
- }
-
- mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, len));
- return mEntries.back().get();
+ return true;
}
virtual ~ZipFileWriter() {
if (mWriter) {
mWriter->Finish();
- fclose(mFile);
}
}
};
} // namespace
-std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path) {
- return util::make_unique<DirectoryWriter>(path);
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
+
+ std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
+ if (!writer->open(diag, path)) {
+ return {};
+ }
+ return std::move(writer);
}
-std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path) {
- return util::make_unique<ZipFileWriter>(path);
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
+ std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
+ if (!writer->open(diag, path)) {
+ return {};
+ }
+ return std::move(writer);
}
} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index c4ddeb3..6da1d2a 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -17,6 +17,7 @@
#ifndef AAPT_FLATTEN_ARCHIVE_H
#define AAPT_FLATTEN_ARCHIVE_H
+#include "Diagnostics.h"
#include "util/BigBuffer.h"
#include "util/Files.h"
#include "util/StringPiece.h"
@@ -42,15 +43,17 @@
struct IArchiveWriter {
virtual ~IArchiveWriter() = default;
- virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- const BigBuffer& buffer) = 0;
- virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- android::FileMap* fileMap, size_t offset, size_t len) = 0;
+ virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
+ virtual bool writeEntry(const BigBuffer& buffer) = 0;
+ virtual bool writeEntry(const void* data, size_t len) = 0;
+ virtual bool finishEntry() = 0;
};
-std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path);
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path);
-std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path);
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path);
} // namespace aapt
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
new file mode 100644
index 0000000..9081c55
--- /dev/null
+++ b/tools/aapt2/io/Data.h
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#ifndef AAPT_IO_DATA_H
+#define AAPT_IO_DATA_H
+
+#include <utils/FileMap.h>
+
+#include <memory>
+
+namespace aapt {
+namespace io {
+
+/**
+ * Interface for a block of contiguous memory. An instance of this interface owns the data.
+ */
+class IData {
+public:
+ virtual ~IData() = default;
+
+ virtual const void* data() const = 0;
+ virtual size_t size() const = 0;
+};
+
+/**
+ * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this
+ * object.
+ */
+class MmappedData : public IData {
+public:
+ explicit MmappedData(android::FileMap&& map) : mMap(std::forward<android::FileMap>(map)) {
+ }
+
+ const void* data() const override {
+ return mMap.getDataPtr();
+ }
+
+ size_t size() const override {
+ return mMap.getDataLength();
+ }
+
+private:
+ android::FileMap mMap;
+};
+
+/**
+ * Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). The
+ * memory is owned by this object.
+ */
+class MallocData : public IData {
+public:
+ MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) :
+ mData(std::move(data)), mSize(size) {
+ }
+
+ const void* data() const override {
+ return mData.get();
+ }
+
+ size_t size() const override {
+ return mSize;
+ }
+
+private:
+ std::unique_ptr<const uint8_t[]> mData;
+ size_t mSize;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_DATA_H */
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
new file mode 100644
index 0000000..9fca398
--- /dev/null
+++ b/tools/aapt2/io/File.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#ifndef AAPT_IO_FILE_H
+#define AAPT_IO_FILE_H
+
+#include "Source.h"
+#include "io/Data.h"
+
+#include <memory>
+#include <vector>
+
+namespace aapt {
+namespace io {
+
+/**
+ * Interface for a file, which could be a real file on the file system, or a file inside
+ * a ZIP archive.
+ */
+class IFile {
+public:
+ virtual ~IFile() = default;
+
+ /**
+ * Open the file and return it as a block of contiguous memory. How this occurs is
+ * implementation dependent. For example, if this is a file on the file system, it may
+ * simply mmap the contents. If this file represents a compressed file in a ZIP archive,
+ * it may need to inflate it to memory, incurring a copy.
+ *
+ * Returns nullptr on failure.
+ */
+ virtual std::unique_ptr<IData> openAsData() = 0;
+
+ /**
+ * Returns the source of this file. This is for presentation to the user and may not be a
+ * valid file system path (for example, it may contain a '@' sign to separate the files within
+ * a ZIP archive from the path to the containing ZIP archive.
+ */
+ virtual const Source& getSource() const = 0;
+};
+
+/**
+ * Interface for a collection of files, all of which share a common source. That source may
+ * simply be the filesystem, or a ZIP archive.
+ */
+class IFileCollection {
+public:
+ virtual ~IFileCollection() = default;
+
+ using const_iterator = std::vector<std::unique_ptr<IFile>>::const_iterator;
+
+ virtual const_iterator begin() const = 0;
+ virtual const_iterator end() const = 0;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_FILE_H */
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
new file mode 100644
index 0000000..5dbefcc
--- /dev/null
+++ b/tools/aapt2/io/FileSystem.h
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#ifndef AAPT_IO_FILESYSTEM_H
+#define AAPT_IO_FILESYSTEM_H
+
+#include "io/File.h"
+#include "util/Files.h"
+
+namespace aapt {
+namespace io {
+
+/**
+ * A regular file from the file system. Uses mmap to open the data.
+ */
+class RegularFile : public IFile {
+public:
+ RegularFile(const Source& source) : mSource(source) {
+ }
+
+ std::unique_ptr<IData> openAsData() override {
+ android::FileMap map;
+ if (Maybe<android::FileMap> map = file::mmapPath(mSource.path, nullptr)) {
+ return util::make_unique<MmappedData>(std::move(map.value()));
+ }
+ return {};
+ }
+
+ const Source& getSource() const override {
+ return mSource;
+ }
+
+private:
+ Source mSource;
+};
+
+/**
+ * An IFileCollection representing the file system.
+ */
+class FileCollection : public IFileCollection {
+public:
+ /**
+ * Adds a file located at path. Returns the IFile representation of that file.
+ */
+ IFile* insertFile(const StringPiece& path) {
+ mFiles.push_back(util::make_unique<RegularFile>(Source(path)));
+ return mFiles.back().get();
+ }
+
+ const_iterator begin() const override {
+ return mFiles.begin();
+ }
+
+ const_iterator end() const override {
+ return mFiles.end();
+ }
+
+private:
+ std::vector<std::unique_ptr<IFile>> mFiles;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_FILESYSTEM_H
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
new file mode 100644
index 0000000..98afc49
--- /dev/null
+++ b/tools/aapt2/io/ZipArchive.h
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+
+#ifndef AAPT_IO_ZIPARCHIVE_H
+#define AAPT_IO_ZIPARCHIVE_H
+
+#include "io/File.h"
+#include "util/StringPiece.h"
+
+#include <utils/FileMap.h>
+#include <ziparchive/zip_archive.h>
+
+namespace aapt {
+namespace io {
+
+/**
+ * An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed
+ * and copied into memory when opened. Otherwise it is mmapped from the ZIP archive.
+ */
+class ZipFile : public IFile {
+public:
+ ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) :
+ mZipHandle(handle), mZipEntry(entry), mSource(source) {
+ }
+
+ std::unique_ptr<IData> openAsData() override {
+ if (mZipEntry.method == kCompressStored) {
+ int fd = GetFileDescriptor(mZipHandle);
+
+ android::FileMap fileMap;
+ bool result = fileMap.create(nullptr, fd, mZipEntry.offset,
+ mZipEntry.uncompressed_length, true);
+ if (!result) {
+ return {};
+ }
+ return util::make_unique<MmappedData>(std::move(fileMap));
+
+ } else {
+ std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(
+ new uint8_t[mZipEntry.uncompressed_length]);
+ int32_t result = ExtractToMemory(mZipHandle, &mZipEntry, data.get(),
+ static_cast<uint32_t>(mZipEntry.uncompressed_length));
+ if (result != 0) {
+ return {};
+ }
+ return util::make_unique<MallocData>(std::move(data), mZipEntry.uncompressed_length);
+ }
+ }
+
+ const Source& getSource() const override {
+ return mSource;
+ }
+
+private:
+ ZipArchiveHandle mZipHandle;
+ ZipEntry mZipEntry;
+ Source mSource;
+};
+
+/**
+ * An IFileCollection that represents a ZIP archive and the entries within it.
+ */
+class ZipFileCollection : public IFileCollection {
+public:
+ static std::unique_ptr<ZipFileCollection> create(const StringPiece& path,
+ std::string* outError) {
+ std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>(
+ new ZipFileCollection());
+
+ int32_t result = OpenArchive(path.data(), &collection->mHandle);
+ if (result != 0) {
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+
+ ZipString suffix(".flat");
+ void* cookie = nullptr;
+ result = StartIteration(collection->mHandle, &cookie, nullptr, &suffix);
+ if (result != 0) {
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+
+ using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
+ IterationEnder iterationEnder(cookie, EndIteration);
+
+ ZipString zipEntryName;
+ ZipEntry zipData;
+ while ((result = Next(cookie, &zipData, &zipEntryName)) == 0) {
+ std::string nestedPath = path.toString();
+ nestedPath += "@" + std::string(reinterpret_cast<const char*>(zipEntryName.name),
+ zipEntryName.name_length);
+ collection->mFiles.push_back(util::make_unique<ZipFile>(collection->mHandle,
+ zipData,
+ Source(nestedPath)));
+ }
+
+ if (result != -1) {
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+ return collection;
+ }
+
+ const_iterator begin() const override {
+ return mFiles.begin();
+ }
+
+ const_iterator end() const override {
+ return mFiles.end();
+ }
+
+ ~ZipFileCollection() override {
+ if (mHandle) {
+ CloseArchive(mHandle);
+ }
+ }
+
+private:
+ ZipFileCollection() : mHandle(nullptr) {
+ }
+
+ ZipArchiveHandle mHandle;
+ std::vector<std::unique_ptr<IFile>> mFiles;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_ZIPARCHIVE_H */
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 9850ae5..33d9272 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -22,6 +22,8 @@
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
+#include "io/FileSystem.h"
+#include "io/ZipArchive.h"
#include "java/JavaClassGenerator.h"
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
@@ -39,7 +41,6 @@
#include <fstream>
#include <sys/stat.h>
-#include <utils/FileMap.h>
#include <vector>
namespace aapt {
@@ -92,7 +93,15 @@
class LinkCommand {
public:
LinkCommand(const LinkOptions& options) :
- mOptions(options), mContext(), mFinalTable() {
+ mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) {
+ std::unique_ptr<io::FileCollection> fileCollection =
+ util::make_unique<io::FileCollection>();
+
+ // Get a pointer to the FileCollection for convenience, but it will be owned by the vector.
+ mFileCollection = fileCollection.get();
+
+ // Move it to the collection.
+ mCollections.push_back(std::move(fileCollection));
}
std::string buildResourceFileName(const ResourceFile& resFile) {
@@ -136,20 +145,9 @@
return builder.build();
}
- /**
- * Loads the resource table (not inside an apk) at the given path.
- */
- std::unique_ptr<ResourceTable> loadTable(const std::string& input) {
- std::string errorStr;
- Maybe<android::FileMap> map = file::mmapPath(input, &errorStr);
- if (!map) {
- mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
- return {};
- }
-
+ std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(&mContext, table.get(), Source(input),
- map.value().getDataPtr(), map.value().getDataLength());
+ BinaryResourceParser parser(&mContext, table.get(), source, data, len);
if (!parser.parse()) {
return {};
}
@@ -159,90 +157,79 @@
/**
* Inflates an XML file from the source path.
*/
- std::unique_ptr<xml::XmlResource> loadXml(const std::string& path) {
+ static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
std::ifstream fin(path, std::ifstream::binary);
if (!fin) {
- mContext.getDiagnostics()->error(DiagMessage(path) << strerror(errno));
+ diag->error(DiagMessage(path) << strerror(errno));
return {};
}
- return xml::inflate(&fin, mContext.getDiagnostics(), Source(path));
+ return xml::inflate(&fin, diag, Source(path));
}
- /**
- * Inflates a binary XML file from the source path.
- */
- std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
- // Read header for symbol info and export info.
+ static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(
+ const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
std::string errorStr;
- Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
- if (!maybeF) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
- return {};
- }
-
- ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
- maybeF.value().getDataLength(), &errorStr);
+ ssize_t offset = getWrappedDataOffset(data, len, &errorStr);
if (offset < 0) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+ diag->error(DiagMessage(source) << errorStr);
return {};
}
std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
- (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
- maybeF.value().getDataLength() - offset,
- mContext.getDiagnostics(), Source(path));
+ reinterpret_cast<const uint8_t*>(data) + static_cast<size_t>(offset),
+ len - static_cast<size_t>(offset),
+ diag,
+ source);
if (!xmlRes) {
return {};
}
return xmlRes;
}
- Maybe<ResourceFile> loadFileExportHeader(const std::string& path) {
- // Read header for symbol info and export info.
+ static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
+ std::unique_ptr<ResourceFile> resFile = util::make_unique<ResourceFile>();
std::string errorStr;
- Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
- if (!maybeF) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
- return {};
- }
-
- ResourceFile resFile;
- ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(),
- maybeF.value().getDataLength(),
- &resFile, &errorStr);
+ ssize_t offset = unwrapFileExportHeader(data, len, resFile.get(), &errorStr);
if (offset < 0) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+ diag->error(DiagMessage(source) << errorStr);
return {};
}
- return std::move(resFile);
+ return resFile;
}
- bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags,
+ bool copyFileToArchive(io::IFile* file, const std::string& outPath, uint32_t flags,
IArchiveWriter* writer) {
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
+ return false;
+ }
+
std::string errorStr;
- Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
- if (!maybeF) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
- return false;
- }
-
- ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
- maybeF.value().getDataLength(),
- &errorStr);
+ ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr);
if (offset < 0) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+ mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
return false;
}
- ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(),
- offset, maybeF.value().getDataLength() - offset);
- if (!entry) {
- mContext.getDiagnostics()->error(
- DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
- return false;
+ if (writer->startEntry(outPath, flags)) {
+ if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset,
+ data->size() - static_cast<size_t>(offset))) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- return true;
+
+ mContext.getDiagnostics()->error(
+ DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
+ return false;
}
Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
@@ -285,9 +272,9 @@
std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
if (mOptions.outputToDirectory) {
- return createDirectoryArchiveWriter(mOptions.outputPath);
+ return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
} else {
- return createZipFileArchiveWriter(mOptions.outputPath);
+ return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
}
}
@@ -300,13 +287,17 @@
return false;
}
- ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer);
- if (!entry) {
- mContext.getDiagnostics()->error(
- DiagMessage() << "failed to write resources.arsc to archive");
- return false;
+ if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- return true;
+
+ mContext.getDiagnostics()->error(
+ DiagMessage() << "failed to write resources.arsc to archive");
+ return false;
}
bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
@@ -320,13 +311,17 @@
return false;
}
- ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer);
- if (!entry) {
- mContext.getDiagnostics()->error(
- DiagMessage() << "failed to write " << path << " to archive");
- return false;
+
+ if (writer->startEntry(path, ArchiveEntry::kCompress)) {
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- return true;
+ mContext.getDiagnostics()->error(
+ DiagMessage() << "failed to write " << path << " to archive");
+ return false;
}
bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
@@ -412,34 +407,44 @@
return true;
}
- bool mergeResourceTable(const std::string& input, bool override) {
+ bool mergeResourceTable(io::IFile* file, bool override) {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
+ mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
}
- std::unique_ptr<ResourceTable> table = loadTable(input);
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
+ return false;
+ }
+
+ std::unique_ptr<ResourceTable> table = loadTable(file->getSource(), data->data(),
+ data->size());
if (!table) {
return false;
}
- if (!mTableMerger->merge(Source(input), table.get(), override)) {
+ if (!mTableMerger->merge(file->getSource(), table.get(), override)) {
return false;
}
return true;
}
- bool mergeCompiledFile(const std::string& input, ResourceFile&& file, bool override) {
- if (file.name.package.empty()) {
- file.name.package = mContext.getCompilationPackage().toString();
+ bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool override) {
+ // Apply the package name used for this compilation phase if none was specified.
+ if (fileDesc->name.package.empty()) {
+ fileDesc->name.package = mContext.getCompilationPackage().toString();
}
- ResourceNameRef resName = file.name;
-
- Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(file.name);
+ // Mangle the name if necessary.
+ ResourceNameRef resName = fileDesc->name;
+ Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(fileDesc->name);
if (mangledName) {
resName = mangledName.value();
}
+ // If we are overriding resources, we supply a custom resolver function.
std::function<int(Value*,Value*)> resolver;
if (override) {
resolver = [](Value* a, Value* b) -> int {
@@ -456,14 +461,14 @@
}
// Add this file to the table.
- if (!mFinalTable.addFileReference(resName, file.config, file.source,
- util::utf8ToUtf16(buildResourceFileName(file)),
+ if (!mFinalTable.addFileReference(resName, fileDesc->config, fileDesc->source,
+ util::utf8ToUtf16(buildResourceFileName(*fileDesc)),
resolver, mContext.getDiagnostics())) {
return false;
}
// Add the exports of this file to the table.
- for (SourcedResourceName& exportedSymbol : file.exportedSymbols) {
+ for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
if (exportedSymbol.name.package.empty()) {
exportedSymbol.name.package = mContext.getCompilationPackage().toString();
}
@@ -477,32 +482,78 @@
}
std::unique_ptr<Id> id = util::make_unique<Id>();
- id->setSource(file.source.withLine(exportedSymbol.line));
+ id->setSource(fileDesc->source.withLine(exportedSymbol.line));
bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
- mContext.getDiagnostics());
+ mContext.getDiagnostics());
if (!result) {
return false;
}
}
- mFilesToProcess.insert(FileToProcess{ std::move(file), Source(input) });
+ // Now add this file for later processing. Once the table is assigned IDs, we can compile
+ // this file.
+ mFilesToProcess.insert(FileToProcess{ std::move(fileDesc), file });
return true;
}
- bool processFile(const std::string& input, bool override) {
- if (util::stringEndsWith<char>(input, ".apk")) {
- return mergeStaticLibrary(input);
- } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
- return mergeResourceTable(input, override);
- } else if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
- return mergeCompiledFile(input, std::move(maybeF.value()), override);
+ /**
+ * Creates an io::IFileCollection from the ZIP archive and processes the files within.
+ */
+ bool mergeArchive(const std::string& input, bool override) {
+ std::string errorStr;
+ std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
+ input, &errorStr);
+ if (!collection) {
+ mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
+ return false;
+ }
+
+ bool error = false;
+ for (const std::unique_ptr<io::IFile>& file : *collection) {
+ if (!processFile(file.get(), override)) {
+ error = true;
+ }
+ }
+
+ // Make sure to move the collection into the set of IFileCollections.
+ mCollections.push_back(std::move(collection));
+ return !error;
+ }
+
+ bool processFile(const std::string& path, bool override) {
+ if (util::stringEndsWith<char>(path, ".flata")) {
+ return mergeArchive(path, override);
+ }
+
+ io::IFile* file = mFileCollection->insertFile(path);
+ return processFile(file, override);
+ }
+
+ bool processFile(io::IFile* file, bool override) {
+ const Source& src = file->getSource();
+ if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
+ return mergeResourceTable(file, override);
+ } else {
+ // Try opening the file and looking for an Export header.
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open");
+ return false;
+ }
+
+ std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
+ src, data->data(), data->size(), mContext.getDiagnostics());
+ if (resourceFile) {
+ return mergeCompiledFile(file, std::move(resourceFile), override);
+ }
}
return false;
}
int run(const std::vector<std::string>& inputFiles) {
// Load the AndroidManifest.xml
- std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath);
+ std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
+ mContext.getDiagnostics());
if (!manifestXml) {
return 1;
}
@@ -648,20 +699,30 @@
}
for (const FileToProcess& file : mFilesToProcess) {
- if (file.file.name.type != ResourceType::kRaw &&
- util::stringEndsWith<char>(file.source.path, ".xml.flat")) {
+ const StringPiece path = file.file->getSource().path;
+
+ if (file.fileExport->name.type != ResourceType::kRaw &&
+ util::stringEndsWith<char>(path, ".xml.flat")) {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage()
- << "linking " << file.source.path);
+ mContext.getDiagnostics()->note(DiagMessage() << "linking " << path);
+ }
+
+ std::unique_ptr<io::IData> data = file.file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(file.file->getSource())
+ << "failed to open file");
+ return 1;
}
std::unique_ptr<xml::XmlResource> xmlRes = loadBinaryXmlSkipFileExport(
- file.source.path);
+ file.file->getSource(), data->data(), data->size(),
+ mContext.getDiagnostics());
if (!xmlRes) {
return 1;
}
- xmlRes->file = std::move(file.file);
+ // Move the file description over.
+ xmlRes->file = std::move(*file.fileExport);
XmlReferenceLinker xmlLinker;
if (xmlLinker.consume(&mContext, xmlRes.get())) {
@@ -689,12 +750,13 @@
xmlRes->file.config,
sdkLevel)) {
xmlRes->file.config.sdkVersion = sdkLevel;
- if (!mFinalTable.addFileReference(xmlRes->file.name,
- xmlRes->file.config,
- xmlRes->file.source,
- util::utf8ToUtf16(
- buildResourceFileName(xmlRes->file)),
- mContext.getDiagnostics())) {
+ bool added = mFinalTable.addFileReference(
+ xmlRes->file.name,
+ xmlRes->file.config,
+ xmlRes->file.source,
+ util::utf8ToUtf16(buildResourceFileName(xmlRes->file)),
+ mContext.getDiagnostics());
+ if (!added) {
error = true;
continue;
}
@@ -712,11 +774,10 @@
}
} else {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "copying "
- << file.source.path);
+ mContext.getDiagnostics()->note(DiagMessage() << "copying " << path);
}
- if (!copyFileToArchive(file.source.path, buildResourceFileName(file.file), 0,
+ if (!copyFileToArchive(file.file, buildResourceFileName(*file.fileExport), 0,
archiveWriter.get())) {
error = true;
}
@@ -802,14 +863,18 @@
ResourceTable mFinalTable;
std::unique_ptr<TableMerger> mTableMerger;
+ io::FileCollection* mFileCollection;
+ std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
+
struct FileToProcess {
- ResourceFile file;
- Source source;
+ std::unique_ptr<ResourceFile> fileExport;
+ io::IFile* file;
};
struct FileToProcessComparator {
bool operator()(const FileToProcess& a, const FileToProcess& b) {
- return std::tie(a.file.name, a.file.config) < std::tie(b.file.name, b.file.config);
+ return std::tie(a.fileExport->name, a.fileExport->config) <
+ std::tie(b.fileExport->name, b.fileExport->config);
}
};
diff --git a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java b/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java
deleted file mode 100644
index 78aedc5..0000000
--- a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.animation;
-
-/**
- * A fake implementation of Animator which doesn't do anything.
- */
-public class FakeAnimator extends Animator {
- @Override
- public long getStartDelay() {
- return 0;
- }
-
- @Override
- public void setStartDelay(long startDelay) {
-
- }
-
- @Override
- public Animator setDuration(long duration) {
- return this;
- }
-
- @Override
- public long getDuration() {
- return 0;
- }
-
- @Override
- public void setInterpolator(TimeInterpolator value) {
-
- }
-
- @Override
- public boolean isRunning() {
- return false;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
index 4603b63..54021c9 100644
--- a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
@@ -16,9 +16,16 @@
package android.animation;
+import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Delegate implementing the native methods of android.animation.PropertyValuesHolder
*
@@ -29,81 +36,161 @@
* around to map int to instance of the delegate.
*
* The main goal of this class' methods are to provide a native way to access setters and getters
- * on some object. In this case we want to default to using Java reflection instead so the native
- * methods do nothing.
+ * on some object. We override these methods to use reflection since the original reflection
+ * implementation of the PropertyValuesHolder won't be able to access protected methods.
*
*/
-/*package*/ class PropertyValuesHolder_Delegate {
+/*package*/
+@SuppressWarnings("unused")
+class PropertyValuesHolder_Delegate {
+ // This code is copied from android.animation.PropertyValuesHolder and must be kept in sync
+ // We try several different types when searching for appropriate setter/getter functions.
+ // The caller may have supplied values in a type that does not match the setter/getter
+ // functions (such as the integers 0 and 1 to represent floating point values for alpha).
+ // Also, the use of generics in constructors means that we end up with the Object versions
+ // of primitive types (Float vs. float). But most likely, the setter/getter functions
+ // will take primitive types instead.
+ // So we supply an ordered array of other types to try before giving up.
+ private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
+ Double.class, Integer.class};
+ private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
+ Float.class, Double.class};
+
+ private static final Object sMethodIndexLock = new Object();
+ private static final Map<Long, Method> ID_TO_METHOD = new HashMap<Long, Method>();
+ private static final Map<String, Long> METHOD_NAME_TO_ID = new HashMap<String, Long>();
+ private static long sNextId = 1;
+
+ private static long registerMethod(Class<?> targetClass, String methodName, Class[] types,
+ int nArgs) {
+ // Encode the number of arguments in the method name
+ String methodIndexName = String.format("%1$s#%2$d", methodName, nArgs);
+ synchronized (sMethodIndexLock) {
+ Long methodId = METHOD_NAME_TO_ID.get(methodIndexName);
+
+ if (methodId != null) {
+ // The method was already registered
+ return methodId;
+ }
+
+ Class[] args = new Class[nArgs];
+ Method method = null;
+ for (Class typeVariant : types) {
+ for (int i = 0; i < nArgs; i++) {
+ args[i] = typeVariant;
+ }
+ try {
+ method = targetClass.getDeclaredMethod(methodName, args);
+ } catch (NoSuchMethodException ignore) {
+ }
+ }
+
+ if (method != null) {
+ methodId = sNextId++;
+ ID_TO_METHOD.put(methodId, method);
+ METHOD_NAME_TO_ID.put(methodIndexName, methodId);
+
+ return methodId;
+ }
+ }
+
+ // Method not found
+ return 0;
+ }
+
+ private static void callMethod(Object target, long methodID, Object... args) {
+ Method method = ID_TO_METHOD.get(methodID);
+ assert method != null;
+
+ try {
+ method.setAccessible(true);
+ method.invoke(target, args);
+ } catch (IllegalAccessException e) {
+ Bridge.getLog().error(null, "Unable to update property during animation", e, null);
+ } catch (InvocationTargetException e) {
+ Bridge.getLog().error(null, "Unable to update property during animation", e, null);
+ }
+ }
@LayoutlibDelegate
/*package*/ static long nGetIntMethod(Class<?> targetClass, String methodName) {
- // return 0 to force PropertyValuesHolder to use Java reflection.
- return 0;
+ return nGetMultipleIntMethod(targetClass, methodName, 1);
}
@LayoutlibDelegate
/*package*/ static long nGetFloatMethod(Class<?> targetClass, String methodName) {
- // return 0 to force PropertyValuesHolder to use Java reflection.
- return 0;
+ return nGetMultipleFloatMethod(targetClass, methodName, 1);
}
@LayoutlibDelegate
/*package*/ static long nGetMultipleIntMethod(Class<?> targetClass, String methodName,
int numParams) {
- // TODO: return the right thing.
- return 0;
+ return registerMethod(targetClass, methodName, INTEGER_VARIANTS, numParams);
}
@LayoutlibDelegate
/*package*/ static long nGetMultipleFloatMethod(Class<?> targetClass, String methodName,
int numParams) {
- // TODO: return the right thing.
- return 0;
+ return registerMethod(targetClass, methodName, FLOAT_VARIANTS, numParams);
}
@LayoutlibDelegate
/*package*/ static void nCallIntMethod(Object target, long methodID, int arg) {
- // do nothing
+ callMethod(target, methodID, arg);
}
@LayoutlibDelegate
/*package*/ static void nCallFloatMethod(Object target, long methodID, float arg) {
- // do nothing
+ callMethod(target, methodID, arg);
}
@LayoutlibDelegate
/*package*/ static void nCallTwoIntMethod(Object target, long methodID, int arg1,
int arg2) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2);
}
@LayoutlibDelegate
/*package*/ static void nCallFourIntMethod(Object target, long methodID, int arg1,
int arg2, int arg3, int arg4) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2, arg3, arg4);
}
@LayoutlibDelegate
/*package*/ static void nCallMultipleIntMethod(Object target, long methodID,
int[] args) {
- // do nothing
+ assert args != null;
+
+ // Box parameters
+ Object[] params = new Object[args.length];
+ for (int i = 0; i < args.length; i++) {
+ params[i] = args;
+ }
+ callMethod(target, methodID, params);
}
@LayoutlibDelegate
/*package*/ static void nCallTwoFloatMethod(Object target, long methodID, float arg1,
float arg2) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2);
}
@LayoutlibDelegate
/*package*/ static void nCallFourFloatMethod(Object target, long methodID, float arg1,
float arg2, float arg3, float arg4) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2, arg3, arg4);
}
@LayoutlibDelegate
/*package*/ static void nCallMultipleFloatMethod(Object target, long methodID,
float[] args) {
- // do nothing
+ assert args != null;
+
+ // Box parameters
+ Object[] params = new Object[args.length];
+ for (int i = 0; i < args.length; i++) {
+ params[i] = args;
+ }
+ callMethod(target, methodID, params);
}
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
index dd2978f..3c71233 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
@@ -44,7 +44,7 @@
// ---- delegate data ----
// This governs how accurate the approximation of the Path is.
- private static final float PRECISION = 0.002f;
+ private static final float PRECISION = 0.0002f;
/**
* Array containing the path points components. There are three components for each point:
diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
index 5f0d98b..9677aaf 100644
--- a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
@@ -18,6 +18,7 @@
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.tools.layoutlib.java.System_Delegate;
/**
* Delegate implementing the native methods of android.os.SystemClock
@@ -30,9 +31,6 @@
*
*/
public class SystemClock_Delegate {
- private static long sBootTime = System.currentTimeMillis();
- private static long sBootTimeNano = System.nanoTime();
-
/**
* Returns milliseconds since boot, not counting time spent in deep sleep.
* <b>Note:</b> This value may get reset occasionally (before it would
@@ -42,7 +40,7 @@
*/
@LayoutlibDelegate
/*package*/ static long uptimeMillis() {
- return System.currentTimeMillis() - sBootTime;
+ return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
}
/**
@@ -52,7 +50,7 @@
*/
@LayoutlibDelegate
/*package*/ static long elapsedRealtime() {
- return System.currentTimeMillis() - sBootTime;
+ return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
}
/**
@@ -62,7 +60,7 @@
*/
@LayoutlibDelegate
/*package*/ static long elapsedRealtimeNanos() {
- return System.nanoTime() - sBootTimeNano;
+ return System_Delegate.nanoTime() - System_Delegate.bootTime();
}
/**
@@ -72,7 +70,7 @@
*/
@LayoutlibDelegate
/*package*/ static long currentThreadTimeMillis() {
- return System.currentTimeMillis();
+ return System_Delegate.currentTimeMillis();
}
/**
@@ -84,7 +82,7 @@
*/
@LayoutlibDelegate
/*package*/ static long currentThreadTimeMicro() {
- return System.currentTimeMillis() * 1000;
+ return System_Delegate.currentTimeMillis() * 1000;
}
/**
diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
index f75ee50..01af669 100644
--- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
@@ -17,6 +17,8 @@
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import java.util.concurrent.atomic.AtomicReference;
+
/**
* Delegate used to provide new implementation of a select few methods of {@link Choreographer}
*
@@ -25,9 +27,41 @@
*
*/
public class Choreographer_Delegate {
+ static final AtomicReference<Choreographer> mInstance = new AtomicReference<Choreographer>();
+
+ @LayoutlibDelegate
+ public static Choreographer getInstance() {
+ if (mInstance.get() == null) {
+ mInstance.compareAndSet(null, Choreographer.getInstance_Original());
+ }
+
+ return mInstance.get();
+ }
@LayoutlibDelegate
public static float getRefreshRate() {
return 60.f;
}
+
+ @LayoutlibDelegate
+ static void scheduleVsyncLocked(Choreographer thisChoreographer) {
+ // do nothing
+ }
+
+ public static void doFrame(long frameTimeNanos) {
+ Choreographer thisChoreographer = Choreographer.getInstance();
+
+ thisChoreographer.mLastFrameTimeNanos = frameTimeNanos;
+
+ thisChoreographer.mFrameInfo.markInputHandlingStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
+
+ thisChoreographer.mFrameInfo.markAnimationsStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+
+ thisChoreographer.mFrameInfo.markPerformTraversalsStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
+
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 6951ede..eea254b 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -541,4 +541,8 @@
@Override
public void endProlongedAnimations() {
}
+
+ @Override
+ public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 48ca7d8..683c4aa 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -183,7 +183,7 @@
*/
private static LayoutLog sCurrentLog = sDefaultLog;
- private static final int LAST_SUPPORTED_FEATURE = Features.RECYCLER_VIEW_ADAPTER;
+ private static final int LAST_SUPPORTED_FEATURE = Features.CHOREOGRAPHER;
@Override
public int getApiLevel() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index feb2590..2ac212c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -23,6 +23,7 @@
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+import com.android.tools.layoutlib.java.System_Delegate;
import android.view.View;
import android.view.ViewGroup;
@@ -191,6 +192,21 @@
}
@Override
+ public void setSystemTimeNanos(long nanos) {
+ System_Delegate.setNanosTime(nanos);
+ }
+
+ @Override
+ public void setSystemBootTimeNanos(long nanos) {
+ System_Delegate.setBootTimeNanos(nanos);
+ }
+
+ @Override
+ public void setElapsedFrameTimeNanos(long nanos) {
+ mSession.setElapsedFrameTimeNanos(nanos);
+ }
+
+ @Override
public void dispose() {
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 0fcfa78..ff15f3b 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1438,6 +1438,14 @@
}
@Override
+ public SharedPreferences getSharedPreferences(File arg0, int arg1) {
+ if (mSharedPreferences == null) {
+ mSharedPreferences = new BridgeSharedPreferences();
+ }
+ return mSharedPreferences;
+ }
+
+ @Override
public Drawable getWallpaper() {
// pass
return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 0ffa357..ec50cfe 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -46,6 +46,7 @@
import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.java.System_Delegate;
import com.android.util.Pair;
import android.animation.AnimationThread;
@@ -62,6 +63,7 @@
import android.preference.Preference_Delegate;
import android.view.AttachInfo_Accessor;
import android.view.BridgeInflater;
+import android.view.Choreographer_Delegate;
import android.view.IWindowManager;
import android.view.IWindowManagerImpl;
import android.view.Surface;
@@ -120,6 +122,10 @@
private int mMeasuredScreenWidth = -1;
private int mMeasuredScreenHeight = -1;
private boolean mIsAlphaChannelImage;
+ /** If >= 0, a frame will be executed */
+ private long mElapsedFrameTimeNanos = -1;
+ /** True if one frame has been already executed to start the animations */
+ private boolean mFirstFrameExecuted = false;
// information being returned through the API
private BufferedImage mImage;
@@ -252,6 +258,14 @@
}
/**
+ * Sets the time for which the next frame will be selected. The time is the elapsed time from
+ * the current system nanos time. You
+ */
+ public void setElapsedFrameTimeNanos(long nanos) {
+ mElapsedFrameTimeNanos = nanos;
+ }
+
+ /**
* Renders the scene.
* <p>
* {@link #acquire(long)} must have been called before this.
@@ -428,6 +442,16 @@
gc.dispose();
}
+ if (mElapsedFrameTimeNanos >= 0) {
+ long initialTime = System_Delegate.nanoTime();
+ if (!mFirstFrameExecuted) {
+ // The first frame will initialize the animations
+ Choreographer_Delegate.doFrame(initialTime);
+ mFirstFrameExecuted = true;
+ }
+ // Second frame will move the animations
+ Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos);
+ }
mViewRoot.draw(mCanvas);
}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
new file mode 100644
index 0000000..9f26627
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
new file mode 100644
index 0000000..89009be
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml
new file mode 100644
index 0000000..70d7396
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:padding="16dp"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <ProgressBar
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent" />
+
+</LinearLayout>
+
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index 9ebeebd..2dca07c 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -48,6 +48,8 @@
import java.net.URL;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
import static org.junit.Assert.fail;
@@ -348,16 +350,46 @@
renderAndVerify(params, "expand_horz_layout.png");
}
+ /** Test expand_layout.xml */
+ @Test
+ public void testVectorAnimation() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
+ "indeterminate_progressbar.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ RenderingMode.V_SCROLL, 22);
+
+ renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
+
+ parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
+ "indeterminate_progressbar.xml");
+ params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ RenderingMode.V_SCROLL, 22);
+ renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
+ }
+
/**
* Create a new rendering session and test that rendering given layout on nexus 5
* doesn't throw any exceptions and matches the provided image.
+ * <p/>If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time
+ * indicates how far in the future is.
*/
- private void renderAndVerify(SessionParams params, String goldenFileName)
+ private void renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos)
throws ClassNotFoundException {
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
RenderSession session = sBridge.createSession(params);
+ if (frameTimeNanos != -1) {
+ session.setElapsedFrameTimeNanos(frameTimeNanos);
+ }
+
if (!session.getResult().isSuccess()) {
getLogger().error(session.getResult().getException(),
session.getResult().getErrorMessage());
@@ -380,6 +412,15 @@
* Create a new rendering session and test that rendering given layout on nexus 5
* doesn't throw any exceptions and matches the provided image.
*/
+ private void renderAndVerify(SessionParams params, String goldenFileName)
+ throws ClassNotFoundException {
+ renderAndVerify(params, goldenFileName, -1);
+ }
+
+ /**
+ * Create a new rendering session and test that rendering given layout on nexus 5
+ * doesn't throw any exceptions and matches the provided image.
+ */
private void renderAndVerify(String layoutFileName, String goldenFileName)
throws ClassNotFoundException {
// Create the layout pull parser.
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index f6c2626..8f0ad01 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -77,6 +77,8 @@
/** Methods to inject. FQCN of class in which method should be injected => runnable that does
* the injection. */
private final Map<String, ICreateInfo.InjectMethodRunnable> mInjectedMethodsMap;
+ /** A map { FQCN => set { field names } } which should be promoted to public visibility */
+ private final Map<String, Set<String>> mPromotedFields;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
@@ -109,20 +111,8 @@
// Create the map/set of methods to change to delegates
mDelegateMethods = new HashMap<String, Set<String>>();
- for (String signature : createInfo.getDelegateMethods()) {
- int pos = signature.indexOf('#');
- if (pos <= 0 || pos >= signature.length() - 1) {
- continue;
- }
- String className = binaryToInternalClassName(signature.substring(0, pos));
- String methodName = signature.substring(pos + 1);
- Set<String> methods = mDelegateMethods.get(className);
- if (methods == null) {
- methods = new HashSet<String>();
- mDelegateMethods.put(className, methods);
- }
- methods.add(methodName);
- }
+ addToMap(createInfo.getDelegateMethods(), mDelegateMethods);
+
for (String className : createInfo.getDelegateClassNatives()) {
className = binaryToInternalClassName(className);
Set<String> methods = mDelegateMethods.get(className);
@@ -187,10 +177,34 @@
returnTypes.add(binaryToInternalClassName(className));
}
+ mPromotedFields = new HashMap<String, Set<String>>();
+ addToMap(createInfo.getPromotedFields(), mPromotedFields);
+
mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
}
/**
+ * For each value in the array, split the value on '#' and add the parts to the map as key
+ * and value.
+ */
+ private void addToMap(String[] entries, Map<String, Set<String>> map) {
+ for (String entry : entries) {
+ int pos = entry.indexOf('#');
+ if (pos <= 0 || pos >= entry.length() - 1) {
+ return;
+ }
+ String className = binaryToInternalClassName(entry.substring(0, pos));
+ String methodOrFieldName = entry.substring(pos + 1);
+ Set<String> set = map.get(className);
+ if (set == null) {
+ set = new HashSet<String>();
+ map.put(className, set);
+ }
+ set.add(methodOrFieldName);
+ }
+ }
+
+ /**
* Returns the list of classes that have not been renamed yet.
* <p/>
* The names are "internal class names" rather than FQCN, i.e. they use "/" instead "."
@@ -380,6 +394,10 @@
}
}
+ Set<String> promoteFields = mPromotedFields.get(className);
+ if (promoteFields != null && !promoteFields.isEmpty()) {
+ cv = new PromoteFieldClassAdapter(cv, promoteFields);
+ }
cr.accept(cv, 0);
return cw.toByteArray();
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index e480ead..b571c5a 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -120,6 +120,11 @@
}
@Override
+ public String[] getPromotedFields() {
+ return PROMOTED_FIELDS;
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return INJECTED_METHODS;
}
@@ -169,7 +174,9 @@
"android.text.format.DateFormat#is24HourFormat",
"android.text.Hyphenator#getSystemHyphenatorLocation",
"android.util.Xml#newPullParser",
+ "android.view.Choreographer#getInstance",
"android.view.Choreographer#getRefreshRate",
+ "android.view.Choreographer#scheduleVsyncLocked",
"android.view.Display#updateDisplayInfoLocked",
"android.view.Display#getWindowManager",
"android.view.LayoutInflater#rInflate",
@@ -290,6 +297,10 @@
"org.kxml2.io.KXmlParser"
};
+ private final static String[] PROMOTED_FIELDS = new String[] {
+ "android.view.Choreographer#mLastFrameTimeNanos"
+ };
+
/**
* List of classes for which the methods returning them should be deleted.
* The array contains a list of null terminated section starting with the name of the class
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
index 54b1fe6..6c62423 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -78,6 +78,13 @@
Set<String> getExcludedClasses();
/**
+ * Returns a list of fields which should be promoted to public visibility. The array values
+ * are in the form of the binary FQCN of the class containing the field and the field name
+ * separated by a '#'.
+ */
+ String[] getPromotedFields();
+
+ /**
* Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be
* called to inject methods into a class.
* Can be empty but must not be null.
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
new file mode 100644
index 0000000..e4b70da
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+
+import java.util.Set;
+
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
+import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ASM4;
+
+/**
+ * Promotes given fields to public visibility.
+ */
+public class PromoteFieldClassAdapter extends ClassVisitor {
+
+ private final Set<String> mFieldNames;
+ private static final int ACC_NOT_PUBLIC = ~(ACC_PRIVATE | ACC_PROTECTED);
+
+ public PromoteFieldClassAdapter(ClassVisitor cv, Set<String> fieldNames) {
+ super(ASM4, cv);
+ mFieldNames = fieldNames;
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ if (mFieldNames.contains(name)) {
+ if ((access & ACC_PUBLIC) == 0) {
+ access = (access & ACC_NOT_PUBLIC) | ACC_PUBLIC;
+ }
+ }
+ return super.visitField(access, name, desc, signature, value);
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index 0b85c48..5e47261 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -134,7 +134,33 @@
}
});
- // Case 5: java.util.LinkedHashMap.eldest()
+ // Case 5: java.lang.System time calls
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ @Override
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
+ return JAVA_LANG_SYSTEM.equals(owner) && name.equals("nanoTime");
+ }
+
+ @Override
+ public void replace(MethodInformation mi) {
+ mi.name = "nanoTime";
+ mi.owner = Type.getInternalName(System_Delegate.class);
+ }
+ });
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ @Override
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
+ return JAVA_LANG_SYSTEM.equals(owner) && name.equals("currentTimeMillis");
+ }
+
+ @Override
+ public void replace(MethodInformation mi) {
+ mi.name = "currentTimeMillis";
+ mi.owner = Type.getInternalName(System_Delegate.class);
+ }
+ });
+
+ // Case 6: java.util.LinkedHashMap.eldest()
METHOD_REPLACERS.add(new MethodReplacer() {
private final String VOID_TO_MAP_ENTRY =
@@ -157,7 +183,7 @@
}
});
- // Case 6: android.content.Context.getClassLoader() in LayoutInflater
+ // Case 7: android.content.Context.getClassLoader() in LayoutInflater
METHOD_REPLACERS.add(new MethodReplacer() {
// When LayoutInflater asks for a class loader, we must return the class loader that
// cannot return app's custom views/classes. This is so that in case of any failure
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
index 613c8d9..be93744 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
@@ -18,12 +18,22 @@
import com.android.tools.layoutlib.create.ReplaceMethodCallsAdapter;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
/**
* Provides dummy implementation of methods that don't exist on the host VM.
+ * This also providers a time control that allows to set a specific system time.
*
* @see ReplaceMethodCallsAdapter
*/
+@SuppressWarnings("unused")
public class System_Delegate {
+ // Current system time
+ private static AtomicLong mNanosTime = new AtomicLong(System.nanoTime());
+ // Time that the system booted up in nanos
+ private static AtomicLong mBootNanosTime = new AtomicLong(System.nanoTime());
+
public static void log(String message) {
// ignore.
}
@@ -31,4 +41,28 @@
public static void log(String message, Throwable th) {
// ignore.
}
+
+ public static void setNanosTime(long nanos) {
+ mNanosTime.set(nanos);
+ }
+
+ public static void setBootTimeNanos(long nanos) {
+ mBootNanosTime.set(nanos);
+ }
+
+ public static long nanoTime() {
+ return mNanosTime.get();
+ }
+
+ public static long currentTimeMillis() {
+ return TimeUnit.NANOSECONDS.toMillis(mNanosTime.get());
+ }
+
+ public static long bootTime() {
+ return mBootNanosTime.get();
+ }
+
+ public static long bootTimeMillis() {
+ return TimeUnit.NANOSECONDS.toMillis(mBootNanosTime.get());
+ }
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 2c21470..8a2235b 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -138,6 +138,11 @@
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return new HashMap<String, InjectMethodRunnable>(0);
}
@@ -213,6 +218,11 @@
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return new HashMap<String, InjectMethodRunnable>(0);
}
@@ -296,6 +306,11 @@
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return new HashMap<String, InjectMethodRunnable>(0);
}
@@ -374,6 +389,11 @@
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
HashMap<String, InjectMethodRunnable> map =
new HashMap<String, InjectMethodRunnable>(1);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index bd030e8..b054f7c 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -665,7 +665,7 @@
* </ul>
* @return a list of network configurations in the form of a list
* of {@link WifiConfiguration} objects. Upon failure to fetch or
- * when when Wi-Fi is turned off, it can be null.
+ * when Wi-Fi is turned off, it can be null.
*/
public List<WifiConfiguration> getConfiguredNetworks() {
try {