Merge "Add subtype for priority"
diff --git a/Android.bp b/Android.bp
index 979ed1c..e23d9c2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -215,7 +215,6 @@
"core/java/android/os/IDeviceIdleController.aidl",
"core/java/android/os/IHardwarePropertiesManager.aidl",
"core/java/android/os/IIncidentManager.aidl",
- "core/java/android/os/IIncidentReportCompletedListener.aidl",
"core/java/android/os/IIncidentReportStatusListener.aidl",
"core/java/android/os/IMaintenanceActivityListener.aidl",
"core/java/android/os/IMessenger.aidl",
@@ -671,6 +670,7 @@
"libphonenumber-platform",
"nist-sip",
"tagsoup",
+ "rappor",
],
dxflags: ["--core-library"],
}
@@ -693,7 +693,6 @@
srcs: [
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
- "tools/streaming_proto/stream.proto",
],
target: {
diff --git a/Android.mk b/Android.mk
index 1035362..995630b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -274,6 +274,9 @@
../opt/net/voip/src/java/android/net/rtp \
../opt/net/voip/src/java/android/net/sip \
+framework_base_android_test_base_src_files := \
+ $(call all-java-files-under, test-base/src/junit)
+
framework_base_android_test_mock_src_files := \
$(call all-java-files-under, test-mock/src/android/test/mock)
@@ -284,7 +287,6 @@
# to document and check apis
files_to_check_apis := \
$(call find-other-java-files, \
- test-base/src \
$(non_base_dirs) \
)
@@ -308,6 +310,7 @@
files_to_document := \
$(files_to_check_apis) \
$(call find-other-java-files,\
+ test-base/src \
test-runner/src)
# These are relative to frameworks/base
@@ -327,6 +330,7 @@
# These are relative to frameworks/base
framework_docs_LOCAL_API_CHECK_SRC_FILES := \
+ $(framework_base_android_test_base_src_files) \
$(framework_base_android_test_mock_src_files) \
$(framework_base_android_test_runner_src_files) \
$(files_to_check_apis) \
@@ -999,7 +1003,6 @@
-Iexternal/protobuf/src
LOCAL_SOURCE_FILES_ALL_GENERATED := true
LOCAL_SRC_FILES := \
- tools/streaming_proto/stream.proto \
cmds/am/proto/instrumentation_data.proto \
$(call all-proto-files-under, core/proto) \
$(call all-proto-files-under, libs/incident/proto) \
diff --git a/api/current.txt b/api/current.txt
index f89df61..ced8354 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3953,7 +3953,7 @@
field public static final android.os.Parcelable.Creator<android.app.ActivityManager.RunningAppProcessInfo> CREATOR;
field public static final deprecated int IMPORTANCE_BACKGROUND = 400; // 0x190
field public static final int IMPORTANCE_CACHED = 400; // 0x190
- field public static final int IMPORTANCE_CANT_SAVE_STATE = 270; // 0x10e
+ field public static final int IMPORTANCE_CANT_SAVE_STATE = 350; // 0x15e
field public static final deprecated int IMPORTANCE_EMPTY = 500; // 0x1f4
field public static final int IMPORTANCE_FOREGROUND = 100; // 0x64
field public static final int IMPORTANCE_FOREGROUND_SERVICE = 125; // 0x7d
@@ -3961,7 +3961,8 @@
field public static final int IMPORTANCE_PERCEPTIBLE = 230; // 0xe6
field public static final int IMPORTANCE_PERCEPTIBLE_PRE_26 = 130; // 0x82
field public static final int IMPORTANCE_SERVICE = 300; // 0x12c
- field public static final int IMPORTANCE_TOP_SLEEPING = 150; // 0x96
+ field public static final int IMPORTANCE_TOP_SLEEPING = 325; // 0x145
+ field public static final deprecated int IMPORTANCE_TOP_SLEEPING_PRE_28 = 150; // 0x96
field public static final int IMPORTANCE_VISIBLE = 200; // 0xc8
field public static final int REASON_PROVIDER_IN_USE = 1; // 0x1
field public static final int REASON_SERVICE_IN_USE = 2; // 0x2
@@ -5199,6 +5200,7 @@
field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
+ field public static final java.lang.String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
field public static final deprecated java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession";
@@ -5482,7 +5484,9 @@
method public java.util.List<android.app.Notification.MessagingStyle.Message> getHistoricMessages();
method public java.util.List<android.app.Notification.MessagingStyle.Message> getMessages();
method public java.lang.CharSequence getUserDisplayName();
+ method public boolean isGroupConversation();
method public android.app.Notification.MessagingStyle setConversationTitle(java.lang.CharSequence);
+ method public android.app.Notification.MessagingStyle setGroupConversation(boolean);
field public static final int MAXIMUM_RETAINED_MESSAGES = 25; // 0x19
}
@@ -6450,6 +6454,7 @@
method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public void setKeepUninstalledPackages(android.content.ComponentName, java.util.List<java.lang.String>);
+ method public boolean setKeyPairCertificate(android.content.ComponentName, java.lang.String, java.util.List<java.security.cert.Certificate>, boolean);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
method public void setLockTaskFeatures(android.content.ComponentName, int);
@@ -7030,6 +7035,7 @@
field public static final java.lang.String HINT_NO_TINT = "no_tint";
field public static final java.lang.String HINT_PARTIAL = "partial";
field public static final java.lang.String HINT_SELECTED = "selected";
+ field public static final java.lang.String HINT_SHORTCUT = "shortcut";
field public static final java.lang.String HINT_SUMMARY = "summary";
field public static final java.lang.String HINT_TITLE = "title";
field public static final java.lang.String SUBTYPE_COLOR = "color";
@@ -8222,7 +8228,7 @@
method public void onAppStatusChanged(android.bluetooth.BluetoothDevice, boolean);
method public void onConnectionStateChanged(android.bluetooth.BluetoothDevice, int);
method public void onGetReport(android.bluetooth.BluetoothDevice, byte, byte, int);
- method public void onIntrData(android.bluetooth.BluetoothDevice, byte, byte[]);
+ method public void onInterruptData(android.bluetooth.BluetoothDevice, byte, byte[]);
method public void onSetProtocol(android.bluetooth.BluetoothDevice, byte);
method public void onSetReport(android.bluetooth.BluetoothDevice, byte, byte, byte[]);
method public void onVirtualCableUnplug(android.bluetooth.BluetoothDevice);
@@ -40342,6 +40348,7 @@
public class CarrierConfigManager {
method public android.os.PersistableBundle getConfig();
method public android.os.PersistableBundle getConfigForSubId(int);
+ method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
method public void notifyConfigChangedForSubId(int);
field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
@@ -41064,6 +41071,7 @@
method public java.lang.String getMeid(int);
method public java.lang.String getMmsUAProfUrl();
method public java.lang.String getMmsUserAgent();
+ method public java.lang.String getNai();
method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo();
method public java.lang.String getNetworkCountryIso();
method public java.lang.String getNetworkOperator();
@@ -41097,11 +41105,12 @@
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
method public boolean isConcurrentVoiceAndDataSupported();
- method public boolean isDataEnabled();
+ method public deprecated boolean isDataEnabled();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isNetworkRoaming();
method public boolean isSmsCapable();
method public deprecated boolean isTtyModeSupported();
+ method public boolean isUserMobileDataEnabled();
method public boolean isVoiceCapable();
method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
method public boolean isWorldPhone();
@@ -41110,10 +41119,11 @@
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
method public void sendVisualVoicemailSms(java.lang.String, int, java.lang.String, android.app.PendingIntent);
- method public void setDataEnabled(boolean);
+ method public deprecated void setDataEnabled(boolean);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
method public boolean setOperatorBrandOverride(java.lang.String);
method public boolean setPreferredNetworkTypeToGlobal();
+ method public void setUserMobileDataEnabled(boolean);
method public void setVisualVoicemailSmsFilterSettings(android.telephony.VisualVoicemailSmsFilterSettings);
method public boolean setVoiceMailNumber(java.lang.String, java.lang.String);
method public deprecated void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri);
@@ -41509,61 +41519,6 @@
}
-package android.test {
-
- public deprecated class AndroidTestCase extends junit.framework.TestCase {
- ctor public AndroidTestCase();
- method public void assertActivityRequiresPermission(java.lang.String, java.lang.String, java.lang.String);
- method public void assertReadingContentUriRequiresPermission(android.net.Uri, java.lang.String);
- method public void assertWritingContentUriRequiresPermission(android.net.Uri, java.lang.String);
- method public android.content.Context getContext();
- method protected void scrubClass(java.lang.Class<?>) throws java.lang.IllegalAccessException;
- method public void setContext(android.content.Context);
- method public void testAndroidTestCaseSetupProperly();
- field protected android.content.Context mContext;
- }
-
- public abstract deprecated class FlakyTest implements java.lang.annotation.Annotation {
- }
-
- public deprecated class InstrumentationTestCase extends junit.framework.TestCase {
- ctor public InstrumentationTestCase();
- method public android.app.Instrumentation getInstrumentation();
- method public deprecated void injectInsrumentation(android.app.Instrumentation);
- method public void injectInstrumentation(android.app.Instrumentation);
- method public final <T extends android.app.Activity> T launchActivity(java.lang.String, java.lang.Class<T>, android.os.Bundle);
- method public final <T extends android.app.Activity> T launchActivityWithIntent(java.lang.String, java.lang.Class<T>, android.content.Intent);
- method public void runTestOnUiThread(java.lang.Runnable) throws java.lang.Throwable;
- method public void sendKeys(java.lang.String);
- method public void sendKeys(int...);
- method public void sendRepeatedKeys(int...);
- }
-
- public deprecated class InstrumentationTestSuite extends junit.framework.TestSuite {
- ctor public InstrumentationTestSuite(android.app.Instrumentation);
- ctor public InstrumentationTestSuite(java.lang.String, android.app.Instrumentation);
- ctor public InstrumentationTestSuite(java.lang.Class, android.app.Instrumentation);
- method public void addTestSuite(java.lang.Class);
- }
-
- public abstract deprecated interface PerformanceTestCase {
- method public abstract boolean isPerformanceOnly();
- method public abstract int startPerformance(android.test.PerformanceTestCase.Intermediates);
- }
-
- public static abstract interface PerformanceTestCase.Intermediates {
- method public abstract void addIntermediate(java.lang.String);
- method public abstract void addIntermediate(java.lang.String, long);
- method public abstract void finishTiming(boolean);
- method public abstract void setInternalIterations(int);
- method public abstract void startTiming(boolean);
- }
-
- public abstract deprecated class UiThreadTest implements java.lang.annotation.Annotation {
- }
-
-}
-
package android.test.mock {
public deprecated class MockApplication extends android.app.Application {
@@ -41857,25 +41812,6 @@
}
-package android.test.suitebuilder.annotation {
-
- public abstract deprecated class LargeTest implements java.lang.annotation.Annotation {
- }
-
- public abstract deprecated class MediumTest implements java.lang.annotation.Annotation {
- }
-
- public abstract deprecated class SmallTest implements java.lang.annotation.Annotation {
- }
-
- public abstract deprecated class Smoke implements java.lang.annotation.Annotation {
- }
-
- public abstract deprecated class Suppress implements java.lang.annotation.Annotation {
- }
-
-}
-
package android.text {
public class AlteredCharSequence implements java.lang.CharSequence android.text.GetChars {
@@ -41979,9 +41915,9 @@
}
public class DynamicLayout extends android.text.Layout {
- ctor public DynamicLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
- ctor public DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
- ctor public DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
+ ctor public deprecated DynamicLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+ ctor public deprecated DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+ ctor public deprecated DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
method public int getBottomPadding();
method public int getEllipsisCount(int);
method public int getEllipsisStart(int);
@@ -42368,9 +42304,9 @@
}
public class StaticLayout extends android.text.Layout {
- ctor public StaticLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
- ctor public StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
- ctor public StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
+ ctor public deprecated StaticLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+ ctor public deprecated StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+ ctor public deprecated StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
method public int getBottomPadding();
method public int getEllipsisCount(int);
method public int getEllipsisStart(int);
@@ -44787,6 +44723,14 @@
field public static final android.os.Parcelable.Creator<android.view.Display.Mode> CREATOR;
}
+ public final class DisplayCutout {
+ method public android.graphics.Region getBounds();
+ method public int getSafeInsetBottom();
+ method public int getSafeInsetLeft();
+ method public int getSafeInsetRight();
+ method public int getSafeInsetTop();
+ }
+
public final class DragAndDropPermissions implements android.os.Parcelable {
method public int describeContents();
method public void release();
@@ -47740,8 +47684,10 @@
public final class WindowInsets {
ctor public WindowInsets(android.view.WindowInsets);
+ method public android.view.WindowInsets consumeDisplayCutout();
method public android.view.WindowInsets consumeStableInsets();
method public android.view.WindowInsets consumeSystemWindowInsets();
+ method public android.view.DisplayCutout getDisplayCutout();
method public int getStableInsetBottom();
method public int getStableInsetLeft();
method public int getStableInsetRight();
@@ -47801,6 +47747,7 @@
field public static final int FIRST_APPLICATION_WINDOW = 1; // 0x1
field public static final int FIRST_SUB_WINDOW = 1000; // 0x3e8
field public static final int FIRST_SYSTEM_WINDOW = 2000; // 0x7d0
+ field public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 1L; // 0x1L
field public static final int FLAGS_CHANGED = 4; // 0x4
field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1
field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
@@ -47895,6 +47842,7 @@
field public float buttonBrightness;
field public float dimAmount;
field public int flags;
+ field public long flags2;
field public int format;
field public int gravity;
field public float horizontalMargin;
@@ -49191,9 +49139,13 @@
method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options);
method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence);
+ method public default java.util.Collection<java.lang.String> getEntitiesForPreset(int);
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
+ field public static final int ENTITY_PRESET_ALL = 0; // 0x0
+ field public static final int ENTITY_PRESET_BASE = 2; // 0x2
+ field public static final int ENTITY_PRESET_NONE = 1; // 0x1
field public static final android.view.textclassifier.TextClassifier NO_OP;
field public static final java.lang.String TYPE_ADDRESS = "address";
field public static final java.lang.String TYPE_EMAIL = "email";
@@ -49203,6 +49155,13 @@
field public static final java.lang.String TYPE_URL = "url";
}
+ public static final class TextClassifier.EntityConfig {
+ ctor public TextClassifier.EntityConfig(int);
+ method public android.view.textclassifier.TextClassifier.EntityConfig excludeEntities(java.lang.String...);
+ method public java.util.List<java.lang.String> getEntities(android.view.textclassifier.TextClassifier);
+ method public android.view.textclassifier.TextClassifier.EntityConfig includeEntities(java.lang.String...);
+ }
+
public final class TextLinks {
method public boolean apply(android.text.SpannableString, java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.text.style.ClickableSpan>);
method public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks();
@@ -49217,7 +49176,9 @@
public static final class TextLinks.Options {
ctor public TextLinks.Options();
method public android.os.LocaleList getDefaultLocales();
+ method public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig();
method public android.view.textclassifier.TextLinks.Options setDefaultLocales(android.os.LocaleList);
+ method public android.view.textclassifier.TextLinks.Options setEntityConfig(android.view.textclassifier.TextClassifier.EntityConfig);
}
public static final class TextLinks.TextLink {
@@ -49918,6 +49879,7 @@
method public android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
method public android.webkit.WebMessagePort[] createWebMessageChannel();
method public void destroy();
+ method public static void disableWebView();
method public void documentHasImages(android.os.Message);
method public static void enableSlowWholeDocumentDraw();
method public void evaluateJavascript(java.lang.String, android.webkit.ValueCallback<java.lang.String>);
@@ -49978,6 +49940,7 @@
method public void saveWebArchive(java.lang.String);
method public void saveWebArchive(java.lang.String, boolean, android.webkit.ValueCallback<java.lang.String>);
method public deprecated void setCertificate(android.net.http.SslCertificate);
+ method public static void setDataDirectorySuffix(java.lang.String);
method public void setDownloadListener(android.webkit.DownloadListener);
method public void setFindListener(android.webkit.WebView.FindListener);
method public deprecated void setHorizontalScrollbarOverlay(boolean);
diff --git a/api/removed.txt b/api/removed.txt
index be4d5be..1a4e796 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -506,14 +506,6 @@
}
-package android.view.accessibility {
-
- public final class AccessibilityWindowInfo implements android.os.Parcelable {
- method public boolean inPictureInPicture();
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/api/system-current.txt b/api/system-current.txt
index 227b483..30530a3 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1339,7 +1339,7 @@
package android.hardware.location {
- public class ContextHubInfo {
+ public class ContextHubInfo implements android.os.Parcelable {
ctor public ContextHubInfo();
method public int describeContents();
method public int getId();
@@ -4159,7 +4159,7 @@
method public deprecated boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);
method public boolean needsOtaServiceProvisioning();
method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
- method public void setDataEnabled(int, boolean);
+ method public deprecated void setDataEnabled(int, boolean);
method public boolean setRadio(boolean);
method public boolean setRadioPower(boolean);
method public deprecated void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
@@ -4605,6 +4605,7 @@
method public boolean canInvokeDrawGlFunctor(android.view.View);
method public void detachDrawGlFunctor(android.view.View, long);
method public android.app.Application getApplication();
+ method public java.lang.String getDataDirectorySuffix();
method public java.lang.String getErrorString(android.content.Context, int);
method public int getPackageId(android.content.res.Resources, java.lang.String);
method public void invokeDrawGlFunctor(android.view.View, long, boolean);
diff --git a/api/test-current.txt b/api/test-current.txt
index 5635b56..e64c320 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -326,6 +326,13 @@
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
+ public class TrafficStats {
+ method public static long getLoopbackRxBytes();
+ method public static long getLoopbackRxPackets();
+ method public static long getLoopbackTxBytes();
+ method public static long getLoopbackTxPackets();
+ }
+
}
package android.os {
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 4777dcd..162a34b 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -23,6 +23,7 @@
#include <android/os/IIncidentManager.h>
#include <android/os/IncidentReportArgs.h>
#include <binder/IServiceManager.h>
+#include <statslog.h>
#include <time.h>
namespace android {
@@ -224,6 +225,9 @@
}
StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.name());
+
+ android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
+ mConfigKey.GetName().c_str(), mAlert.name().c_str());
}
void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index c37f05e..1c6d9b0 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -79,7 +79,8 @@
IsolatedUidChanged isolated_uid_changed = 43;
PacketWakeupOccurred packet_wakeup_occurred = 44;
DropboxErrorChanged dropbox_error_changed = 45;
- AppHook app_hook = 46;
+ AnomalyDetected anomaly_detected = 46;
+ AppHook app_hook = 47;
// TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
}
@@ -841,6 +842,23 @@
}
/**
+ * Logs when statsd detects an anomaly.
+ *
+ * Logged from:
+ * frameworks/base/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+ */
+message AnomalyDetected {
+ // Uid that owns the config whose anomaly detection alert fired.
+ optional int32 config_uid = 1;
+
+ // Name of the config whose anomaly detection alert fired.
+ optional string config_name = 2;
+
+ // Name of the alert (i.e. name of the anomaly that was detected).
+ optional string alert_name = 3;
+}
+
+/**
* Pulls bytes transferred via wifi (Sum of foreground and background usage).
*
* Pulled from:
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 200ef0b..5d0e97e 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -206,7 +206,7 @@
// clock will not grow very aggressive. New metrics will be delayed up to
// MIN_BUCKET_SIZE_SEC before starting.
long currentTimeSec = time(nullptr);
- uint64_t startTimeNs = (currentTimeSec + kMinBucketSizeSec -
+ uint64_t startTimeNs = (currentTimeSec - kMinBucketSizeSec -
(currentTimeSec - timeBaseSec) % kMinBucketSizeSec) *
NS_PER_SEC;
@@ -410,12 +410,15 @@
return false;
}
- if (((!metric.gauge_fields().has_include_all() ||
- (metric.gauge_fields().has_include_all() &&
- metric.gauge_fields().include_all() == false)) &&
- metric.gauge_fields().field_num_size() == 0) ||
- (metric.gauge_fields().has_include_all() && metric.gauge_fields().include_all() == true &&
- metric.gauge_fields().field_num_size() > 0)) {
+ if ((!metric.gauge_fields().has_include_all() ||
+ (metric.gauge_fields().include_all() == false)) &&
+ metric.gauge_fields().field_num_size() == 0) {
+ ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str());
+ return false;
+ }
+ if ((metric.gauge_fields().has_include_all() &&
+ metric.gauge_fields().include_all() == true) &&
+ metric.gauge_fields().field_num_size() > 0) {
ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str());
return false;
}
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 6e7a613..3018be1 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -31,10 +31,9 @@
namespace os {
namespace statsd {
-UidMap::UidMap() : mBytesUsed(0) {
-}
-UidMap::~UidMap() {
-}
+UidMap::UidMap() : mBytesUsed(0) {}
+
+UidMap::~UidMap() {}
bool UidMap::hasApp(int uid, const string& packageName) const {
lock_guard<mutex> lock(mMutex);
@@ -48,6 +47,27 @@
return false;
}
+string UidMap::normalizeAppName(const string& appName) const {
+ string normalizedName = appName;
+ std::transform(normalizedName.begin(), normalizedName.end(), normalizedName.begin(), ::tolower);
+ return normalizedName;
+}
+
+std::set<string> UidMap::getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const {
+ lock_guard<mutex> lock(mMutex);
+ return getAppNamesFromUidLocked(uid,returnNormalized);
+}
+
+std::set<string> UidMap::getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const {
+ std::set<string> names;
+ auto range = mMap.equal_range(uid);
+ for (auto it = range.first; it != range.second; ++it) {
+ names.insert(returnNormalized ?
+ normalizeAppName(it->second.packageName) : it->second.packageName);
+ }
+ return names;
+}
+
int64_t UidMap::getAppVersion(int uid, const string& packageName) const {
lock_guard<mutex> lock(mMutex);
@@ -97,17 +117,17 @@
const int64_t& versionCode) {
lock_guard<mutex> lock(mMutex);
- string app = string(String8(app_16).string());
+ string appName = string(String8(app_16).string());
// Notify any interested producers that this app has updated
for (auto it : mSubscribers) {
- it->notifyAppUpgrade(app, uid, versionCode);
+ it->notifyAppUpgrade(appName, uid, versionCode);
}
auto log = mOutput.add_changes();
log->set_deletion(false);
log->set_timestamp_nanos(timestamp);
- log->set_app(app);
+ log->set_app(appName);
log->set_uid(uid);
log->set_version(versionCode);
mBytesUsed += log->ByteSize();
@@ -117,16 +137,15 @@
auto range = mMap.equal_range(int(uid));
for (auto it = range.first; it != range.second; ++it) {
- if (it->second.packageName == app) {
+ // If we find the exact same app name and uid, update the app version directly.
+ if (it->second.packageName == appName) {
it->second.versionCode = versionCode;
return;
}
- VLOG("updateApp failed to find the app %s with uid %i to update", app.c_str(), uid);
- return;
}
// Otherwise, we need to add an app at this uid.
- mMap.insert(make_pair(uid, AppData(app, versionCode)));
+ mMap.insert(make_pair(uid, AppData(appName, versionCode)));
}
void UidMap::ensureBytesUsedBelowLimit() {
@@ -154,6 +173,7 @@
void UidMap::removeApp(const String16& app_16, const int32_t& uid) {
removeApp(time(nullptr) * NS_PER_SEC, app_16, uid);
}
+
void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
lock_guard<mutex> lock(mMutex);
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 9e1ad69..487fdf9 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef STATSD_UIDMAP_H
-#define STATSD_UIDMAP_H
+#pragma once
#include "config/ConfigKey.h"
#include "config/ConfigListener.h"
@@ -66,6 +65,9 @@
// Returns true if the given uid contains the specified app (eg. com.google.android.gms).
bool hasApp(int uid, const string& packageName) const;
+ // Returns the app names from uid.
+ std::set<string> getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const;
+
int64_t getAppVersion(int uid, const string& packageName) const;
// Helper for debugging contents of this uid map. Can be triggered with:
@@ -103,6 +105,9 @@
size_t getBytesUsed();
private:
+ std::set<string> getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const;
+ string normalizeAppName(const string& appName) const;
+
void updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
const vector<int64_t>& versionCode, const vector<String16>& packageName);
@@ -160,4 +165,3 @@
} // namespace os
} // namespace android
-#endif // STATSD_UIDMAP_H
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 5b2cedd..3fa96d3 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -74,6 +74,14 @@
EXPECT_TRUE(m.hasApp(1000, kApp1));
EXPECT_TRUE(m.hasApp(1000, kApp2));
EXPECT_FALSE(m.hasApp(1000, "not.app"));
+
+ std::set<string> name_set = m.getAppNamesFromUid(1000u, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ name_set = m.getAppNamesFromUid(12345, true /* returnNormalized */);
+ EXPECT_TRUE(name_set.empty());
}
TEST(UidMapTest, TestAddAndRemove) {
@@ -90,12 +98,59 @@
versions.push_back(5);
m.updateMap(uids, versions, apps);
+ std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ // Update the app1 version.
m.updateApp(String16(kApp1.c_str()), 1000, 40);
EXPECT_EQ(40, m.getAppVersion(1000, kApp1));
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
m.removeApp(String16(kApp1.c_str()), 1000);
EXPECT_FALSE(m.hasApp(1000, kApp1));
EXPECT_TRUE(m.hasApp(1000, kApp2));
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 1u);
+ EXPECT_TRUE(name_set.find(kApp1) == name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ // Remove app2.
+ m.removeApp(String16(kApp2.c_str()), 1000);
+ EXPECT_FALSE(m.hasApp(1000, kApp1));
+ EXPECT_FALSE(m.hasApp(1000, kApp2));
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_TRUE(name_set.empty());
+}
+
+TEST(UidMapTest, TestUpdateApp) {
+ UidMap m;
+ m.updateMap({1000, 1000}, {4, 5}, {String16(kApp1.c_str()), String16(kApp2.c_str())});
+ std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ // Adds a new name for uid 1000.
+ m.updateApp(String16("NeW_aPP1_NAmE"), 1000, 40);
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 3u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+ EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
+ EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
+
+ // This name is also reused by another uid 2000.
+ m.updateApp(String16("NeW_aPP1_NAmE"), 2000, 1);
+ name_set = m.getAppNamesFromUid(2000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 1u);
+ EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
+ EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
}
TEST(UidMapTest, TestClearingOutput) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 847082a..1adae7a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -457,6 +457,20 @@
/** @hide User operation call: one of related users cannot be stopped. */
public static final int USER_OP_ERROR_RELATED_USERS_CANNOT_STOP = -4;
+ /**
+ * @hide
+ * Process states, describing the kind of state a particular process is in.
+ * When updating these, make sure to also check all related references to the
+ * constant in code, and update these arrays:
+ *
+ * @see com.android.internal.app.procstats.ProcessState#PROCESS_STATE_TO_STATE
+ * @see com.android.server.am.ProcessList#sProcStateToProcMem
+ * @see com.android.server.am.ProcessList#sFirstAwakePssTimes
+ * @see com.android.server.am.ProcessList#sSameAwakePssTimes
+ * @see com.android.server.am.ProcessList#sTestFirstPssTimes
+ * @see com.android.server.am.ProcessList#sTestSamePssTimes
+ */
+
/** @hide Not a real process state. */
public static final int PROCESS_STATE_UNKNOWN = -1;
@@ -476,35 +490,35 @@
/** @hide Process is hosting a foreground service. */
public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
- /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
- public static final int PROCESS_STATE_TOP_SLEEPING = 5;
-
/** @hide Process is important to the user, and something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6;
+ public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
/** @hide Process is important to the user, but not something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7;
+ public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 6;
/** @hide Process is in the background transient so we will try to keep running. */
- public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 8;
+ public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 7;
/** @hide Process is in the background running a backup/restore operation. */
- public static final int PROCESS_STATE_BACKUP = 9;
-
- /** @hide Process is in the background, but it can't restore its state so we want
- * to try to avoid killing it. */
- public static final int PROCESS_STATE_HEAVY_WEIGHT = 10;
+ public static final int PROCESS_STATE_BACKUP = 8;
/** @hide Process is in the background running a service. Unlike oom_adj, this level
* is used for both the normal running in background state and the executing
* operations state. */
- public static final int PROCESS_STATE_SERVICE = 11;
+ public static final int PROCESS_STATE_SERVICE = 9;
/** @hide Process is in the background running a receiver. Note that from the
* perspective of oom_adj, receivers run at a higher foreground level, but for our
* prioritization here that is not necessary and putting them below services means
* many fewer changes in some process states as they receive broadcasts. */
- public static final int PROCESS_STATE_RECEIVER = 12;
+ public static final int PROCESS_STATE_RECEIVER = 10;
+
+ /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
+ public static final int PROCESS_STATE_TOP_SLEEPING = 11;
+
+ /** @hide Process is in the background, but it can't restore its state so we want
+ * to try to avoid killing it. */
+ public static final int PROCESS_STATE_HEAVY_WEIGHT = 12;
/** @hide Process is in the background but hosts the home activity. */
public static final int PROCESS_STATE_HOME = 13;
@@ -810,7 +824,7 @@
* impose on your application to let the overall system work best. The
* returned value is in megabytes; the baseline Android memory class is
* 16 (which happens to be the Java heap limit of those devices); some
- * device with more memory may return 24 or even higher numbers.
+ * devices with more memory may return 24 or even higher numbers.
*/
public int getMemoryClass() {
return staticGetMemoryClass();
@@ -837,7 +851,7 @@
* constrained devices, or it may be significantly larger on devices with
* a large amount of available RAM.
*
- * <p>The is the size of the application's Dalvik heap if it has
+ * <p>This is the size of the application's Dalvik heap if it has
* specified <code>android:largeHeap="true"</code> in its manifest.
*/
public int getLargeMemoryClass() {
@@ -2884,13 +2898,13 @@
public static final int IMPORTANCE_FOREGROUND_SERVICE = 125;
/**
- * Constant for {@link #importance}: This process is running the foreground
- * UI, but the device is asleep so it is not visible to the user. This means
- * the user is not really aware of the process, because they can not see or
- * interact with it, but it is quite important because it what they expect to
- * return to once unlocking the device.
+ * @deprecated Pre-{@link android.os.Build.VERSION_CODES#P} version of
+ * {@link #IMPORTANCE_TOP_SLEEPING}. As of Android
+ * {@link android.os.Build.VERSION_CODES#P}, this is considered much less
+ * important since we want to reduce what apps can do when the screen is off.
*/
- public static final int IMPORTANCE_TOP_SLEEPING = 150;
+ @Deprecated
+ public static final int IMPORTANCE_TOP_SLEEPING_PRE_28 = 150;
/**
* Constant for {@link #importance}: This process is running something
@@ -2942,14 +2956,6 @@
public static final int IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170;
/**
- * Constant for {@link #importance}: This process is running an
- * application that can not save its state, and thus can't be killed
- * while in the background. This will be used with apps that have
- * {@link android.R.attr#cantSaveState} set on their application tag.
- */
- public static final int IMPORTANCE_CANT_SAVE_STATE = 270;
-
- /**
* Constant for {@link #importance}: This process is contains services
* that should remain running. These are background services apps have
* started, not something the user is aware of, so they may be killed by
@@ -2959,6 +2965,23 @@
public static final int IMPORTANCE_SERVICE = 300;
/**
+ * Constant for {@link #importance}: This process is running the foreground
+ * UI, but the device is asleep so it is not visible to the user. Though the
+ * system will try hard to keep its process from being killed, in all other
+ * ways we consider it a kind of cached process, with the limitations that go
+ * along with that state: network access, running background services, etc.
+ */
+ public static final int IMPORTANCE_TOP_SLEEPING = 325;
+
+ /**
+ * Constant for {@link #importance}: This process is running an
+ * application that can not save its state, and thus can't be killed
+ * while in the background. This will be used with apps that have
+ * {@link android.R.attr#cantSaveState} set on their application tag.
+ */
+ public static final int IMPORTANCE_CANT_SAVE_STATE = 350;
+
+ /**
* Constant for {@link #importance}: This process process contains
* cached code that is expendable, not actively running any app components
* we care about.
@@ -2993,16 +3016,16 @@
return IMPORTANCE_GONE;
} else if (procState >= PROCESS_STATE_HOME) {
return IMPORTANCE_CACHED;
- } else if (procState >= PROCESS_STATE_SERVICE) {
- return IMPORTANCE_SERVICE;
} else if (procState == PROCESS_STATE_HEAVY_WEIGHT) {
return IMPORTANCE_CANT_SAVE_STATE;
+ } else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
+ return IMPORTANCE_TOP_SLEEPING;
+ } else if (procState >= PROCESS_STATE_SERVICE) {
+ return IMPORTANCE_SERVICE;
} else if (procState >= PROCESS_STATE_TRANSIENT_BACKGROUND) {
return IMPORTANCE_PERCEPTIBLE;
} else if (procState >= PROCESS_STATE_IMPORTANT_FOREGROUND) {
return IMPORTANCE_VISIBLE;
- } else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
- return IMPORTANCE_TOP_SLEEPING;
} else if (procState >= PROCESS_STATE_FOREGROUND_SERVICE) {
return IMPORTANCE_FOREGROUND_SERVICE;
} else {
@@ -3036,6 +3059,8 @@
switch (importance) {
case IMPORTANCE_PERCEPTIBLE:
return IMPORTANCE_PERCEPTIBLE_PRE_26;
+ case IMPORTANCE_TOP_SLEEPING:
+ return IMPORTANCE_TOP_SLEEPING_PRE_28;
case IMPORTANCE_CANT_SAVE_STATE:
return IMPORTANCE_CANT_SAVE_STATE_PRE_26;
}
@@ -3049,16 +3074,18 @@
return PROCESS_STATE_NONEXISTENT;
} else if (importance >= IMPORTANCE_CACHED) {
return PROCESS_STATE_HOME;
+ } else if (importance >= IMPORTANCE_CANT_SAVE_STATE) {
+ return PROCESS_STATE_HEAVY_WEIGHT;
+ } else if (importance >= IMPORTANCE_TOP_SLEEPING) {
+ return PROCESS_STATE_TOP_SLEEPING;
} else if (importance >= IMPORTANCE_SERVICE) {
return PROCESS_STATE_SERVICE;
- } else if (importance == IMPORTANCE_CANT_SAVE_STATE) {
- return PROCESS_STATE_HEAVY_WEIGHT;
} else if (importance >= IMPORTANCE_PERCEPTIBLE) {
return PROCESS_STATE_TRANSIENT_BACKGROUND;
} else if (importance >= IMPORTANCE_VISIBLE) {
return PROCESS_STATE_IMPORTANT_FOREGROUND;
- } else if (importance >= IMPORTANCE_TOP_SLEEPING) {
- return PROCESS_STATE_TOP_SLEEPING;
+ } else if (importance >= IMPORTANCE_TOP_SLEEPING_PRE_28) {
+ return PROCESS_STATE_FOREGROUND_SERVICE;
} else if (importance >= IMPORTANCE_FOREGROUND_SERVICE) {
return PROCESS_STATE_FOREGROUND_SERVICE;
} else {
@@ -3837,7 +3864,7 @@
pw.println();
dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { packageName });
pw.println();
- dumpService(pw, fd, "usagestats", new String[] { "--packages", packageName });
+ dumpService(pw, fd, "usagestats", new String[] { packageName });
pw.println();
dumpService(pw, fd, BatteryStats.SERVICE_NAME, new String[] { packageName });
pw.flush();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 84f032d..de346f3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1152,7 +1152,7 @@
int N = stats.dbStats.size();
if (N > 0) {
pw.println(" DATABASES");
- printRow(pw, " %8s %8s %14s %14s %s", "pgsz", "dbsz", "Lookaside(b)", "cache",
+ printRow(pw, DB_INFO_FORMAT, "pgsz", "dbsz", "Lookaside(b)", "cache",
"Dbname");
for (int i = 0; i < N; i++) {
DbStats dbStats = stats.dbStats.get(i);
@@ -1184,6 +1184,124 @@
}
@Override
+ public void dumpMemInfoProto(ParcelFileDescriptor pfd, Debug.MemoryInfo mem,
+ boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
+ boolean dumpUnreachable, String[] args) {
+ ProtoOutputStream proto = new ProtoOutputStream(pfd.getFileDescriptor());
+ try {
+ dumpMemInfo(proto, mem, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
+ } finally {
+ proto.flush();
+ IoUtils.closeQuietly(pfd);
+ }
+ }
+
+ private void dumpMemInfo(ProtoOutputStream proto, Debug.MemoryInfo memInfo,
+ boolean dumpFullInfo, boolean dumpDalvik,
+ boolean dumpSummaryOnly, boolean dumpUnreachable) {
+ long nativeMax = Debug.getNativeHeapSize() / 1024;
+ long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
+ long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
+
+ Runtime runtime = Runtime.getRuntime();
+ runtime.gc(); // Do GC since countInstancesOfClass counts unreachable objects.
+ long dalvikMax = runtime.totalMemory() / 1024;
+ long dalvikFree = runtime.freeMemory() / 1024;
+ long dalvikAllocated = dalvikMax - dalvikFree;
+
+ Class[] classesToCount = new Class[] {
+ ContextImpl.class,
+ Activity.class,
+ WebView.class,
+ OpenSSLSocketImpl.class
+ };
+ long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true);
+ long appContextInstanceCount = instanceCounts[0];
+ long activityInstanceCount = instanceCounts[1];
+ long webviewInstanceCount = instanceCounts[2];
+ long openSslSocketCount = instanceCounts[3];
+
+ long viewInstanceCount = ViewDebug.getViewInstanceCount();
+ long viewRootInstanceCount = ViewDebug.getViewRootImplCount();
+ int globalAssetCount = AssetManager.getGlobalAssetCount();
+ int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
+ int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
+ int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
+ int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
+ long parcelSize = Parcel.getGlobalAllocSize();
+ long parcelCount = Parcel.getGlobalAllocCount();
+ SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
+
+ final long mToken = proto.start(MemInfoProto.AppData.PROCESS_MEMORY);
+ proto.write(MemInfoProto.ProcessMemory.PID, Process.myPid());
+ proto.write(MemInfoProto.ProcessMemory.PROCESS_NAME,
+ (mBoundApplication != null) ? mBoundApplication.processName : "unknown");
+ dumpMemInfoTable(proto, memInfo, dumpDalvik, dumpSummaryOnly,
+ nativeMax, nativeAllocated, nativeFree,
+ dalvikMax, dalvikAllocated, dalvikFree);
+ proto.end(mToken);
+
+ final long oToken = proto.start(MemInfoProto.AppData.OBJECTS);
+ proto.write(MemInfoProto.AppData.ObjectStats.VIEW_INSTANCE_COUNT, viewInstanceCount);
+ proto.write(MemInfoProto.AppData.ObjectStats.VIEW_ROOT_INSTANCE_COUNT,
+ viewRootInstanceCount);
+ proto.write(MemInfoProto.AppData.ObjectStats.APP_CONTEXT_INSTANCE_COUNT,
+ appContextInstanceCount);
+ proto.write(MemInfoProto.AppData.ObjectStats.ACTIVITY_INSTANCE_COUNT,
+ activityInstanceCount);
+ proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_COUNT, globalAssetCount);
+ proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_MANAGER_COUNT,
+ globalAssetManagerCount);
+ proto.write(MemInfoProto.AppData.ObjectStats.LOCAL_BINDER_OBJECT_COUNT,
+ binderLocalObjectCount);
+ proto.write(MemInfoProto.AppData.ObjectStats.PROXY_BINDER_OBJECT_COUNT,
+ binderProxyObjectCount);
+ proto.write(MemInfoProto.AppData.ObjectStats.PARCEL_MEMORY_KB, parcelSize / 1024);
+ proto.write(MemInfoProto.AppData.ObjectStats.PARCEL_COUNT, parcelCount);
+ proto.write(MemInfoProto.AppData.ObjectStats.BINDER_OBJECT_DEATH_COUNT,
+ binderDeathObjectCount);
+ proto.write(MemInfoProto.AppData.ObjectStats.OPEN_SSL_SOCKET_COUNT, openSslSocketCount);
+ proto.write(MemInfoProto.AppData.ObjectStats.WEBVIEW_INSTANCE_COUNT,
+ webviewInstanceCount);
+ proto.end(oToken);
+
+ // SQLite mem info
+ final long sToken = proto.start(MemInfoProto.AppData.SQL);
+ proto.write(MemInfoProto.AppData.SqlStats.MEMORY_USED_KB, stats.memoryUsed / 1024);
+ proto.write(MemInfoProto.AppData.SqlStats.PAGECACHE_OVERFLOW_KB,
+ stats.pageCacheOverflow / 1024);
+ proto.write(MemInfoProto.AppData.SqlStats.MALLOC_SIZE_KB, stats.largestMemAlloc / 1024);
+ int n = stats.dbStats.size();
+ for (int i = 0; i < n; i++) {
+ DbStats dbStats = stats.dbStats.get(i);
+
+ final long dToken = proto.start(MemInfoProto.AppData.SqlStats.DATABASES);
+ proto.write(MemInfoProto.AppData.SqlStats.Database.NAME, dbStats.dbName);
+ proto.write(MemInfoProto.AppData.SqlStats.Database.PAGE_SIZE, dbStats.pageSize);
+ proto.write(MemInfoProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize);
+ proto.write(MemInfoProto.AppData.SqlStats.Database.LOOKASIDE_B, dbStats.lookaside);
+ proto.write(MemInfoProto.AppData.SqlStats.Database.CACHE, dbStats.cache);
+ proto.end(dToken);
+ }
+ proto.end(sToken);
+
+ // Asset details.
+ String assetAlloc = AssetManager.getAssetAllocations();
+ if (assetAlloc != null) {
+ proto.write(MemInfoProto.AppData.ASSET_ALLOCATIONS, assetAlloc);
+ }
+
+ // Unreachable native memory
+ if (dumpUnreachable) {
+ int flags = mBoundApplication == null ? 0 : mBoundApplication.appInfo.flags;
+ boolean showContents = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0
+ || android.os.Build.IS_DEBUGGABLE;
+ proto.write(MemInfoProto.AppData.UNREACHABLE_MEMORY,
+ Debug.getUnreachableMemory(100, showContents));
+ }
+ }
+
+ @Override
public void dumpGfxInfo(ParcelFileDescriptor pfd, String[] args) {
nDumpGraphicsInfo(pfd.getFileDescriptor());
WindowManagerGlobal.getInstance().dumpGfxInfo(pfd.getFileDescriptor(), args);
@@ -2335,23 +2453,23 @@
*
* @param hasSwappedOutPss determines whether to use dirtySwap or dirtySwapPss
*/
- private static void dumpHeap(ProtoOutputStream proto, long fieldId, String name,
+ private static void dumpMemoryInfo(ProtoOutputStream proto, long fieldId, String name,
int pss, int cleanPss, int sharedDirty, int privateDirty,
int sharedClean, int privateClean,
boolean hasSwappedOutPss, int dirtySwap, int dirtySwapPss) {
final long token = proto.start(fieldId);
- proto.write(MemInfoProto.NativeProcess.MemoryInfo.NAME, name);
- proto.write(MemInfoProto.NativeProcess.MemoryInfo.TOTAL_PSS_KB, pss);
- proto.write(MemInfoProto.NativeProcess.MemoryInfo.CLEAN_PSS_KB, cleanPss);
- proto.write(MemInfoProto.NativeProcess.MemoryInfo.SHARED_DIRTY_KB, sharedDirty);
- proto.write(MemInfoProto.NativeProcess.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty);
- proto.write(MemInfoProto.NativeProcess.MemoryInfo.SHARED_CLEAN_KB, sharedClean);
- proto.write(MemInfoProto.NativeProcess.MemoryInfo.PRIVATE_CLEAN_KB, privateClean);
+ proto.write(MemInfoProto.ProcessMemory.MemoryInfo.NAME, name);
+ proto.write(MemInfoProto.ProcessMemory.MemoryInfo.TOTAL_PSS_KB, pss);
+ proto.write(MemInfoProto.ProcessMemory.MemoryInfo.CLEAN_PSS_KB, cleanPss);
+ proto.write(MemInfoProto.ProcessMemory.MemoryInfo.SHARED_DIRTY_KB, sharedDirty);
+ proto.write(MemInfoProto.ProcessMemory.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty);
+ proto.write(MemInfoProto.ProcessMemory.MemoryInfo.SHARED_CLEAN_KB, sharedClean);
+ proto.write(MemInfoProto.ProcessMemory.MemoryInfo.PRIVATE_CLEAN_KB, privateClean);
if (hasSwappedOutPss) {
- proto.write(MemInfoProto.NativeProcess.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss);
+ proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss);
} else {
- proto.write(MemInfoProto.NativeProcess.MemoryInfo.DIRTY_SWAP_KB, dirtySwap);
+ proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_KB, dirtySwap);
}
proto.end(token);
@@ -2366,26 +2484,26 @@
long dalvikMax, long dalvikAllocated, long dalvikFree) {
if (!dumpSummaryOnly) {
- final long nhToken = proto.start(MemInfoProto.NativeProcess.NATIVE_HEAP);
- dumpHeap(proto, MemInfoProto.NativeProcess.HeapInfo.MEM_INFO, "Native Heap",
+ final long nhToken = proto.start(MemInfoProto.ProcessMemory.NATIVE_HEAP);
+ dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "Native Heap",
memInfo.nativePss, memInfo.nativeSwappablePss, memInfo.nativeSharedDirty,
memInfo.nativePrivateDirty, memInfo.nativeSharedClean,
memInfo.nativePrivateClean, memInfo.hasSwappedOutPss,
memInfo.nativeSwappedOut, memInfo.nativeSwappedOutPss);
- proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_SIZE_KB, nativeMax);
- proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_ALLOC_KB, nativeAllocated);
- proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_FREE_KB, nativeFree);
+ proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax);
+ proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated);
+ proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree);
proto.end(nhToken);
- final long dvToken = proto.start(MemInfoProto.NativeProcess.DALVIK_HEAP);
- dumpHeap(proto, MemInfoProto.NativeProcess.HeapInfo.MEM_INFO, "Dalvik Heap",
+ final long dvToken = proto.start(MemInfoProto.ProcessMemory.DALVIK_HEAP);
+ dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "Dalvik Heap",
memInfo.dalvikPss, memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty,
memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean,
memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss,
memInfo.dalvikSwappedOut, memInfo.dalvikSwappedOutPss);
- proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_SIZE_KB, dalvikMax);
- proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated);
- proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_FREE_KB, dalvikFree);
+ proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, dalvikMax);
+ proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated);
+ proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, dalvikFree);
proto.end(dvToken);
int otherPss = memInfo.otherPss;
@@ -2409,7 +2527,7 @@
if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
|| mySharedClean != 0 || myPrivateClean != 0
|| (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
- dumpHeap(proto, MemInfoProto.NativeProcess.OTHER_HEAPS,
+ dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.OTHER_HEAPS,
Debug.MemoryInfo.getOtherLabel(i),
myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
mySharedClean, myPrivateClean,
@@ -2426,21 +2544,21 @@
}
}
- dumpHeap(proto, MemInfoProto.NativeProcess.UNKNOWN_HEAP, "Unknown",
+ dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.UNKNOWN_HEAP, "Unknown",
otherPss, otherSwappablePss,
otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean,
memInfo.hasSwappedOutPss, otherSwappedOut, otherSwappedOutPss);
- final long tToken = proto.start(MemInfoProto.NativeProcess.TOTAL_HEAP);
- dumpHeap(proto, MemInfoProto.NativeProcess.HeapInfo.MEM_INFO, "TOTAL",
+ final long tToken = proto.start(MemInfoProto.ProcessMemory.TOTAL_HEAP);
+ dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "TOTAL",
memInfo.getTotalPss(), memInfo.getTotalSwappablePss(),
memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(),
memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(),
memInfo.hasSwappedOutPss, memInfo.getTotalSwappedOut(),
memInfo.getTotalSwappedOutPss());
- proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_SIZE_KB, nativeMax + dalvikMax);
- proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_ALLOC_KB,
+ proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax + dalvikMax);
+ proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB,
nativeAllocated + dalvikAllocated);
- proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_FREE_KB, nativeFree + dalvikFree);
+ proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree + dalvikFree);
proto.end(tToken);
if (dumpDalvik) {
@@ -2458,7 +2576,7 @@
if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
|| mySharedClean != 0 || myPrivateClean != 0
|| (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
- dumpHeap(proto, MemInfoProto.NativeProcess.DALVIK_DETAILS,
+ dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.DALVIK_DETAILS,
Debug.MemoryInfo.getOtherLabel(i),
myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
mySharedClean, myPrivateClean,
@@ -2468,24 +2586,24 @@
}
}
- final long asToken = proto.start(MemInfoProto.NativeProcess.APP_SUMMARY);
- proto.write(MemInfoProto.NativeProcess.AppSummary.JAVA_HEAP_PSS_KB,
+ final long asToken = proto.start(MemInfoProto.ProcessMemory.APP_SUMMARY);
+ proto.write(MemInfoProto.ProcessMemory.AppSummary.JAVA_HEAP_PSS_KB,
memInfo.getSummaryJavaHeap());
- proto.write(MemInfoProto.NativeProcess.AppSummary.NATIVE_HEAP_PSS_KB,
+ proto.write(MemInfoProto.ProcessMemory.AppSummary.NATIVE_HEAP_PSS_KB,
memInfo.getSummaryNativeHeap());
- proto.write(MemInfoProto.NativeProcess.AppSummary.CODE_PSS_KB, memInfo.getSummaryCode());
- proto.write(MemInfoProto.NativeProcess.AppSummary.STACK_PSS_KB, memInfo.getSummaryStack());
- proto.write(MemInfoProto.NativeProcess.AppSummary.GRAPHICS_PSS_KB,
+ proto.write(MemInfoProto.ProcessMemory.AppSummary.CODE_PSS_KB, memInfo.getSummaryCode());
+ proto.write(MemInfoProto.ProcessMemory.AppSummary.STACK_PSS_KB, memInfo.getSummaryStack());
+ proto.write(MemInfoProto.ProcessMemory.AppSummary.GRAPHICS_PSS_KB,
memInfo.getSummaryGraphics());
- proto.write(MemInfoProto.NativeProcess.AppSummary.PRIVATE_OTHER_PSS_KB,
+ proto.write(MemInfoProto.ProcessMemory.AppSummary.PRIVATE_OTHER_PSS_KB,
memInfo.getSummaryPrivateOther());
- proto.write(MemInfoProto.NativeProcess.AppSummary.SYSTEM_PSS_KB,
+ proto.write(MemInfoProto.ProcessMemory.AppSummary.SYSTEM_PSS_KB,
memInfo.getSummarySystem());
if (memInfo.hasSwappedOutPss) {
- proto.write(MemInfoProto.NativeProcess.AppSummary.TOTAL_SWAP_PSS,
+ proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
memInfo.getSummaryTotalSwapPss());
} else {
- proto.write(MemInfoProto.NativeProcess.AppSummary.TOTAL_SWAP_PSS,
+ proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
memInfo.getSummaryTotalSwap());
}
proto.end(asToken);
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
index a0fb6ee..3a355d9 100644
--- a/core/java/android/app/DialogFragment.java
+++ b/core/java/android/app/DialogFragment.java
@@ -137,7 +137,9 @@
* {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
* embed}
*
- * @deprecated Use {@link android.support.v4.app.DialogFragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.DialogFragment} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public class DialogFragment extends Fragment
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index a92684b..4ff07f2 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -257,7 +257,9 @@
* pressing back will pop it to return the user to whatever previous state
* the activity UI was in.
*
- * @deprecated Use {@link android.support.v4.app.Fragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.Fragment} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener {
diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java
index a1dd32f..536c866 100644
--- a/core/java/android/app/FragmentContainer.java
+++ b/core/java/android/app/FragmentContainer.java
@@ -25,7 +25,8 @@
/**
* Callbacks to a {@link Fragment}'s container.
*
- * @deprecated Use {@link android.support.v4.app.FragmentContainer}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentContainer}.
*/
@Deprecated
public abstract class FragmentContainer {
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index cbb58d4..40bc248 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -38,7 +38,8 @@
* It is the responsibility of the host to take care of the Fragment's lifecycle.
* The methods provided by {@link FragmentController} are for that purpose.
*
- * @deprecated Use {@link android.support.v4.app.FragmentController}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentController}
*/
@Deprecated
public class FragmentController {
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index 1edc68e..b48817b 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -38,7 +38,8 @@
* host fragments, implement {@link FragmentHostCallback}, overriding the methods
* applicable to the host.
*
- * @deprecated Use {@link android.support.v4.app.FragmentHostCallback}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentHostCallback}
*/
@Deprecated
public abstract class FragmentHostCallback<E> extends FragmentContainer {
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 12e60b8..708450f 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -75,7 +75,9 @@
* <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
* Fragments For All</a> for more details.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentManager} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public abstract class FragmentManager {
@@ -90,7 +92,8 @@
* the identifier as returned by {@link #getId} is the only thing that
* will be persisted across activity instances.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager.BackStackEntry}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a> {@link android.support.v4.app.FragmentManager.BackStackEntry}
*/
@Deprecated
public interface BackStackEntry {
@@ -136,7 +139,9 @@
/**
* Interface to watch for changes to the back stack.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a>
+ * {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
*/
@Deprecated
public interface OnBackStackChangedListener {
@@ -438,7 +443,9 @@
* Callback interface for listening to fragment state changes that happen
* within a given FragmentManager.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a>
+ * {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
*/
@Deprecated
public abstract static class FragmentLifecycleCallbacks {
diff --git a/core/java/android/app/FragmentManagerNonConfig.java b/core/java/android/app/FragmentManagerNonConfig.java
index beb1a15..326438a 100644
--- a/core/java/android/app/FragmentManagerNonConfig.java
+++ b/core/java/android/app/FragmentManagerNonConfig.java
@@ -28,7 +28,8 @@
* {@link FragmentController#retainNonConfig()} and
* {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
*
- * @deprecated Use {@link android.support.v4.app.FragmentManagerNonConfig}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentManagerNonConfig}
*/
@Deprecated
public class FragmentManagerNonConfig {
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 1103649..713a559 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -22,7 +22,8 @@
* guide.</p>
* </div>
*
- * @deprecated Use {@link android.support.v4.app.FragmentTransaction}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentTransaction}
*/
@Deprecated
public abstract class FragmentTransaction {
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index b25d778..893a41c 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -110,6 +110,9 @@
void dumpMemInfo(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, boolean checkin,
boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
in String[] args);
+ void dumpMemInfoProto(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem,
+ boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
+ in String[] args);
void dumpGfxInfo(in ParcelFileDescriptor fd, in String[] args);
void dumpProvider(in ParcelFileDescriptor fd, IBinder servicetoken,
in String[] args);
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 49d58eb..80782e3 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -155,4 +155,9 @@
* Unregister a callback that was receiving color updates
*/
void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId);
+
+ /**
+ * Called from SystemUI when it shows the AoD UI.
+ */
+ void setInAmbientMode(boolean inAmbienMode);
}
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 90b77b3..7790f70 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -145,7 +145,9 @@
* @see #setListAdapter
* @see android.widget.ListView
*
- * @deprecated Use {@link android.support.v4.app.ListFragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.ListFragment} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public class ListFragment extends Fragment {
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 7969684..86d0fd62 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -55,14 +55,16 @@
* <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
* </div>
*
- * @deprecated Use {@link android.support.v4.app.LoaderManager}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.LoaderManager}
*/
@Deprecated
public abstract class LoaderManager {
/**
* Callback interface for a client to interact with the manager.
*
- * @deprecated Use {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a> {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
*/
@Deprecated
public interface LoaderCallbacks<D> {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 705f9a0..85c3be8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1090,6 +1090,12 @@
public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
/**
+ * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
+ * represents a group conversation.
+ */
+ public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
+
+ /**
* {@link #extras} key: whether the notification should be colorized as
* supplied to {@link Builder#setColorized(boolean)}}.
*/
@@ -5960,9 +5966,10 @@
public static final int MAXIMUM_RETAINED_MESSAGES = 25;
CharSequence mUserDisplayName;
- CharSequence mConversationTitle;
+ @Nullable CharSequence mConversationTitle;
List<Message> mMessages = new ArrayList<>();
List<Message> mHistoricMessages = new ArrayList<>();
+ boolean mIsGroupConversation;
MessagingStyle() {
}
@@ -5985,20 +5992,20 @@
}
/**
- * Sets the title to be displayed on this conversation. This should only be used for
- * group messaging and left unset for one-on-one conversations.
- * @param conversationTitle
+ * Sets the title to be displayed on this conversation. May be set to {@code null}.
+ *
+ * @param conversationTitle A name for the conversation, or {@code null}
* @return this object for method chaining.
*/
- public MessagingStyle setConversationTitle(CharSequence conversationTitle) {
+ public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
mConversationTitle = conversationTitle;
return this;
}
/**
- * Return the title to be displayed on this conversation. Can be <code>null</code> and
- * should be for one-on-one conversations
+ * Return the title to be displayed on this conversation. May return {@code null}.
*/
+ @Nullable
public CharSequence getConversationTitle() {
return mConversationTitle;
}
@@ -6075,6 +6082,24 @@
}
/**
+ * Sets whether this conversation notification represents a group.
+ * @param isGroupConversation {@code true} if the conversation represents a group,
+ * {@code false} otherwise.
+ * @return this object for method chaining
+ */
+ public MessagingStyle setGroupConversation(boolean isGroupConversation) {
+ mIsGroupConversation = isGroupConversation;
+ return this;
+ }
+
+ /**
+ * Returns {@code true} if this notification represents a group conversation.
+ */
+ public boolean isGroupConversation() {
+ return mIsGroupConversation;
+ }
+
+ /**
* @hide
*/
@Override
@@ -6094,6 +6119,7 @@
}
fixTitleAndTextExtras(extras);
+ extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
}
private void fixTitleAndTextExtras(Bundle extras) {
@@ -6136,6 +6162,7 @@
mMessages = Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
+ mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
}
/**
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index 9d40381f..35a1789 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -16,18 +16,15 @@
package android.app;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources.NotFoundException;
import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
@@ -39,6 +36,9 @@
import android.util.Printer;
import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
/**
@@ -76,6 +76,7 @@
final int mContextUriResource;
final int mContextDescriptionResource;
final boolean mShowMetadataInPreview;
+ final boolean mSupportsAmbientMode;
/**
* Constructor.
@@ -89,15 +90,7 @@
mService = service;
ServiceInfo si = service.serviceInfo;
- PackageManager pm = context.getPackageManager();
- String settingsActivityComponent = null;
- int thumbnailRes = -1;
- int authorRes = -1;
- int descriptionRes = -1;
- int contextUriRes = -1;
- int contextDescriptionRes = -1;
- boolean showMetadataInPreview = false;
-
+ final PackageManager pm = context.getPackageManager();
XmlResourceParser parser = null;
try {
parser = si.loadXmlMetaData(pm, WallpaperService.SERVICE_META_DATA);
@@ -123,27 +116,30 @@
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.Wallpaper);
- settingsActivityComponent = sa.getString(
+ mSettingsActivityName = sa.getString(
com.android.internal.R.styleable.Wallpaper_settingsActivity);
-
- thumbnailRes = sa.getResourceId(
+
+ mThumbnailResource = sa.getResourceId(
com.android.internal.R.styleable.Wallpaper_thumbnail,
-1);
- authorRes = sa.getResourceId(
+ mAuthorResource = sa.getResourceId(
com.android.internal.R.styleable.Wallpaper_author,
-1);
- descriptionRes = sa.getResourceId(
+ mDescriptionResource = sa.getResourceId(
com.android.internal.R.styleable.Wallpaper_description,
-1);
- contextUriRes = sa.getResourceId(
+ mContextUriResource = sa.getResourceId(
com.android.internal.R.styleable.Wallpaper_contextUri,
-1);
- contextDescriptionRes = sa.getResourceId(
+ mContextDescriptionResource = sa.getResourceId(
com.android.internal.R.styleable.Wallpaper_contextDescription,
-1);
- showMetadataInPreview = sa.getBoolean(
+ mShowMetadataInPreview = sa.getBoolean(
com.android.internal.R.styleable.Wallpaper_showMetadataInPreview,
false);
+ mSupportsAmbientMode = sa.getBoolean(
+ com.android.internal.R.styleable.Wallpaper_supportsAmbientMode,
+ false);
sa.recycle();
} catch (NameNotFoundException e) {
@@ -152,14 +148,6 @@
} finally {
if (parser != null) parser.close();
}
-
- mSettingsActivityName = settingsActivityComponent;
- mThumbnailResource = thumbnailRes;
- mAuthorResource = authorRes;
- mDescriptionResource = descriptionRes;
- mContextUriResource = contextUriRes;
- mContextDescriptionResource = contextDescriptionRes;
- mShowMetadataInPreview = showMetadataInPreview;
}
WallpaperInfo(Parcel source) {
@@ -170,6 +158,7 @@
mContextUriResource = source.readInt();
mContextDescriptionResource = source.readInt();
mShowMetadataInPreview = source.readInt() != 0;
+ mSupportsAmbientMode = source.readInt() != 0;
mService = ResolveInfo.CREATOR.createFromParcel(source);
}
@@ -326,6 +315,16 @@
}
/**
+ * Returns whether a wallpaper was optimized or not for ambient mode.
+ *
+ * @return {@code true} if wallpaper can draw in ambient mode.
+ * @hide
+ */
+ public boolean getSupportsAmbientMode() {
+ return mSupportsAmbientMode;
+ }
+
+ /**
* Return the class name of an activity that provides a settings UI for
* the wallpaper. You can launch this activity be starting it with
* an {@link android.content.Intent} whose action is MAIN and with an
@@ -366,6 +365,7 @@
dest.writeInt(mContextUriResource);
dest.writeInt(mContextDescriptionResource);
dest.writeInt(mShowMetadataInPreview ? 1 : 0);
+ dest.writeInt(mSupportsAmbientMode ? 1 : 0);
mService.writeToParcel(dest, flags);
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 89df421..9816297 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2635,10 +2635,121 @@
}
/**
+ * The maximum number of characters allowed in the password blacklist.
+ */
+ private static final int PASSWORD_BLACKLIST_CHARACTER_LIMIT = 128 * 1000;
+
+ /**
+ * Throws an exception if the password blacklist is too large.
+ *
+ * @hide
+ */
+ public static void enforcePasswordBlacklistSize(List<String> blacklist) {
+ if (blacklist == null) {
+ return;
+ }
+ long characterCount = 0;
+ for (final String item : blacklist) {
+ characterCount += item.length();
+ }
+ if (characterCount > PASSWORD_BLACKLIST_CHARACTER_LIMIT) {
+ throw new IllegalArgumentException("128 thousand blacklist character limit exceeded by "
+ + (characterCount - PASSWORD_BLACKLIST_CHARACTER_LIMIT) + " characters");
+ }
+ }
+
+ /**
+ * Called by an application that is administering the device to blacklist passwords.
+ * <p>
+ * Any blacklisted password or PIN is prevented from being enrolled by the user or the admin.
+ * Note that the match against the blacklist is case insensitive. The blacklist applies for all
+ * password qualities requested by {@link #setPasswordQuality} however it is not taken into
+ * consideration by {@link #isActivePasswordSufficient}.
+ * <p>
+ * The blacklist can be cleared by passing {@code null} or an empty list. The blacklist is
+ * given a name that is used to track which blacklist is currently set by calling {@link
+ * #getPasswordBlacklistName}. If the blacklist is being cleared, the name is ignored and {@link
+ * #getPasswordBlacklistName} will return {@code null}. The name can only be {@code null} when
+ * the blacklist is being cleared.
+ * <p>
+ * The blacklist is limited to a total of 128 thousand characters rather than limiting to a
+ * number of entries.
+ * <p>
+ * This method can be called on the {@link DevicePolicyManager} instance returned by
+ * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
+ * profile.
+ *
+ * @param admin the {@link DeviceAdminReceiver} this request is associated with
+ * @param name name to associate with the blacklist
+ * @param blacklist list of passwords to blacklist or {@code null} to clear the blacklist
+ * @return whether the new blacklist was successfully installed
+ * @throws SecurityException if {@code admin} is not a device or profile owner
+ * @throws IllegalArgumentException if the blacklist surpasses the character limit
+ * @throws NullPointerException if {@code name} is {@code null} when setting a non-empty list
+ *
+ * @see #getPasswordBlacklistName
+ * @see #isActivePasswordSufficient
+ * @see #resetPasswordWithToken
+ *
+ * TODO(63578054): unhide for P
+ * @hide
+ */
+ public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name,
+ @Nullable List<String> blacklist) {
+ enforcePasswordBlacklistSize(blacklist);
+
+ try {
+ return mService.setPasswordBlacklist(admin, name, blacklist, mParentInstance);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the name of the password blacklist set by the given admin.
+ *
+ * @param admin the {@link DeviceAdminReceiver} this request is associated with
+ * @return the name of the blacklist or {@code null} if no blacklist is set
+ *
+ * @see #setPasswordBlacklist
+ *
+ * TODO(63578054): unhide for P
+ * @hide
+ */
+ public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) {
+ try {
+ return mService.getPasswordBlacklistName(admin, myUserId(), mParentInstance);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Test if a given password is blacklisted.
+ *
+ * @param userId the user to valiate for
+ * @param password the password to check against the blacklist
+ * @return whether the password is blacklisted
+ *
+ * @see #setPasswordBlacklist
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.TEST_BLACKLISTED_PASSWORD)
+ public boolean isPasswordBlacklisted(@UserIdInt int userId, @NonNull String password) {
+ try {
+ return mService.isPasswordBlacklisted(userId, password);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Determine whether the current password the user has set is sufficient to meet the policy
* requirements (e.g. quality, minimum length) that have been requested by the admins of this
* user and its participating profiles. Restrictions on profiles that have a separate challenge
- * are not taken into account. The user must be unlocked in order to perform the check.
+ * are not taken into account. The user must be unlocked in order to perform the check. The
+ * password blacklist is not considered when checking sufficiency.
* <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
@@ -2676,6 +2787,7 @@
* @see UserManager#DISALLOW_UNIFIED_PASSWORD
*/
public boolean isUsingUnifiedPassword(@NonNull ComponentName admin) {
+ throwIfParentInstance("isUsingUnifiedPassword");
if (mService != null) {
try {
return mService.isUsingUnifiedPassword(admin);
@@ -4062,6 +4174,52 @@
return null;
}
+
+ /**
+ * Called by a device or profile owner, or delegated certificate installer, to associate
+ * certificates with a key pair that was generated using {@link #generateKeyPair}, and
+ * set whether the key is available for the user to choose in the certificate selection
+ * prompt.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if calling from a delegated certificate installer.
+ * @param alias The private key alias under which to install the certificate. The {@code alias}
+ * should denote an existing private key. If a certificate with that alias already
+ * exists, it will be overwritten.
+ * @param certs The certificate chain to install. The chain should start with the leaf
+ * certificate and include the chain of trust in order. This will be returned by
+ * {@link android.security.KeyChain#getCertificateChain}.
+ * @param isUserSelectable {@code true} to indicate that a user can select this key via the
+ * certificate selection prompt, {@code false} to indicate that this key can only be
+ * granted access by implementing
+ * {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+ * @return {@code true} if the provided {@code alias} exists and the certificates has been
+ * successfully associated with it, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+ * owner, or {@code admin} is null but the calling application is not a delegated
+ * certificate installer.
+ */
+ public boolean setKeyPairCertificate(@Nullable ComponentName admin,
+ @NonNull String alias, @NonNull List<Certificate> certs, boolean isUserSelectable) {
+ throwIfParentInstance("setKeyPairCertificate");
+ try {
+ final byte[] pemCert = Credentials.convertToPem(certs.get(0));
+ byte[] pemChain = null;
+ if (certs.size() > 1) {
+ pemChain = Credentials.convertToPem(
+ certs.subList(1, certs.size()).toArray(new Certificate[0]));
+ }
+ return mService.setKeyPairCertificate(admin, mContext.getPackageName(), alias, pemCert,
+ pemChain, isUserSelectable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (CertificateException | IOException e) {
+ Log.w(TAG, "Could not pem-encode certificate", e);
+ }
+ return false;
+ }
+
+
/**
* @return the alias of a given CA certificate in the certificate store, or {@code null} if it
* doesn't exist.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9128208..5b02c22 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -78,6 +78,10 @@
long getPasswordExpiration(in ComponentName who, int userHandle, boolean parent);
+ boolean setPasswordBlacklist(in ComponentName who, String name, in List<String> blacklist, boolean parent);
+ String getPasswordBlacklistName(in ComponentName who, int userId, boolean parent);
+ boolean isPasswordBlacklisted(int userId, String password);
+
boolean isActivePasswordSufficient(int userHandle, boolean parent);
boolean isProfileActivePasswordSufficientForParent(int userHandle);
boolean isUsingUnifiedPassword(in ComponentName admin);
@@ -171,6 +175,8 @@
boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm,
in ParcelableKeyGenParameterSpec keySpec,
out KeymasterCertificateChain attestationChain);
+ boolean setKeyPairCertificate(in ComponentName who, in String callerPackage, in String alias,
+ in byte[] certBuffer, in byte[] certChainBuffer, boolean isUserSelectable);
void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
void setDelegatedScopes(in ComponentName who, in String delegatePackage, in List<String> scopes);
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index ccb77c67..5c7f674 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -63,7 +63,7 @@
HINT_ACTIONS,
HINT_SELECTED,
HINT_NO_TINT,
- HINT_HIDDEN,
+ HINT_SHORTCUT,
HINT_TOGGLE,
HINT_HORIZONTAL,
HINT_PARTIAL,
@@ -118,12 +118,10 @@
*/
public static final String HINT_NO_TINT = "no_tint";
/**
- * Hint to indicate that this content should not be shown in larger renderings
- * of Slices. This content may be used to populate the shortcut/icon
- * format of the slice.
- * @hide
+ * Hint to indicate that this content should only be displayed if the slice is presented
+ * as a shortcut.
*/
- public static final String HINT_HIDDEN = "hidden";
+ public static final String HINT_SHORTCUT = "shortcut";
/**
* Hint indicating this content should be shown instead of the normal content when the slice
* is in small format.
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index f38e462..2fab305 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -85,7 +85,7 @@
*
* @see BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)
* @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
- * @see BluetoothHidDeviceCallback#onIntrData(BluetoothDevice, byte, byte[])
+ * @see BluetoothHidDeviceCallback#onInterruptData(BluetoothDevice, byte, byte[])
*/
public static final byte REPORT_TYPE_INPUT = (byte) 1;
public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
@@ -155,8 +155,8 @@
}
@Override
- public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
- mCallback.onIntrData(device, reportId, data);
+ public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+ mCallback.onInterruptData(device, reportId, data);
}
@Override
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
index bd19955..e71b00f 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -106,8 +106,8 @@
* @param reportId Report Id.
* @param data Report data.
*/
- public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
- Log.d(TAG, "onIntrData: device=" + device + " reportId=" + reportId);
+ public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+ Log.d(TAG, "onInterruptData: device=" + device + " reportId=" + reportId);
}
/**
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 6e9f09c..c44e356 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -50,7 +50,8 @@
*
* @param <D> the data type to be loaded.
*
- * @deprecated Use {@link android.support.v4.content.AsyncTaskLoader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.AsyncTaskLoader}
*/
@Deprecated
public abstract class AsyncTaskLoader<D> extends Loader<D> {
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 7f24c51..5a08636 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -39,7 +39,8 @@
* {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
* and {@link #setProjection(String[])}.
*
- * @deprecated Use {@link android.support.v4.content.CursorLoader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.CursorLoader}
*/
@Deprecated
public class CursorLoader extends AsyncTaskLoader<Cursor> {
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index 80f9a14..b0555d4 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -49,7 +49,8 @@
*
* @param <D> The result returned when the load is complete
*
- * @deprecated Use {@link android.support.v4.content.Loader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.Loader}
*/
@Deprecated
public class Loader<D> {
@@ -561,4 +562,4 @@
writer.print(" mReset="); writer.println(mReset);
}
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 8623524..30222b7 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -39,9 +39,9 @@
* The ShortcutManager performs operations on an app's set of <em>shortcuts</em>. The
* {@link ShortcutInfo} class contains information about each of the shortcuts themselves.
*
- * <p>An app's shortcuts represent specific tasks and actions that users can take within your app.
- * When a user selects a shortcut in the currently-active launcher, your app opens an activity other
- * than the app's starting activity, provided that the currently-active launcher supports app
+ * <p>An app's shortcuts represent specific tasks and actions that users can perform within your
+ * app. When a user selects a shortcut in the currently-active launcher, your app opens an activity
+ * other than the app's starting activity, provided that the currently-active launcher supports app
* shortcuts.</p>
*
* <p>The types of shortcuts that you create for your app depend on the app's key use cases. For
@@ -94,13 +94,15 @@
*
* <p>When the launcher displays an app's shortcuts, they should appear in the following order:
*
- * <ul>
- * <li>Static shortcuts—shortcuts whose {@link ShortcutInfo#isDeclaredInManifest()} method
- * returns {@code true}—followed by dynamic shortcuts—shortcuts whose
- * {@link ShortcutInfo#isDynamic()} method returns {@code true}.
- * <li>Within each shortcut type (static and dynamic), shortcuts are sorted in order of increasing
- * rank according to {@link ShortcutInfo#getRank()}.
- * </ul>
+ * <ol>
+ * <li><b>Static shortcuts:</b> Shortcuts whose {@link ShortcutInfo#isDeclaredInManifest()} method
+ * returns {@code true}.</li>
+ * <li><b>Dynamic shortcuts:</b> Shortcuts whose {@link ShortcutInfo#isDynamic()} method returns
+ * {@code true}.</li>
+ * </ol>
+ *
+ * <p>Within each shortcut type (static and dynamic), shortcuts are sorted in order of increasing
+ * rank according to {@link ShortcutInfo#getRank()}.</p>
*
* <h4>Shortcut ranks</h4>
*
@@ -118,8 +120,8 @@
* <h3>Options for static shortcuts</h3>
*
* The following list includes descriptions for the different attributes within a static shortcut.
- * You must provide a value for {@code android:shortcutId}, {@code android:shortcutShortLabel}; all
- * other values are optional.
+ * You must provide a value for {@code android:shortcutId} and {@code android:shortcutShortLabel};
+ * all other values are optional.
*
* <dl>
* <dt>{@code android:shortcutId}</dt>
@@ -134,7 +136,7 @@
* <p>The default value is {@code true}. If you set it to {@code false}, you should also set
* {@code android:shortcutDisabledMessage} to a message that explains why you've disabled the
* shortcut. If you don't think you need to provide such a message, it's easiest to just remove
- * the shortcut from the XML file entirely, rather than changing the values of its
+ * the shortcut from the XML file entirely, rather than changing the values of the shortcut's
* {@code android:enabled} and {@code android:shortcutDisabledMessage} attributes.
* </dd>
*
@@ -152,7 +154,7 @@
* <dd><p>A concise phrase that describes the shortcut's purpose. For more information, see
* {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}.</p>
* <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as
- * <code>@string/shortcut_label</code>.</p>
+ * <code>@string/shortcut_short_label</code>.</p>
* </dd>
*
* <dt>{@code android:shortcutLongLabel}</dt>
@@ -165,8 +167,8 @@
*
* <dt>{@code android:shortcutDisabledMessage}</dt>
* <dd><p>The message that appears in a supported launcher when the user attempts to launch a
- * disabled shortcut. This attribute's value has no effect if {@code android:enabled} is
- * {@code true}. The message should explain to the user why the shortcut is now disabled.</p>
+ * disabled shortcut. The message should explain to the user why the shortcut is now disabled.
+ * This attribute's value has no effect if {@code android:enabled} is {@code true}.</p>
* <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as
* <code>@string/shortcut_disabled_message</code>.</p>
* </dd>
@@ -175,20 +177,20 @@
* <h3>Inner elements that define static shortcuts</h3>
*
* <p>The XML file that lists an app's static shortcuts supports the following elements inside each
- * {@code <shortcut>} element. You must include an {@code intent} inner element for each
+ * {@code <shortcut>} element. You must include an {@code intent} inner element for each
* static shortcut that you define.</p>
*
* <dl>
* <dt>{@code intent}</dt>
* <dd><p>The action that the system launches when the user selects the shortcut. This intent must
* provide a value for the {@code android:action} attribute.</p>
- * <p>You can provide multiple intents for a single shortcut so that the last defined activity is
- * launched with the other activities in the
+ * <p>You can provide multiple intents for a single shortcut. If you do so, the last defined
+ * activity is launched, and the other activities are placed in the
* <a href="/guide/components/tasks-and-back-stack.html">back stack</a>. See
* <a href="/guide/topics/ui/shortcuts.html#static">Using Static Shortcuts</a> and the
* {@link android.app.TaskStackBuilder} class reference for details.</p>
* <p class="note"><b>Note:</b> This {@code intent} element cannot include string resources.</p>
- * <p>For more information, see
+ * <p>To learn more about how to configure intents, see
* <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a>.</p>
* </dd>
*
diff --git a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java
index e02e68d..5bf3a7c 100644
--- a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java
+++ b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java
@@ -37,7 +37,7 @@
private static final String TAG = "SQLiteCompatibilityWalFlags";
- private static volatile boolean sInitialized = true; // Temporarily disable flags
+ private static volatile boolean sInitialized;
private static volatile boolean sFlagsSet;
private static volatile boolean sCompatibilityWalSupported;
private static volatile String sWALSyncMode;
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 7409671..a85b5f7 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -486,6 +486,7 @@
this.mConfiguredSize = other.mConfiguredSize;
this.mConfiguredGenerationId = other.mConfiguredGenerationId;
this.mIsDeferredConfig = other.mIsDeferredConfig;
+ this.mIsShared = other.mIsShared;
}
/**
@@ -498,6 +499,7 @@
int width = source.readInt();
int height = source.readInt();
boolean isDeferred = source.readInt() == 1;
+ boolean isShared = source.readInt() == 1;
ArrayList<Surface> surfaces = new ArrayList<Surface>();
source.readTypedList(surfaces, Surface.CREATOR);
@@ -508,6 +510,7 @@
mSurfaces = surfaces;
mConfiguredSize = new Size(width, height);
mIsDeferredConfig = isDeferred;
+ mIsShared = isShared;
mSurfaces = surfaces;
if (mSurfaces.size() > 0) {
mSurfaceType = SURFACE_TYPE_UNKNOWN;
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index e1137aa..c2b2800 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -26,7 +26,7 @@
* @hide
*/
@SystemApi
-public class ContextHubInfo {
+public class ContextHubInfo implements Parcelable {
private int mId;
private String mName;
private String mVendor;
@@ -262,7 +262,7 @@
@Override
public String toString() {
String retVal = "";
- retVal += "Id : " + mId;
+ retVal += "ID/handle : " + mId;
retVal += ", Name : " + mName;
retVal += "\n\tVendor : " + mVendor;
retVal += ", Toolchain : " + mToolchain;
@@ -275,8 +275,6 @@
retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes";
- retVal += "\n\tSupported sensors : " + Arrays.toString(mSupportedSensors);
- retVal += "\n\tMemory Regions : " + Arrays.toString(mMemoryRegions);
return retVal;
}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 5b89f54..6da6fb7 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -258,9 +258,9 @@
}
/**
- * Returns the list of context hubs in the system.
+ * Returns the list of ContextHubInfo objects describing the available Context Hubs.
*
- * @return the list of context hub informations
+ * @return the list of ContextHubInfo objects
*
* @see ContextHubInfo
*
@@ -268,7 +268,11 @@
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public List<ContextHubInfo> getContextHubs() {
- throw new UnsupportedOperationException("TODO: Implement this");
+ try {
+ return mService.getContextHubs();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index db5bd36..233e857 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -43,23 +43,26 @@
ContextHubInfo getContextHubInfo(int contextHubHandle);
// Loads a nanoapp at the specified hub (old API)
- int loadNanoApp(int hubHandle, in NanoApp app);
+ int loadNanoApp(int contextHubHandle, in NanoApp nanoApp);
// Unloads a nanoapp given its instance ID (old API)
- int unloadNanoApp(int nanoAppInstanceHandle);
+ int unloadNanoApp(int nanoAppHandle);
// Gets the NanoAppInstanceInfo of a nanoapp give its instance ID
- NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppInstanceHandle);
+ NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle);
// Finds all nanoApp instances matching some filter
- int[] findNanoAppOnHub(int hubHandle, in NanoAppFilter filter);
+ int[] findNanoAppOnHub(int contextHubHandle, in NanoAppFilter filter);
// Sends a message to a nanoApp
- int sendMessage(int hubHandle, int nanoAppHandle, in ContextHubMessage msg);
+ int sendMessage(int contextHubHandle, int nanoAppHandle, in ContextHubMessage msg);
// Creates a client to send and receive messages
IContextHubClient createClient(in IContextHubClientCallback client, int contextHubId);
+ // Returns a list of ContextHub objects of available hubs
+ List<ContextHubInfo> getContextHubs();
+
// Loads a nanoapp at the specified hub (new API)
void loadNanoAppOnHub(
int contextHubId, in IContextHubTransactionCallback transactionCallback,
diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java
index b7e6b66..f73fd87 100644
--- a/core/java/android/hardware/location/NanoAppInstanceInfo.java
+++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java
@@ -27,15 +27,13 @@
* Describes an instance of a nanoapp, used by the internal state manged by ContextHubService.
*
* TODO(b/69270990) Remove this class once the old API is deprecated.
- * TODO(b/70624255) Clean up toString() by removing unnecessary fields
*
* @hide
*/
@SystemApi
public class NanoAppInstanceInfo {
- private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown";
- private String mPublisher = PRE_LOADED_GENERIC_UNKNOWN;
- private String mName = PRE_LOADED_GENERIC_UNKNOWN;
+ private String mPublisher = "Unknown";
+ private String mName = "Unknown";
private int mHandle;
private long mAppId;
@@ -227,9 +225,7 @@
public String toString() {
String retVal = "handle : " + mHandle;
retVal += ", Id : 0x" + Long.toHexString(mAppId);
- retVal += ", Version : " + mAppVersion;
- retVal += ", Name : " + mName;
- retVal += ", Publisher : " + mPublisher;
+ retVal += ", Version : 0x" + Integer.toHexString(mAppVersion);
return retVal;
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 8071e8b..11d338d 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1794,7 +1794,7 @@
ITelephony it = ITelephony.Stub.asInterface(b);
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
Log.d("ConnectivityManager", "getMobileDataEnabled()+ subId=" + subId);
- boolean retVal = it.getDataEnabled(subId);
+ boolean retVal = it.isUserDataEnabled(subId);
Log.d("ConnectivityManager", "getMobileDataEnabled()- subId=" + subId
+ " retVal=" + retVal);
return retVal;
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 954e59c..d701550 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -19,6 +19,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.DownloadManager;
import android.app.backup.BackupManager;
import android.app.usage.NetworkStatsManager;
@@ -154,6 +155,8 @@
private static Object sProfilingLock = new Object();
+ private static final String LOOPBACK_IFACE = "lo";
+
/**
* Set active tag to use when accounting {@link Socket} traffic originating
* from the current thread. Only one active tag per thread is supported.
@@ -542,6 +545,30 @@
return nativeGetIfaceStat(iface, TYPE_RX_BYTES);
}
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackTxPackets() {
+ return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_PACKETS);
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackRxPackets() {
+ return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_PACKETS);
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackTxBytes() {
+ return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_BYTES);
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackRxBytes() {
+ return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_BYTES);
+ }
+
/**
* Return number of packets transmitted since device boot. Counts packets
* across all network interfaces, and always increases monotonically since
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2e9eeb1..9513b9b 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -658,32 +658,40 @@
*/
public static final int PROCESS_STATE_FOREGROUND_SERVICE = 1;
/**
- * Time this uid has any process that is top while the device is sleeping, but none
- * in the "foreground service" or better state.
- */
- public static final int PROCESS_STATE_TOP_SLEEPING = 2;
- /**
* Time this uid has any process in an active foreground state, but none in the
* "top sleeping" or better state.
*/
- public static final int PROCESS_STATE_FOREGROUND = 3;
+ public static final int PROCESS_STATE_FOREGROUND = 2;
/**
* Time this uid has any process in an active background state, but none in the
* "foreground" or better state.
*/
- public static final int PROCESS_STATE_BACKGROUND = 4;
+ public static final int PROCESS_STATE_BACKGROUND = 3;
+ /**
+ * Time this uid has any process that is top while the device is sleeping, but not
+ * active for any other reason. We kind-of consider it a kind of cached process
+ * for execution restrictions.
+ */
+ public static final int PROCESS_STATE_TOP_SLEEPING = 4;
+ /**
+ * Time this uid has any process that is in the background but it has an activity
+ * marked as "can't save state". This is essentially a cached process, though the
+ * system will try much harder than normal to avoid killing it.
+ */
+ public static final int PROCESS_STATE_HEAVY_WEIGHT = 5;
/**
* Time this uid has any processes that are sitting around cached, not in one of the
* other active states.
*/
- public static final int PROCESS_STATE_CACHED = 5;
+ public static final int PROCESS_STATE_CACHED = 6;
/**
* Total number of process states we track.
*/
- public static final int NUM_PROCESS_STATE = 6;
+ public static final int NUM_PROCESS_STATE = 7;
static final String[] PROCESS_STATE_NAMES = {
- "Top", "Fg Service", "Top Sleeping", "Foreground", "Background", "Cached"
+ "Top", "Fg Service", "Foreground", "Background", "Top Sleeping", "Heavy Weight",
+ "Cached"
};
public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 2acf36f..848ab88 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1136,7 +1136,7 @@
int intervalUs) {
VMDebug.startMethodTracing(fixTracePath(tracePath), bufferSize, 0, true, intervalUs);
}
-
+
/**
* Formats name of trace log file for method tracing.
*/
@@ -1706,11 +1706,11 @@
* Retrieves information about this processes memory usages. This information is broken down by
* how much is in use by dalvik, the native heap, and everything else.
*
- * <p><b>Note:</b> this method directly retrieves memory information for the give process
+ * <p><b>Note:</b> this method directly retrieves memory information for the given process
* from low-level data available to it. It may not be able to retrieve information about
* some protected allocations, such as graphics. If you want to be sure you can see
- * all information about allocations by the process, use instead
- * {@link android.app.ActivityManager#getProcessMemoryInfo(int[])}.</p>
+ * all information about allocations by the process, use
+ * {@link android.app.ActivityManager#getProcessMemoryInfo(int[])} instead.</p>
*/
public static native void getMemoryInfo(MemoryInfo memoryInfo);
diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl
index 1a76648..b67b99f 100644
--- a/core/java/android/os/IIncidentManager.aidl
+++ b/core/java/android/os/IIncidentManager.aidl
@@ -16,7 +16,6 @@
package android.os;
-import android.os.IIncidentReportCompletedListener;
import android.os.IIncidentReportStatusListener;
import android.os.IncidentReportArgs;
diff --git a/core/java/android/os/IIncidentReportCompletedListener.aidl b/core/java/android/os/IIncidentReportCompletedListener.aidl
deleted file mode 100644
index 2d66bf6..0000000
--- a/core/java/android/os/IIncidentReportCompletedListener.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-/**
- * Listener for incident report status
- *
- * {@hide}
- */
-oneway interface IIncidentReportCompletedListener {
- /**
- * Called when there has been an incident report.
- *
- * The system service implementing this method should delete or move the file
- * after it is finished with it.
- */
- void onIncidentReport(String filename);
-}
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index b9b9a18..bbb8a7b 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -334,6 +334,23 @@
}
/**
+ * Performs {@code action} for each cookie associated with a callback, calling
+ * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping
+ *
+ * @hide
+ */
+ public <C> void broadcastForEachCookie(Consumer<C> action) {
+ int itemCount = beginBroadcast();
+ try {
+ for (int i = 0; i < itemCount; i++) {
+ action.accept((C) getBroadcastCookie(i));
+ }
+ } finally {
+ finishBroadcast();
+ }
+ }
+
+ /**
* Returns the number of registered callbacks. Note that the number of registered
* callbacks may differ from the value returned by {@link #beginBroadcast()} since
* the former returns the number of callbacks registered at the time of the call
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index ecec448..8632aad 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,10 +1,13 @@
package android.os;
+import android.annotation.Nullable;
import android.os.WorkSourceProto;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Objects;
/**
* Describes the source of some work that may be done by someone else.
@@ -19,6 +22,8 @@
int[] mUids;
String[] mNames;
+ private ArrayList<WorkChain> mChains;
+
/**
* Internal statics to avoid object allocations in some operations.
* The WorkSource object itself is not thread safe, but we need to
@@ -39,6 +44,7 @@
*/
public WorkSource() {
mNum = 0;
+ mChains = null;
}
/**
@@ -48,6 +54,7 @@
public WorkSource(WorkSource orig) {
if (orig == null) {
mNum = 0;
+ mChains = null;
return;
}
mNum = orig.mNum;
@@ -58,6 +65,16 @@
mUids = null;
mNames = null;
}
+
+ if (orig.mChains != null) {
+ // Make a copy of all WorkChains that exist on |orig| since they are mutable.
+ mChains = new ArrayList<>(orig.mChains.size());
+ for (WorkChain chain : orig.mChains) {
+ mChains.add(new WorkChain(chain));
+ }
+ } else {
+ mChains = null;
+ }
}
/** @hide */
@@ -65,6 +82,7 @@
mNum = 1;
mUids = new int[] { uid, 0 };
mNames = null;
+ mChains = null;
}
/** @hide */
@@ -75,12 +93,21 @@
mNum = 1;
mUids = new int[] { uid, 0 };
mNames = new String[] { name, null };
+ mChains = null;
}
WorkSource(Parcel in) {
mNum = in.readInt();
mUids = in.createIntArray();
mNames = in.createStringArray();
+
+ int numChains = in.readInt();
+ if (numChains > 0) {
+ mChains = new ArrayList<>(numChains);
+ in.readParcelableList(mChains, WorkChain.class.getClassLoader());
+ } else {
+ mChains = null;
+ }
}
/** @hide */
@@ -99,7 +126,8 @@
}
/**
- * Clear names from this WorkSource. Uids are left intact.
+ * Clear names from this WorkSource. Uids are left intact. WorkChains if any, are left
+ * intact.
*
* <p>Useful when combining with another WorkSource that doesn't have names.
* @hide
@@ -127,11 +155,16 @@
*/
public void clear() {
mNum = 0;
+ if (mChains != null) {
+ mChains.clear();
+ }
}
@Override
public boolean equals(Object o) {
- return o instanceof WorkSource && !diff((WorkSource)o);
+ return o instanceof WorkSource
+ && !diff((WorkSource) o)
+ && Objects.equals(mChains, ((WorkSource) o).mChains);
}
@Override
@@ -145,6 +178,11 @@
result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode();
}
}
+
+ if (mChains != null) {
+ result = ((result << 4) | (result >>> 28)) ^ mChains.hashCode();
+ }
+
return result;
}
@@ -153,6 +191,8 @@
* @param other The WorkSource to compare against.
* @return If there is a difference, true is returned.
*/
+ // TODO: This is a public API so it cannot be renamed. Because it is used in several places,
+ // we keep its semantics the same and ignore any differences in WorkChains (if any).
public boolean diff(WorkSource other) {
int N = mNum;
if (N != other.mNum) {
@@ -175,12 +215,15 @@
/**
* Replace the current contents of this work source with the given
- * work source. If <var>other</var> is null, the current work source
+ * work source. If {@code other} is null, the current work source
* will be made empty.
*/
public void set(WorkSource other) {
if (other == null) {
mNum = 0;
+ if (mChains != null) {
+ mChains.clear();
+ }
return;
}
mNum = other.mNum;
@@ -203,6 +246,18 @@
mUids = null;
mNames = null;
}
+
+ if (other.mChains != null) {
+ if (mChains != null) {
+ mChains.clear();
+ } else {
+ mChains = new ArrayList<>(other.mChains.size());
+ }
+
+ for (WorkChain chain : other.mChains) {
+ mChains.add(new WorkChain(chain));
+ }
+ }
}
/** @hide */
@@ -211,6 +266,7 @@
if (mUids == null) mUids = new int[2];
mUids[0] = uid;
mNames = null;
+ mChains.clear();
}
/** @hide */
@@ -225,9 +281,21 @@
}
mUids[0] = uid;
mNames[0] = name;
+ mChains.clear();
}
- /** @hide */
+ /**
+ * Legacy API, DO NOT USE: Only deals with flat UIDs and tags. No chains are transferred, and no
+ * differences in chains are returned. This will be removed once its callers have been
+ * rewritten.
+ *
+ * NOTE: This is currently only used in GnssLocationProvider.
+ *
+ * @hide
+ * @deprecated for internal use only. WorkSources are opaque and no external callers should need
+ * to be aware of internal differences.
+ */
+ @Deprecated
public WorkSource[] setReturningDiffs(WorkSource other) {
synchronized (sTmpWorkSource) {
sNewbWork = null;
@@ -251,11 +319,34 @@
*/
public boolean add(WorkSource other) {
synchronized (sTmpWorkSource) {
- return updateLocked(other, false, false);
+ boolean uidAdded = updateLocked(other, false, false);
+
+ boolean chainAdded = false;
+ if (other.mChains != null) {
+ // NOTE: This is quite an expensive operation, especially if the number of chains
+ // is large. We could look into optimizing it if it proves problematic.
+ if (mChains == null) {
+ mChains = new ArrayList<>(other.mChains.size());
+ }
+
+ for (WorkChain wc : other.mChains) {
+ if (!mChains.contains(wc)) {
+ mChains.add(new WorkChain(wc));
+ }
+ }
+ }
+
+ return uidAdded || chainAdded;
}
}
- /** @hide */
+ /**
+ * Legacy API: DO NOT USE. Only in use from unit tests.
+ *
+ * @hide
+ * @deprecated meant for unit testing use only. Will be removed in a future API revision.
+ */
+ @Deprecated
public WorkSource addReturningNewbs(WorkSource other) {
synchronized (sTmpWorkSource) {
sNewbWork = null;
@@ -311,22 +402,14 @@
return true;
}
- /** @hide */
- public WorkSource addReturningNewbs(int uid) {
- synchronized (sTmpWorkSource) {
- sNewbWork = null;
- sTmpWorkSource.mUids[0] = uid;
- updateLocked(sTmpWorkSource, false, true);
- return sNewbWork;
- }
- }
-
public boolean remove(WorkSource other) {
if (mNum <= 0 || other.mNum <= 0) {
return false;
}
+
+ boolean uidRemoved = false;
if (mNames == null && other.mNames == null) {
- return removeUids(other);
+ uidRemoved = removeUids(other);
} else {
if (mNames == null) {
throw new IllegalArgumentException("Other " + other + " has names, but target "
@@ -336,24 +419,44 @@
throw new IllegalArgumentException("Target " + this + " has names, but other "
+ other + " does not");
}
- return removeUidsAndNames(other);
+ uidRemoved = removeUidsAndNames(other);
}
+
+ boolean chainRemoved = false;
+ if (other.mChains != null) {
+ if (mChains != null) {
+ chainRemoved = mChains.removeAll(other.mChains);
+ }
+ } else if (mChains != null) {
+ mChains.clear();
+ chainRemoved = true;
+ }
+
+ return uidRemoved || chainRemoved;
}
- /** @hide */
- public WorkSource stripNames() {
- if (mNum <= 0) {
- return new WorkSource();
+ /**
+ * Create a new {@code WorkChain} associated with this WorkSource and return it.
+ *
+ * @hide
+ */
+ public WorkChain createWorkChain() {
+ if (mChains == null) {
+ mChains = new ArrayList<>(4);
}
- WorkSource result = new WorkSource();
- int lastUid = -1;
- for (int i=0; i<mNum; i++) {
- int uid = mUids[i];
- if (i == 0 || lastUid != uid) {
- result.add(uid);
- }
- }
- return result;
+
+ final WorkChain wc = new WorkChain();
+ mChains.add(wc);
+
+ return wc;
+ }
+
+ /**
+ * @return the list of {@code WorkChains} associated with this {@code WorkSource}.
+ * @hide
+ */
+ public ArrayList<WorkChain> getWorkChains() {
+ return mChains;
}
private boolean removeUids(WorkSource other) {
@@ -664,6 +767,167 @@
}
}
+ /**
+ * Represents an attribution chain for an item of work being performed. An attribution chain is
+ * an indexed list of {code (uid, tag)} nodes. The node at {@code index == 0} is the initiator
+ * of the work, and the node at the highest index performs the work. Nodes at other indices
+ * are intermediaries that facilitate the work. Examples :
+ *
+ * (1) Work being performed by uid=2456 (no chaining):
+ * <pre>
+ * WorkChain {
+ * mUids = { 2456 }
+ * mTags = { null }
+ * mSize = 1;
+ * }
+ * </pre>
+ *
+ * (2) Work being performed by uid=2456 (from component "c1") on behalf of uid=5678:
+ *
+ * <pre>
+ * WorkChain {
+ * mUids = { 5678, 2456 }
+ * mTags = { null, "c1" }
+ * mSize = 1
+ * }
+ * </pre>
+ *
+ * Attribution chains are mutable, though the only operation that can be performed on them
+ * is the addition of a new node at the end of the attribution chain to represent
+ *
+ * @hide
+ */
+ public static class WorkChain implements Parcelable {
+ private int mSize;
+ private int[] mUids;
+ private String[] mTags;
+
+ // @VisibleForTesting
+ public WorkChain() {
+ mSize = 0;
+ mUids = new int[4];
+ mTags = new String[4];
+ }
+
+ // @VisibleForTesting
+ public WorkChain(WorkChain other) {
+ mSize = other.mSize;
+ mUids = other.mUids.clone();
+ mTags = other.mTags.clone();
+ }
+
+ private WorkChain(Parcel in) {
+ mSize = in.readInt();
+ mUids = in.createIntArray();
+ mTags = in.createStringArray();
+ }
+
+ /**
+ * Append a node whose uid is {@code uid} and whose optional tag is {@code tag} to this
+ * {@code WorkChain}.
+ */
+ public WorkChain addNode(int uid, @Nullable String tag) {
+ if (mSize == mUids.length) {
+ resizeArrays();
+ }
+
+ mUids[mSize] = uid;
+ mTags[mSize] = tag;
+ mSize++;
+
+ return this;
+ }
+
+ // TODO: The following three trivial getters are purely for testing and will be removed
+ // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
+ // diffing it etc.
+ //
+ // @VisibleForTesting
+ public int[] getUids() {
+ return mUids;
+ }
+ // @VisibleForTesting
+ public String[] getTags() {
+ return mTags;
+ }
+ // @VisibleForTesting
+ public int getSize() {
+ return mSize;
+ }
+
+ private void resizeArrays() {
+ final int newSize = mSize * 2;
+ int[] uids = new int[newSize];
+ String[] tags = new String[newSize];
+
+ System.arraycopy(mUids, 0, uids, 0, mSize);
+ System.arraycopy(mTags, 0, tags, 0, mSize);
+
+ mUids = uids;
+ mTags = tags;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder("WorkChain{");
+ for (int i = 0; i < mSize; ++i) {
+ if (i != 0) {
+ result.append(", ");
+ }
+ result.append("(");
+ result.append(mUids[i]);
+ if (mTags[i] != null) {
+ result.append(", ");
+ result.append(mTags[i]);
+ }
+ result.append(")");
+ }
+
+ result.append("}");
+ return result.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return (mSize + 31 * Arrays.hashCode(mUids)) * 31 + Arrays.hashCode(mTags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof WorkChain) {
+ WorkChain other = (WorkChain) o;
+
+ return mSize == other.mSize
+ && Arrays.equals(mUids, other.mUids)
+ && Arrays.equals(mTags, other.mTags);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSize);
+ dest.writeIntArray(mUids);
+ dest.writeStringArray(mTags);
+ }
+
+ public static final Parcelable.Creator<WorkChain> CREATOR =
+ new Parcelable.Creator<WorkChain>() {
+ public WorkChain createFromParcel(Parcel in) {
+ return new WorkChain(in);
+ }
+ public WorkChain[] newArray(int size) {
+ return new WorkChain[size];
+ }
+ };
+ }
+
@Override
public int describeContents() {
return 0;
@@ -674,6 +938,13 @@
dest.writeInt(mNum);
dest.writeIntArray(mUids);
dest.writeStringArray(mNames);
+
+ if (mChains == null) {
+ dest.writeInt(-1);
+ } else {
+ dest.writeInt(mChains.size());
+ dest.writeParcelableList(mChains, flags);
+ }
}
@Override
@@ -690,6 +961,17 @@
result.append(mNames[i]);
}
}
+
+ if (mChains != null) {
+ result.append(" chains=");
+ for (int i = 0; i < mChains.size(); ++i) {
+ if (i != 0) {
+ result.append(", ");
+ }
+ result.append(mChains.get(i));
+ }
+ }
+
result.append("}");
return result.toString();
}
diff --git a/core/java/android/privacy/DifferentialPrivacyConfig.java b/core/java/android/privacy/DifferentialPrivacyConfig.java
new file mode 100644
index 0000000..e14893e
--- /dev/null
+++ b/core/java/android/privacy/DifferentialPrivacyConfig.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.privacy;
+
+/**
+ * An interface for differential privacy configuration.
+ * {@link DifferentialPrivacyEncoder} will apply this configuration to do differential privacy
+ * encoding.
+ *
+ * @hide
+ */
+public interface DifferentialPrivacyConfig {
+
+ /**
+ * Returns the name of the algorithm used in differential privacy config.
+ *
+ * @return The name of the algorithm
+ */
+ String getAlgorithm();
+}
diff --git a/core/java/android/privacy/DifferentialPrivacyEncoder.java b/core/java/android/privacy/DifferentialPrivacyEncoder.java
new file mode 100644
index 0000000..9355d6a
--- /dev/null
+++ b/core/java/android/privacy/DifferentialPrivacyEncoder.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.privacy;
+
+/**
+ * An interface for differential privacy encoder.
+ * Applications can use it to convert privacy sensitive data to privacy protected report.
+ * There is no decoder implemented in Android as it is not possible decode a single report by
+ * design.
+ *
+ * <p>Each type of log should have its own encoder, otherwise it may leak
+ * some information about Permanent Randomized Response(PRR, is used to create a “noisy”
+ * answer which is memoized by the client and permanently reused in place of the real answer).
+ *
+ * <p>Some encoders may not support all encoding methods, and it will throw {@link
+ * UnsupportedOperationException} if you call unsupported encoding method.
+ *
+ * <p><b>WARNING:</b> Privacy protection works only when encoder uses a suitable DP configuration,
+ * and the configuration and algorithm that is suitable is highly dependent on the use case.
+ * If the configuration is not suitable for the use case, it may hurt privacy or utility or both.
+ *
+ * @hide
+ */
+public interface DifferentialPrivacyEncoder {
+
+ /**
+ * Apply differential privacy to encode a string.
+ *
+ * @param original An arbitrary string
+ * @return Differential privacy encoded bytes derived from the string
+ */
+ byte[] encodeString(String original);
+
+ /**
+ * Apply differential privacy to encode a boolean.
+ *
+ * @param original An arbitrary boolean.
+ * @return Differential privacy encoded bytes derived from the boolean
+ */
+ byte[] encodeBoolean(boolean original);
+
+ /**
+ * Apply differential privacy to encode sequence of bytes.
+ *
+ * @param original An arbitrary byte array.
+ * @return Differential privacy encoded bytes derived from the bytes
+ */
+ byte[] encodeBits(byte[] original);
+
+ /**
+ * Returns the configuration that this encoder is using.
+ */
+ DifferentialPrivacyConfig getConfig();
+
+ /**
+ * Return True if the output from encoder is NOT securely randomized, otherwise encoder should
+ * be secure to randomize input.
+ *
+ * <b> A non-secure encoder is intended only for testing only and must not be used to process
+ * real data.
+ * </b>
+ */
+ boolean isInsecureEncoderForTest();
+}
diff --git a/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingConfig.java b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingConfig.java
new file mode 100644
index 0000000..ee910fc
--- /dev/null
+++ b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingConfig.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.privacy.internal.longitudinalreporting;
+
+import android.privacy.DifferentialPrivacyConfig;
+import android.privacy.internal.rappor.RapporConfig;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A class to store {@link LongitudinalReportingEncoder} configuration.
+ *
+ * <ul>
+ * <li> f is probability to flip input value, used in IRR.
+ * <li> p is probability to override input value, used in PRR1.
+ * <li> q is probability to set input value as 1 when result of PRR(p) is true, used in PRR2.
+ * </ul>
+ *
+ * @hide
+ */
+public class LongitudinalReportingConfig implements DifferentialPrivacyConfig {
+
+ private static final String ALGORITHM_NAME = "LongitudinalReporting";
+
+ // Probability to flip input value.
+ private final double mProbabilityF;
+
+ // Probability to override original value.
+ private final double mProbabilityP;
+ // Probability to override value with 1.
+ private final double mProbabilityQ;
+
+ // IRR config to randomize original value
+ private final RapporConfig mIRRConfig;
+
+ private final String mEncoderId;
+
+ /**
+ * Constructor to create {@link LongitudinalReportingConfig} used for {@link
+ * LongitudinalReportingEncoder}
+ *
+ * @param encoderId Unique encoder id.
+ * @param probabilityF Probability F used in Longitudinal Reporting algorithm.
+ * @param probabilityP Probability P used in Longitudinal Reporting algorithm. This will be
+ * quantized to the nearest 1/256.
+ * @param probabilityQ Probability Q used in Longitudinal Reporting algorithm. This will be
+ * quantized to the nearest 1/256.
+ */
+ public LongitudinalReportingConfig(String encoderId, double probabilityF,
+ double probabilityP, double probabilityQ) {
+ Preconditions.checkArgument(probabilityF >= 0 && probabilityF <= 1,
+ "probabilityF must be in range [0.0, 1.0]");
+ this.mProbabilityF = probabilityF;
+ Preconditions.checkArgument(probabilityP >= 0 && probabilityP <= 1,
+ "probabilityP must be in range [0.0, 1.0]");
+ this.mProbabilityP = probabilityP;
+ Preconditions.checkArgument(probabilityQ >= 0 && probabilityQ <= 1,
+ "probabilityQ must be in range [0.0, 1.0]");
+ this.mProbabilityQ = probabilityQ;
+ Preconditions.checkArgument(!TextUtils.isEmpty(encoderId), "encoderId cannot be empty");
+ mEncoderId = encoderId;
+ mIRRConfig = new RapporConfig(encoderId, 1, 0.0, probabilityF, 1 - probabilityF, 1, 1);
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return ALGORITHM_NAME;
+ }
+
+ RapporConfig getIRRConfig() {
+ return mIRRConfig;
+ }
+
+ double getProbabilityP() {
+ return mProbabilityP;
+ }
+
+ double getProbabilityQ() {
+ return mProbabilityQ;
+ }
+
+ String getEncoderId() {
+ return mEncoderId;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("EncoderId: %s, ProbabilityF: %.3f, ProbabilityP: %.3f"
+ + ", ProbabilityQ: %.3f",
+ mEncoderId, mProbabilityF, mProbabilityP, mProbabilityQ);
+ }
+}
diff --git a/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java
new file mode 100644
index 0000000..219868d
--- /dev/null
+++ b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.privacy.internal.longitudinalreporting;
+
+import android.privacy.DifferentialPrivacyEncoder;
+import android.privacy.internal.rappor.RapporConfig;
+import android.privacy.internal.rappor.RapporEncoder;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Differential privacy encoder by using Longitudinal Reporting algorithm.
+ *
+ * <b>
+ * Notes: It supports encodeBoolean() only for now.
+ * </b>
+ *
+ * <p>
+ * Definition:
+ * PRR = Permanent Randomized Response
+ * IRR = Instantaneous Randomized response
+ *
+ * Algorithm:
+ * Step 1: Create long-term secrets x(ignoreOriginalInput)=Ber(P), y=Ber(Q), where Ber denotes
+ * Bernoulli distribution on {0, 1}, and we use it as a long-term secret, we implement Ber(x) by
+ * using PRR(2x, 0) when x < 1/2, PRR(2(1-x), 1) when x >= 1/2.
+ * Step 2: If x is 0, report IRR(F, original), otherwise report IRR(F, y)
+ * </p>
+ *
+ * Reference: go/bit-reporting-with-longitudinal-privacy
+ * TODO: Add a public blog / site to explain how it works.
+ *
+ * @hide
+ */
+public class LongitudinalReportingEncoder implements DifferentialPrivacyEncoder {
+
+ // Suffix that will be added to Rappor's encoder id. There's a (relatively) small risk some
+ // other Rappor encoder may re-use the same encoder id.
+ private static final String PRR1_ENCODER_ID = "prr1_encoder_id";
+ private static final String PRR2_ENCODER_ID = "prr2_encoder_id";
+
+ private final LongitudinalReportingConfig mConfig;
+
+ // IRR encoder to encode input value.
+ private final RapporEncoder mIRREncoder;
+
+ // A value that used to replace original value as input, so there's always a chance we are
+ // doing IRR on a fake value not actual original value.
+ // Null if original value does not need to be replaced.
+ private final Boolean mFakeValue;
+
+ // True if encoder is securely randomized.
+ private final boolean mIsSecure;
+
+ /**
+ * Create {@link LongitudinalReportingEncoder} with
+ * {@link LongitudinalReportingConfig} provided.
+ *
+ * @param config Longitudinal Reporting parameters to encode input
+ * @param userSecret User generated secret that used to generate PRR
+ * @return {@link LongitudinalReportingEncoder} instance
+ */
+ public static LongitudinalReportingEncoder createEncoder(LongitudinalReportingConfig config,
+ byte[] userSecret) {
+ return new LongitudinalReportingEncoder(config, true, userSecret);
+ }
+
+ /**
+ * Create <strong>insecure</strong> {@link LongitudinalReportingEncoder} with
+ * {@link LongitudinalReportingConfig} provided.
+ * Should not use it to process sensitive data.
+ *
+ * @param config Rappor parameters to encode input.
+ * @return {@link LongitudinalReportingEncoder} instance.
+ */
+ @VisibleForTesting
+ public static LongitudinalReportingEncoder createInsecureEncoderForTest(
+ LongitudinalReportingConfig config) {
+ return new LongitudinalReportingEncoder(config, false, null);
+ }
+
+ private LongitudinalReportingEncoder(LongitudinalReportingConfig config,
+ boolean secureEncoder, byte[] userSecret) {
+ mConfig = config;
+ mIsSecure = secureEncoder;
+ final boolean ignoreOriginalInput = getLongTermRandomizedResult(config.getProbabilityP(),
+ secureEncoder, userSecret, config.getEncoderId() + PRR1_ENCODER_ID);
+
+ if (ignoreOriginalInput) {
+ mFakeValue = getLongTermRandomizedResult(config.getProbabilityQ(),
+ secureEncoder, userSecret, config.getEncoderId() + PRR2_ENCODER_ID);
+ } else {
+ // Not using fake value, so IRR will be processed on real input value.
+ mFakeValue = null;
+ }
+
+ final RapporConfig irrConfig = config.getIRRConfig();
+ mIRREncoder = secureEncoder
+ ? RapporEncoder.createEncoder(irrConfig, userSecret)
+ : RapporEncoder.createInsecureEncoderForTest(irrConfig);
+ }
+
+ @Override
+ public byte[] encodeString(String original) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public byte[] encodeBoolean(boolean original) {
+ if (mFakeValue != null) {
+ // Use the fake value generated in PRR.
+ original = mFakeValue.booleanValue();
+ }
+ return mIRREncoder.encodeBoolean(original);
+ }
+
+ @Override
+ public byte[] encodeBits(byte[] bits) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public LongitudinalReportingConfig getConfig() {
+ return mConfig;
+ }
+
+ @Override
+ public boolean isInsecureEncoderForTest() {
+ return !mIsSecure;
+ }
+
+ /**
+ * Get PRR result that with probability p is 1, probability 1-p is 0.
+ */
+ @VisibleForTesting
+ public static boolean getLongTermRandomizedResult(double p, boolean secureEncoder,
+ byte[] userSecret, String encoderId) {
+ // Use Rappor to get PRR result. Rappor's P and Q are set to 0 and 1 so IRR will not be
+ // effective.
+ // As Rappor has rapporF/2 chance returns 0, rapporF/2 chance returns 1, and 1-rapporF
+ // chance returns original input.
+ // If p < 0.5, setting rapporF=2p and input=0 will make Rappor has p chance to return 1
+ // P(output=1 | input=0) = rapporF/2 = 2p/2 = p.
+ // If p >= 0.5, setting rapporF=2(1-p) and input=1 will make Rappor has p chance
+ // to return 1.
+ // P(output=1 | input=1) = rapporF/2 + (1 - rapporF) = 2(1-p)/2 + (1 - 2(1-p)) = p.
+ final double effectiveF = p < 0.5f ? p * 2 : (1 - p) * 2;
+ final boolean prrInput = p < 0.5f ? false : true;
+ final RapporConfig prrConfig = new RapporConfig(encoderId, 1, effectiveF,
+ 0, 1, 1, 1);
+ final RapporEncoder encoder = secureEncoder
+ ? RapporEncoder.createEncoder(prrConfig, userSecret)
+ : RapporEncoder.createInsecureEncoderForTest(prrConfig);
+ return encoder.encodeBoolean(prrInput)[0] > 0;
+ }
+}
diff --git a/core/java/android/privacy/internal/rappor/RapporConfig.java b/core/java/android/privacy/internal/rappor/RapporConfig.java
new file mode 100644
index 0000000..221999b
--- /dev/null
+++ b/core/java/android/privacy/internal/rappor/RapporConfig.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.privacy.internal.rappor;
+
+import android.privacy.DifferentialPrivacyConfig;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A class to store {@link RapporEncoder} config.
+ *
+ * @hide
+ */
+public class RapporConfig implements DifferentialPrivacyConfig {
+
+ private static final String ALGORITHM_NAME = "Rappor";
+
+ final String mEncoderId;
+ final int mNumBits;
+ final double mProbabilityF;
+ final double mProbabilityP;
+ final double mProbabilityQ;
+ final int mNumCohorts;
+ final int mNumBloomHashes;
+
+ /**
+ * Constructor for {@link RapporConfig}.
+ *
+ * @param encoderId Unique id for encoder.
+ * @param numBits Number of bits to be encoded in Rappor algorithm.
+ * @param probabilityF Probability F that used in Rappor algorithm. This will be
+ * quantized to the nearest 1/128.
+ * @param probabilityP Probability P that used in Rappor algorithm.
+ * @param probabilityQ Probability Q that used in Rappor algorithm.
+ * @param numCohorts Number of cohorts that used in Rappor algorithm.
+ * @param numBloomHashes Number of bloom hashes that used in Rappor algorithm.
+ */
+ public RapporConfig(String encoderId, int numBits, double probabilityF,
+ double probabilityP, double probabilityQ, int numCohorts, int numBloomHashes) {
+ Preconditions.checkArgument(!TextUtils.isEmpty(encoderId), "encoderId cannot be empty");
+ this.mEncoderId = encoderId;
+ Preconditions.checkArgument(numBits > 0, "numBits needs to be > 0");
+ this.mNumBits = numBits;
+ Preconditions.checkArgument(probabilityF >= 0 && probabilityF <= 1,
+ "probabilityF must be in range [0.0, 1.0]");
+ this.mProbabilityF = probabilityF;
+ Preconditions.checkArgument(probabilityP >= 0 && probabilityP <= 1,
+ "probabilityP must be in range [0.0, 1.0]");
+ this.mProbabilityP = probabilityP;
+ Preconditions.checkArgument(probabilityQ >= 0 && probabilityQ <= 1,
+ "probabilityQ must be in range [0.0, 1.0]");
+ this.mProbabilityQ = probabilityQ;
+ Preconditions.checkArgument(numCohorts > 0, "numCohorts needs to be > 0");
+ this.mNumCohorts = numCohorts;
+ Preconditions.checkArgument(numBloomHashes > 0, "numBloomHashes needs to be > 0");
+ this.mNumBloomHashes = numBloomHashes;
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return ALGORITHM_NAME;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "EncoderId: %s, NumBits: %d, ProbabilityF: %.3f, ProbabilityP: %.3f"
+ + ", ProbabilityQ: %.3f, NumCohorts: %d, NumBloomHashes: %d",
+ mEncoderId, mNumBits, mProbabilityF, mProbabilityP, mProbabilityQ,
+ mNumCohorts, mNumBloomHashes);
+ }
+}
diff --git a/core/java/android/privacy/internal/rappor/RapporEncoder.java b/core/java/android/privacy/internal/rappor/RapporEncoder.java
new file mode 100644
index 0000000..2eca4c98
--- /dev/null
+++ b/core/java/android/privacy/internal/rappor/RapporEncoder.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.privacy.internal.rappor;
+
+import android.privacy.DifferentialPrivacyEncoder;
+
+import com.google.android.rappor.Encoder;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * Differential privacy encoder by using
+ * <a href="https://research.google.com/pubs/pub42852.html">RAPPOR</a>
+ * algorithm.
+ *
+ * @hide
+ */
+public class RapporEncoder implements DifferentialPrivacyEncoder {
+
+ // Hard-coded seed and secret for insecure encoder
+ private static final long INSECURE_RANDOM_SEED = 0x12345678L;
+ private static final byte[] INSECURE_SECRET = new byte[]{
+ (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
+ (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+ (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
+ (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+ (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93,
+ (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54,
+ (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54
+ };
+ private static final SecureRandom sSecureRandom = new SecureRandom();
+
+ private final RapporConfig mConfig;
+
+ // Rappor encoder
+ private final Encoder mEncoder;
+ // True if encoder is secure (seed is securely randomized)
+ private final boolean mIsSecure;
+
+
+ private RapporEncoder(RapporConfig config, boolean secureEncoder, byte[] userSecret) {
+ mConfig = config;
+ mIsSecure = secureEncoder;
+ final Random random;
+ if (secureEncoder) {
+ // Use SecureRandom as random generator.
+ random = sSecureRandom;
+ } else {
+ // Hard-coded random generator, to have deterministic result.
+ random = new Random(INSECURE_RANDOM_SEED);
+ userSecret = INSECURE_SECRET;
+ }
+ mEncoder = new Encoder(random, null, null,
+ userSecret, config.mEncoderId, config.mNumBits,
+ config.mProbabilityF, config.mProbabilityP, config.mProbabilityQ,
+ config.mNumCohorts, config.mNumBloomHashes);
+ }
+
+ /**
+ * Create {@link RapporEncoder} with {@link RapporConfig} and user secret provided.
+ *
+ * @param config Rappor parameters to encode input.
+ * @param userSecret Per device unique secret key.
+ * @return {@link RapporEncoder} instance.
+ */
+ public static RapporEncoder createEncoder(RapporConfig config, byte[] userSecret) {
+ return new RapporEncoder(config, true, userSecret);
+ }
+
+ /**
+ * Create <strong>insecure</strong> {@link RapporEncoder} with {@link RapporConfig} provided.
+ * Should not use it to process sensitive data.
+ *
+ * @param config Rappor parameters to encode input.
+ * @return {@link RapporEncoder} instance.
+ */
+ public static RapporEncoder createInsecureEncoderForTest(RapporConfig config) {
+ return new RapporEncoder(config, false, null);
+ }
+
+ @Override
+ public byte[] encodeString(String original) {
+ return mEncoder.encodeString(original);
+ }
+
+ @Override
+ public byte[] encodeBoolean(boolean original) {
+ return mEncoder.encodeBoolean(original);
+ }
+
+ @Override
+ public byte[] encodeBits(byte[] bits) {
+ return mEncoder.encodeBits(bits);
+ }
+
+ @Override
+ public RapporConfig getConfig() {
+ return mConfig;
+ }
+
+ @Override
+ public boolean isInsecureEncoderForTest() {
+ return !mIsSecure;
+ }
+}
diff --git a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl
index 1058463a..1674e51 100644
--- a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl
+++ b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.security.keystore.recoverablekeystore;
+package android.security.recoverablekeystore;
/* @hide */
parcelable KeyEntryRecoveryData;
diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
index 0510320..f2f225d 100644
--- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
+++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
@@ -19,37 +19,40 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+import android.security.KeyStore;
+import android.util.AndroidException;
import com.android.internal.widget.ILockSettings;
import java.util.List;
/**
- * A wrapper around KeyStore which lets key be exported to
- * trusted hardware on server side and recovered later.
+ * A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
+ * recovered later.
*
* @hide
*/
-public class RecoverableKeyStoreLoader {
+public class RecoverableKeyStoreLoader {
- private final ILockSettings mBinder;
+ public static final String PERMISSION_RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
- // Exception codes, should be in sync with {@code KeyStoreException}.
- public static final int SYSTEM_ERROR = 4;
-
+ public static final int NO_ERROR = KeyStore.NO_ERROR;
+ public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR;
public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
-
// Too many updates to recovery public key or server parameters.
public static final int RATE_LIMIT_EXCEEDED = 21;
+ private final ILockSettings mBinder;
+
private RecoverableKeyStoreLoader(ILockSettings binder) {
mBinder = binder;
}
- /**
- * @hide
- */
+ /** @hide */
public static RecoverableKeyStoreLoader getInstance() {
ILockSettings lockSettings =
ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
@@ -57,29 +60,69 @@
}
/**
+ * Exceptions returned by {@link RecoverableKeyStoreLoader}.
+ *
* @hide
*/
- public static class RecoverableKeyStoreLoaderException extends Exception {
- private final int mErrorCode;
+ public static class RecoverableKeyStoreLoaderException extends AndroidException {
+ private int mErrorCode;
- public RecoverableKeyStoreLoaderException(int errorCode, String message) {
- super(message);
- mErrorCode = errorCode;
+ /**
+ * Creates new {@link #RecoverableKeyStoreLoaderException} instance from the error code.
+ *
+ * @param errorCode
+ * @hide
+ */
+ public static RecoverableKeyStoreLoaderException fromErrorCode(int errorCode) {
+ return new RecoverableKeyStoreLoaderException(
+ errorCode, getMessageFromErrorCode(errorCode));
}
+ /**
+ * Creates new {@link #RecoverableKeyStoreLoaderException} from {@link
+ * ServiceSpecificException}.
+ *
+ * @param e exception thrown on service side.
+ * @hide
+ */
+ static RecoverableKeyStoreLoaderException fromServiceSpecificException(
+ ServiceSpecificException e) throws RecoverableKeyStoreLoaderException {
+ throw RecoverableKeyStoreLoaderException.fromErrorCode(e.errorCode);
+ }
+
+ private RecoverableKeyStoreLoaderException(int errorCode, String message) {
+ super(message);
+ }
+
+ /** Returns errorCode. */
public int getErrorCode() {
return mErrorCode;
}
+
+ /** @hide */
+ private static String getMessageFromErrorCode(int errorCode) {
+ switch (errorCode) {
+ case NO_ERROR:
+ return "OK";
+ case SYSTEM_ERROR:
+ return "System error";
+ case UNINITIALIZED_RECOVERY_PUBLIC_KEY:
+ return "Recovery service is not initialized";
+ case RATE_LIMIT_EXCEEDED:
+ return "Rate limit exceeded";
+ default:
+ return String.valueOf("Unknown error code " + errorCode);
+ }
+ }
}
/**
* Initializes key recovery service for the calling application. RecoverableKeyStoreLoader
- * randomly chooses one of the keys from the list
- * and keeps it to use for future key export operations. Collection of all keys
- * in the list must be signed by the provided {@code rootCertificateAlias}, which must also be
- * present in the list of root certificates preinstalled on the device. The random selection
- * allows RecoverableKeyStoreLoader to select which of a set of remote recovery service
- * devices will be used.
+ * randomly chooses one of the keys from the list and keeps it to use for future key export
+ * operations. Collection of all keys in the list must be signed by the provided {@code
+ * rootCertificateAlias}, which must also be present in the list of root certificates
+ * preinstalled on the device. The random selection allows RecoverableKeyStoreLoader to select
+ * which of a set of remote recovery service devices will be used.
*
* <p>In addition, RecoverableKeyStoreLoader enforces a delay of three months between
* consecutive initialization attempts, to limit the ability of an attacker to often switch
@@ -88,127 +131,200 @@
* @param rootCertificateAlias alias of a root certificate preinstalled on the device
* @param signedPublicKeyList binary blob a list of X509 certificates and signature
* @throws RecoverableKeyStoreLoaderException if signature is invalid, or key rotation was rate
- * limited.
+ * limited.
* @hide
*/
- public void initRecoveryService(@NonNull String rootCertificateAlias,
- @NonNull byte[] signedPublicKeyList)
+ public void initRecoveryService(
+ @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
throws RecoverableKeyStoreLoaderException {
- throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
- // TODO: extend widget/ILockSettings.aidl
- /* try {
- mBinder.initRecoveryService(rootCertificate, publicKeyList);
- } catch (RemoteException e) {
+ try {
+ mBinder.initRecoveryService(
+ rootCertificateAlias, signedPublicKeyList, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } */
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
}
/**
- * Returns data necessary to store all recoverable keys for given account.
- * Key material is encrypted with user secret and recovery public key.
+ * Returns data necessary to store all recoverable keys for given account. Key material is
+ * encrypted with user secret and recovery public key.
+ *
+ * @param account specific to Recovery agent.
+ * @return Data necessary to recover keystore.
+ * @hide
*/
public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
throws RecoverableKeyStoreLoaderException {
- throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+ try {
+ KeyStoreRecoveryData recoveryData =
+ mBinder.getRecoveryData(account, UserHandle.getCallingUserId());
+ return recoveryData;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
}
/**
* Server parameters used to generate new recovery key blobs. This value will be included in
- * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}.
- * The same value must be included in vaultParams {@link startRecoverySession}
+ * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
+ * in vaultParams {@link startRecoverySession}
*
+ * @param serverParameters included in recovery key blob.
* @see #getRecoveryData
* @throws RecoverableKeyStoreLoaderException If parameters rotation is rate limited.
+ * @hide
*/
- public void updateServerParameters(long serverParameters)
+ public void setServerParameters(long serverParameters)
throws RecoverableKeyStoreLoaderException {
- throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+ try {
+ mBinder.setServerParameters(serverParameters, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
}
/**
- * Updates recovery status for given keys.
- * It is used to notify keystore that key was successfully stored on the server or
- * there were an error. Returned as a part of KeyInfo data structure.
+ * Updates recovery status for given keys. It is used to notify keystore that key was
+ * successfully stored on the server or there were an error. Returned as a part of KeyInfo data
+ * structure.
*
* @param packageName Application whose recoverable keys' statuses are to be updated.
* @param aliases List of application-specific key aliases. If the array is empty, updates the
- * status for all existing recoverable keys.
+ * status for all existing recoverable keys.
* @param status Status specific to recovery agent.
*/
- public void setRecoveryStatus(@NonNull String packageName, @Nullable String[] aliases,
- int status) throws NameNotFoundException, RecoverableKeyStoreLoaderException {
- throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+ public void setRecoveryStatus(
+ @NonNull String packageName, @Nullable String[] aliases, int status)
+ throws NameNotFoundException, RecoverableKeyStoreLoaderException {
+ try {
+ mBinder.setRecoveryStatus(packageName, aliases, status, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
}
/**
- * Specifies a set of secret types used for end-to-end keystore encryption.
- * Knowing all of them is necessary to recover data.
+ * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
+ * is necessary to recover data.
*
- * @param secretTypes {@link KeyStoreRecoveryMetadata#TYPE_LOCKSCREEN} or
- * {@link KeyStoreRecoveryMetadata#TYPE_CUSTOM_PASSWORD}
+ * @param secretTypes {@link KeyStoreRecoveryMetadata#TYPE_LOCKSCREEN} or {@link
+ * KeyStoreRecoveryMetadata#TYPE_CUSTOM_PASSWORD}
*/
- public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
- int[] secretTypes) throws RecoverableKeyStoreLoaderException {
- throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+ public void setRecoverySecretTypes(
+ @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ mBinder.setRecoverySecretTypes(secretTypes, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
}
/**
- * Defines a set of secret types used for end-to-end keystore encryption.
- * Knowing all of them is necessary to generate KeyStoreRecoveryData.
+ * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
+ * necessary to generate KeyStoreRecoveryData.
+ *
+ * @return list of recovery secret types
* @see KeyStoreRecoveryData
*/
public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getRecoverySecretTypes()
throws RecoverableKeyStoreLoaderException {
- throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+ try {
+ return mBinder.getRecoverySecretTypes(UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
}
/**
* Returns a list of recovery secret types, necessary to create a pending recovery snapshot.
- * When user enters a secret of a pending type
- * {@link #recoverySecretAvailable} should be called.
+ * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be
+ * called.
+ *
+ * @return list of recovery secret types
*/
public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes()
throws RecoverableKeyStoreLoaderException {
- throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+ try {
+ return mBinder.getPendingRecoverySecretTypes(UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
}
/**
- * Method notifies KeyStore that a user-generated secret is available.
- * This method generates a symmetric session key which a trusted remote device can use
- * to return a recovery key.
- * Caller should use {@link KeyStoreRecoveryMetadata#clearSecret} to override the secret value
- * in memory.
+ * Method notifies KeyStore that a user-generated secret is available. This method generates a
+ * symmetric session key which a trusted remote device can use to return a recovery key. Caller
+ * should use {@link KeyStoreRecoveryMetadata#clearSecret} to override the secret value in
+ * memory.
*
- * @param recoverySecret user generated secret together with parameters necessary to
- * regenerate it on a new device.
+ * @param recoverySecret user generated secret together with parameters necessary to regenerate
+ * it on a new device.
*/
public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret)
throws RecoverableKeyStoreLoaderException {
- throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+ try {
+ mBinder.recoverySecretAvailable(recoverySecret, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
}
/**
* Initializes recovery session and returns a blob with proof of recovery secrets possession.
- * The method generates symmetric key for a session, which trusted remote device can use
- * to return recovery key.
+ * The method generates symmetric key for a session, which trusted remote device can use to
+ * return recovery key.
*
* @param sessionId ID for recovery session.
- * @param verifierPublicKey Certificate with Public key used to create the recovery blob on
- * the source device. Keystore will verify the certificate using root of trust.
+ * @param verifierPublicKey Certificate with Public key used to create the recovery blob on the
+ * source device. Keystore will verify the certificate using root of trust.
* @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
- * Used to limit number of guesses.
+ * Used to limit number of guesses.
* @param vaultChallenge Data passed from server for this recovery session and used to prevent
- * replay attacks
+ * replay attacks
* @param secrets Secrets provided by user, the method only uses type and secret fields.
- * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and
- * contains a proof of user secrets, session symmetric key and parameters necessary to identify
- * the counter with the number of failed recovery attempts.
+ * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and contains
+ * a proof of user secrets, session symmetric key and parameters necessary to identify the
+ * counter with the number of failed recovery attempts.
*/
- public @NonNull byte[] startRecoverySession(@NonNull String sessionId,
- @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams,
- @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets)
+ public @NonNull byte[] startRecoverySession(
+ @NonNull String sessionId,
+ @NonNull byte[] verifierPublicKey,
+ @NonNull byte[] vaultParams,
+ @NonNull byte[] vaultChallenge,
+ @NonNull List<KeyStoreRecoveryMetadata> secrets)
throws RecoverableKeyStoreLoaderException {
- throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+ try {
+ byte[] recoveryClaim =
+ mBinder.startRecoverySession(
+ sessionId,
+ verifierPublicKey,
+ vaultParams,
+ vaultChallenge,
+ secrets,
+ UserHandle.getCallingUserId());
+ return recoveryClaim;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
}
/**
@@ -217,12 +333,21 @@
* @param sessionId Id for recovery session, same as in = {@link startRecoverySession}.
* @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
* @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
- * and session. KeyStore only uses package names from the application info in
- * {@link KeyEntryRecoveryData}. Caller is responsibility to perform certificates check.
+ * and session. KeyStore only uses package names from the application info in {@link
+ * KeyEntryRecoveryData}. Caller is responsibility to perform certificates check.
*/
- public void recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob,
+ public void recoverKeys(
+ @NonNull String sessionId,
+ @NonNull byte[] recoveryKeyBlob,
@NonNull List<KeyEntryRecoveryData> applicationKeys)
throws RecoverableKeyStoreLoaderException {
- throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented");
+ try {
+ mBinder.recoverKeys(
+ sessionId, recoveryKeyBlob, applicationKeys, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
}
}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 2600f8a..917efa8 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -438,7 +438,7 @@
* AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
*
* save(username, password);
- * </pre>
+ * </pre>
*
* <a name="Privacy"></a>
* <h3>Privacy</h3>
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index fb6f637..b8f2191 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -27,6 +27,7 @@
void setDesiredSize(int width, int height);
void setDisplayPadding(in Rect padding);
void setVisibility(boolean visible);
+ void setInAmbientMode(boolean inAmbientDisplay);
void dispatchPointer(in MotionEvent event);
void dispatchWallpaperCommand(String action, int x, int y,
int z, in Bundle extras);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index e5ab3e1..595bfb7 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -102,6 +102,7 @@
private static final int DO_DETACH = 20;
private static final int DO_SET_DESIRED_SIZE = 30;
private static final int DO_SET_DISPLAY_PADDING = 40;
+ private static final int DO_IN_AMBIENT_MODE = 50;
private static final int MSG_UPDATE_SURFACE = 10000;
private static final int MSG_VISIBILITY_CHANGED = 10010;
@@ -195,6 +196,7 @@
float mPendingYOffsetStep;
boolean mPendingSync;
MotionEvent mPendingMove;
+ boolean mIsInAmbientMode;
// Needed for throttling onComputeColors.
private long mLastColorInvalidation;
@@ -431,6 +433,15 @@
public boolean isPreview() {
return mIWallpaperEngine.mIsPreview;
}
+
+ /**
+ * Returns true if this engine is running in ambient mode -- that is,
+ * it is being shown in low power mode, in always on display.
+ * @hide
+ */
+ public boolean isInAmbientMode() {
+ return mIsInAmbientMode;
+ }
/**
* Control whether this wallpaper will receive raw touch events
@@ -549,6 +560,15 @@
}
/**
+ * Called when the device enters or exits ambient mode.
+ *
+ * @param inAmbientMode {@code true} if in ambient mode.
+ * @hide
+ */
+ public void onAmbientModeChanged(boolean inAmbientMode) {
+ }
+
+ /**
* Called when an application has changed the desired virtual size of
* the wallpaper.
*/
@@ -631,6 +651,16 @@
return null;
}
+ /**
+ * Sets internal engine state. Only for testing.
+ * @param created {@code true} or {@code false}.
+ * @hide
+ */
+ @VisibleForTesting
+ public void setCreated(boolean created) {
+ mCreated = created;
+ }
+
protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
out.print(prefix); out.print("mInitializing="); out.print(mInitializing);
out.print(" mDestroyed="); out.println(mDestroyed);
@@ -683,7 +713,8 @@
}
Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
mCaller.sendMessage(msg);
- } else {event.recycle();
+ } else {
+ event.recycle();
}
}
@@ -986,6 +1017,26 @@
updateSurface(false, false, false);
}
+ /**
+ * Executes life cycle event and updates internal ambient mode state based on
+ * message sent from handler.
+ *
+ * @param inAmbientMode True if in ambient mode.
+ * @hide
+ */
+ @VisibleForTesting
+ public void doAmbientModeChanged(boolean inAmbientMode) {
+ if (!mDestroyed) {
+ if (DEBUG) {
+ Log.v(TAG, "onAmbientModeChanged(" + inAmbientMode + "): " + this);
+ }
+ mIsInAmbientMode = inAmbientMode;
+ if (mCreated) {
+ onAmbientModeChanged(inAmbientMode);
+ }
+ }
+ }
+
void doDesiredSizeChanged(int desiredWidth, int desiredHeight) {
if (!mDestroyed) {
if (DEBUG) Log.v(TAG, "onDesiredSizeChanged("
@@ -1226,6 +1277,12 @@
mCaller.sendMessage(msg);
}
+ @Override
+ public void setInAmbientMode(boolean inAmbientDisplay) throws RemoteException {
+ Message msg = mCaller.obtainMessageI(DO_IN_AMBIENT_MODE, inAmbientDisplay ? 1 : 0);
+ mCaller.sendMessage(msg);
+ }
+
public void dispatchPointer(MotionEvent event) {
if (mEngine != null) {
mEngine.dispatchPointer(event);
@@ -1263,6 +1320,7 @@
mCaller.sendMessage(msg);
}
+ @Override
public void executeMessage(Message message) {
switch (message.what) {
case DO_ATTACH: {
@@ -1289,6 +1347,11 @@
}
case DO_SET_DISPLAY_PADDING: {
mEngine.doDisplayPaddingChanged((Rect) message.obj);
+ return;
+ }
+ case DO_IN_AMBIENT_MODE: {
+ mEngine.doAmbientModeChanged(message.arg1 != 0);
+ return;
}
case MSG_UPDATE_SURFACE:
mEngine.updateSurface(true, false, false);
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index fba358c..6bca37a 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -42,8 +42,7 @@
* {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
* Canvas.drawText()} directly.</p>
*/
-public class DynamicLayout extends Layout
-{
+public class DynamicLayout extends Layout {
private static final int PRIORITY = 128;
private static final int BLOCK_MINIMUM_CHARACTER_LENGTH = 400;
@@ -303,8 +302,9 @@
}
/**
- * Make a layout for the specified text that will be updated as the text is changed.
+ * @deprecated Use {@link Builder} instead.
*/
+ @Deprecated
public DynamicLayout(@NonNull CharSequence base,
@NonNull TextPaint paint,
@IntRange(from = 0) int width, @NonNull Alignment align,
@@ -315,9 +315,9 @@
}
/**
- * Make a layout for the transformed text (password transformation being the primary example of
- * a transformation) that will be updated as the base text is changed.
+ * @deprecated Use {@link Builder} instead.
*/
+ @Deprecated
public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
@NonNull TextPaint paint,
@IntRange(from = 0) int width, @NonNull Alignment align,
@@ -328,10 +328,9 @@
}
/**
- * Make a layout for the transformed text (password transformation being the primary example of
- * a transformation) that will be updated as the base text is changed. If ellipsize is non-null,
- * the Layout will ellipsize the text down to ellipsizedWidth.
+ * @deprecated Use {@link Builder} instead.
*/
+ @Deprecated
public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
@NonNull TextPaint paint,
@IntRange(from = 0) int width, @NonNull Alignment align,
@@ -351,7 +350,9 @@
* the Layout will ellipsize the text down to ellipsizedWidth.
*
* @hide
+ * @deprecated Use {@link Builder} instead.
*/
+ @Deprecated
public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
@NonNull TextPaint paint,
@IntRange(from = 0) int width,
@@ -492,7 +493,9 @@
}
}
- private void reflow(CharSequence s, int where, int before, int after) {
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void reflow(CharSequence s, int where, int before, int after) {
if (s != mBase)
return;
@@ -805,8 +808,8 @@
return;
}
- int firstBlock = -1;
- int lastBlock = -1;
+ /*final*/ int firstBlock = -1;
+ /*final*/ int lastBlock = -1;
for (int i = 0; i < mNumberOfBlocks; i++) {
if (mBlockEndLines[i] >= startLine) {
firstBlock = i;
@@ -821,10 +824,10 @@
}
final int lastBlockEndLine = mBlockEndLines[lastBlock];
- boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 :
+ final boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 :
mBlockEndLines[firstBlock - 1] + 1);
- boolean createBlock = newLineCount > 0;
- boolean createBlockAfter = endLine < mBlockEndLines[lastBlock];
+ final boolean createBlock = newLineCount > 0;
+ final boolean createBlockAfter = endLine < mBlockEndLines[lastBlock];
int numAddedBlocks = 0;
if (createBlockBefore) numAddedBlocks++;
@@ -863,12 +866,18 @@
if (numAddedBlocks + numRemovedBlocks != 0 && mBlocksAlwaysNeedToBeRedrawn != null) {
final ArraySet<Integer> set = new ArraySet<>();
+ final int changedBlockCount = numAddedBlocks - numRemovedBlocks;
for (int i = 0; i < mBlocksAlwaysNeedToBeRedrawn.size(); i++) {
Integer block = mBlocksAlwaysNeedToBeRedrawn.valueAt(i);
- if (block > firstBlock) {
- block += numAddedBlocks - numRemovedBlocks;
+ if (block < firstBlock) {
+ // block index is before firstBlock add it since it did not change
+ set.add(block);
}
- set.add(block);
+ if (block > lastBlock) {
+ // block index is after lastBlock, the index reduced to += changedBlockCount
+ block += changedBlockCount;
+ set.add(block);
+ }
}
mBlocksAlwaysNeedToBeRedrawn = set;
}
diff --git a/core/java/android/text/OWNERS b/core/java/android/text/OWNERS
new file mode 100644
index 0000000..0f85e1f
--- /dev/null
+++ b/core/java/android/text/OWNERS
@@ -0,0 +1,4 @@
+siyamed@google.com
+nona@google.com
+clarabayarri@google.com
+toki@google.com
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 2e10fe8d..d69b119 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -454,6 +454,10 @@
private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
}
+ /**
+ * @deprecated Use {@link Builder} instead.
+ */
+ @Deprecated
public StaticLayout(CharSequence source, TextPaint paint,
int width,
Alignment align, float spacingmult, float spacingadd,
@@ -463,16 +467,9 @@
}
/**
- * @hide
+ * @deprecated Use {@link Builder} instead.
*/
- public StaticLayout(CharSequence source, TextPaint paint,
- int width, Alignment align, TextDirectionHeuristic textDir,
- float spacingmult, float spacingadd,
- boolean includepad) {
- this(source, 0, source.length(), paint, width, align, textDir,
- spacingmult, spacingadd, includepad);
- }
-
+ @Deprecated
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align,
@@ -483,17 +480,9 @@
}
/**
- * @hide
+ * @deprecated Use {@link Builder} instead.
*/
- public StaticLayout(CharSequence source, int bufstart, int bufend,
- TextPaint paint, int outerwidth,
- Alignment align, TextDirectionHeuristic textDir,
- float spacingmult, float spacingadd,
- boolean includepad) {
- this(source, bufstart, bufend, paint, outerwidth, align, textDir,
- spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
-}
-
+ @Deprecated
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align,
@@ -507,7 +496,9 @@
/**
* @hide
+ * @deprecated Use {@link Builder} instead.
*/
+ @Deprecated
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align, TextDirectionHeuristic textDir,
@@ -565,6 +556,9 @@
Builder.recycle(b);
}
+ /**
+ * Used by DynamicLayout.
+ */
/* package */ StaticLayout(@Nullable CharSequence text) {
super(text, null, 0, null, 0, 0);
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 19cd42e..e448f14 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -21,40 +21,37 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
-import android.annotation.NonNull;
+import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.ArrayList;
import java.util.List;
/**
* Represents a part of the display that is not functional for displaying content.
*
* <p>{@code DisplayCutout} is immutable.
- *
- * @hide will become API
*/
public final class DisplayCutout {
- private static final Rect ZERO_RECT = new Rect(0, 0, 0, 0);
- private static final ArrayList<Point> EMPTY_LIST = new ArrayList<>();
+ private static final Rect ZERO_RECT = new Rect();
+ private static final Region EMPTY_REGION = new Region();
/**
- * An instance where {@link #hasCutout()} returns {@code false}.
+ * An instance where {@link #isEmpty()} returns {@code true}.
*
* @hide
*/
- public static final DisplayCutout NO_CUTOUT =
- new DisplayCutout(ZERO_RECT, ZERO_RECT, EMPTY_LIST);
+ public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION);
private final Rect mSafeInsets;
- private final Rect mBoundingRect;
- private final List<Point> mBoundingPolygon;
+ private final Region mBounds;
/**
* Creates a DisplayCutout instance.
@@ -64,22 +61,18 @@
* @hide
*/
@VisibleForTesting
- public DisplayCutout(Rect safeInsets, Rect boundingRect, List<Point> boundingPolygon) {
+ public DisplayCutout(Rect safeInsets, Region bounds) {
mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT;
- mBoundingRect = boundingRect != null ? boundingRect : ZERO_RECT;
- mBoundingPolygon = boundingPolygon != null ? boundingPolygon : EMPTY_LIST;
+ mBounds = bounds != null ? bounds : Region.obtain();
}
/**
- * Returns whether there is a cutout.
+ * Returns true if there is no cutout or it is outside of the content view.
*
- * If false, the safe insets will all return zero, and the bounding box or polygon will be
- * empty or outside the content view.
- *
- * @return {@code true} if there is a cutout, {@code false} otherwise
+ * @hide
*/
- public boolean hasCutout() {
- return !mSafeInsets.equals(ZERO_RECT);
+ public boolean isEmpty() {
+ return mSafeInsets.equals(ZERO_RECT);
}
/** Returns the inset from the top which avoids the display cutout. */
@@ -103,44 +96,41 @@
}
/**
- * Obtains the safe insets in a rect.
+ * Returns the safe insets in a rect.
*
- * @param out a rect which is set to the safe insets.
+ * @return a rect which is set to the safe insets.
* @hide
*/
- public void getSafeInsets(@NonNull Rect out) {
- out.set(mSafeInsets);
+ public Rect getSafeInsets() {
+ return new Rect(mSafeInsets);
}
/**
- * Obtains the bounding rect of the cutout.
+ * Returns the bounding region of the cutout.
*
- * @param outRect is filled with the bounding rect of the cutout. Coordinates are relative
+ * @return the bounding region of the cutout. Coordinates are relative
* to the top-left corner of the content view.
*/
- public void getBoundingRect(@NonNull Rect outRect) {
- outRect.set(mBoundingRect);
+ public Region getBounds() {
+ return Region.obtain(mBounds);
}
/**
- * Obtains the bounding polygon of the cutout.
+ * Returns the bounding rect of the cutout.
*
- * @param outPolygon is filled with a list of points representing the corners of a convex
- * polygon which covers the cutout. Coordinates are relative to the
- * top-left corner of the content view.
+ * @return the bounding rect of the cutout. Coordinates are relative
+ * to the top-left corner of the content view.
+ * @hide
*/
- public void getBoundingPolygon(List<Point> outPolygon) {
- outPolygon.clear();
- for (int i = 0; i < mBoundingPolygon.size(); i++) {
- outPolygon.add(new Point(mBoundingPolygon.get(i)));
- }
+ public Rect getBoundingRect() {
+ // TODO(roosa): Inline.
+ return mBounds.getBounds();
}
@Override
public int hashCode() {
int result = mSafeInsets.hashCode();
- result = result * 31 + mBoundingRect.hashCode();
- result = result * 31 + mBoundingPolygon.hashCode();
+ result = result * 31 + mBounds.getBounds().hashCode();
return result;
}
@@ -152,8 +142,7 @@
if (o instanceof DisplayCutout) {
DisplayCutout c = (DisplayCutout) o;
return mSafeInsets.equals(c.mSafeInsets)
- && mBoundingRect.equals(c.mBoundingRect)
- && mBoundingPolygon.equals(c.mBoundingPolygon);
+ && mBounds.equals(c.mBounds);
}
return false;
}
@@ -161,7 +150,7 @@
@Override
public String toString() {
return "DisplayCutout{insets=" + mSafeInsets
- + " bounding=" + mBoundingRect
+ + " bounds=" + mBounds
+ "}";
}
@@ -172,15 +161,13 @@
* @hide
*/
public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
- if (mBoundingRect.isEmpty()
+ if (mBounds.isEmpty()
|| insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
return this;
}
Rect safeInsets = new Rect(mSafeInsets);
- Rect boundingRect = new Rect(mBoundingRect);
- ArrayList<Point> boundingPolygon = new ArrayList<>();
- getBoundingPolygon(boundingPolygon);
+ Region bounds = Region.obtain(mBounds);
// Note: it's not really well defined what happens when the inset is negative, because we
// don't know if the safe inset needs to expand in general.
@@ -197,10 +184,9 @@
safeInsets.right = atLeastZero(safeInsets.right - insetRight);
}
- boundingRect.offset(-insetLeft, -insetTop);
- offset(boundingPolygon, -insetLeft, -insetTop);
+ bounds.translate(-insetLeft, -insetTop);
- return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+ return new DisplayCutout(safeInsets, bounds);
}
/**
@@ -210,20 +196,17 @@
* @hide
*/
public DisplayCutout calculateRelativeTo(Rect frame) {
- if (mBoundingRect.isEmpty() || !Rect.intersects(frame, mBoundingRect)) {
+ if (mBounds.isEmpty() || !Rect.intersects(frame, mBounds.getBounds())) {
return NO_CUTOUT;
}
- Rect boundingRect = new Rect(mBoundingRect);
- ArrayList<Point> boundingPolygon = new ArrayList<>();
- getBoundingPolygon(boundingPolygon);
-
- return DisplayCutout.calculateRelativeTo(frame, boundingRect, boundingPolygon);
+ return DisplayCutout.calculateRelativeTo(frame, Region.obtain(mBounds));
}
- private static DisplayCutout calculateRelativeTo(Rect frame, Rect boundingRect,
- ArrayList<Point> boundingPolygon) {
+ private static DisplayCutout calculateRelativeTo(Rect frame, Region bounds) {
+ Rect boundingRect = bounds.getBounds();
Rect safeRect = new Rect();
+
int bestArea = 0;
int bestVariant = 0;
for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) {
@@ -247,10 +230,9 @@
Math.max(0, frame.bottom - safeRect.bottom));
}
- boundingRect.offset(-frame.left, -frame.top);
- offset(boundingPolygon, -frame.left, -frame.top);
+ bounds.translate(-frame.left, -frame.top);
- return new DisplayCutout(safeRect, boundingRect, boundingPolygon);
+ return new DisplayCutout(safeRect, bounds);
}
private static int calculateInsetVariantArea(Rect frame, Rect boundingRect, int variant,
@@ -277,11 +259,6 @@
return value < 0 ? 0 : value;
}
- private static void offset(ArrayList<Point> points, int dx, int dy) {
- for (int i = 0; i < points.size(); i++) {
- points.get(i).offset(dx, dy);
- }
- }
/**
* Creates an instance from a bounding polygon.
@@ -289,20 +266,28 @@
* @hide
*/
public static DisplayCutout fromBoundingPolygon(List<Point> points) {
- Rect boundingRect = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE,
- Integer.MIN_VALUE, Integer.MIN_VALUE);
- ArrayList<Point> boundingPolygon = new ArrayList<>();
+ Region bounds = Region.obtain();
+ Path path = new Path();
+ path.reset();
for (int i = 0; i < points.size(); i++) {
Point point = points.get(i);
- boundingRect.left = Math.min(boundingRect.left, point.x);
- boundingRect.right = Math.max(boundingRect.right, point.x);
- boundingRect.top = Math.min(boundingRect.top, point.y);
- boundingRect.bottom = Math.max(boundingRect.bottom, point.y);
- boundingPolygon.add(new Point(point));
+ if (i == 0) {
+ path.moveTo(point.x, point.y);
+ } else {
+ path.lineTo(point.x, point.y);
+ }
}
+ path.close();
- return new DisplayCutout(ZERO_RECT, boundingRect, boundingPolygon);
+ RectF clipRect = new RectF();
+ path.computeBounds(clipRect, false /* unused */);
+ Region clipRegion = Region.obtain();
+ clipRegion.set((int) clipRect.left, (int) clipRect.top,
+ (int) clipRect.right, (int) clipRect.bottom);
+
+ bounds.setPath(path, clipRegion);
+ return new DisplayCutout(ZERO_RECT, bounds);
}
/**
@@ -336,8 +321,7 @@
} else {
out.writeInt(1);
out.writeTypedObject(mInner.mSafeInsets, flags);
- out.writeTypedObject(mInner.mBoundingRect, flags);
- out.writeTypedList(mInner.mBoundingPolygon, flags);
+ out.writeTypedObject(mInner.mBounds, flags);
}
}
@@ -368,13 +352,10 @@
return NO_CUTOUT;
}
- ArrayList<Point> boundingPolygon = new ArrayList<>();
-
Rect safeInsets = in.readTypedObject(Rect.CREATOR);
- Rect boundingRect = in.readTypedObject(Rect.CREATOR);
- in.readTypedList(boundingPolygon, Point.CREATOR);
+ Region bounds = in.readTypedObject(Region.CREATOR);
- return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+ return new DisplayCutout(safeInsets, bounds);
}
public DisplayCutout get() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2c82ac4..6c5091c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1602,7 +1602,7 @@
if (!layoutInCutout) {
// Window is either not laid out in cutout or the status bar inset takes care of
// clearing the cutout, so we don't need to dispatch the cutout to the hierarchy.
- insets = insets.consumeCutout();
+ insets = insets.consumeDisplayCutout();
}
host.dispatchApplyWindowInsets(insets);
}
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index df124ac..e5cbe96 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -17,7 +17,7 @@
package android.view;
-import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Rect;
/**
@@ -49,7 +49,7 @@
private boolean mSystemWindowInsetsConsumed = false;
private boolean mWindowDecorInsetsConsumed = false;
private boolean mStableInsetsConsumed = false;
- private boolean mCutoutConsumed = false;
+ private boolean mDisplayCutoutConsumed = false;
private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
@@ -80,8 +80,9 @@
mIsRound = isRound;
mAlwaysConsumeNavBar = alwaysConsumeNavBar;
- mCutoutConsumed = displayCutout == null;
- mDisplayCutout = mCutoutConsumed ? DisplayCutout.NO_CUTOUT : displayCutout;
+ mDisplayCutoutConsumed = displayCutout == null;
+ mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty())
+ ? null : displayCutout;
}
/**
@@ -99,7 +100,7 @@
mIsRound = src.mIsRound;
mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar;
mDisplayCutout = src.mDisplayCutout;
- mCutoutConsumed = src.mCutoutConsumed;
+ mDisplayCutoutConsumed = src.mDisplayCutoutConsumed;
}
/** @hide */
@@ -269,15 +270,16 @@
*/
public boolean hasInsets() {
return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets()
- || mDisplayCutout.hasCutout();
+ || mDisplayCutout != null;
}
/**
- * @return the display cutout
+ * Returns the display cutout if there is one.
+ *
+ * @return the display cutout or null if there is none
* @see DisplayCutout
- * @hide pending API
*/
- @NonNull
+ @Nullable
public DisplayCutout getDisplayCutout() {
return mDisplayCutout;
}
@@ -286,12 +288,11 @@
* Returns a copy of this WindowInsets with the cutout fully consumed.
*
* @return A modified copy of this WindowInsets
- * @hide pending API
*/
- public WindowInsets consumeCutout() {
+ public WindowInsets consumeDisplayCutout() {
final WindowInsets result = new WindowInsets(this);
- result.mDisplayCutout = DisplayCutout.NO_CUTOUT;
- result.mCutoutConsumed = true;
+ result.mDisplayCutout = null;
+ result.mDisplayCutoutConsumed = true;
return result;
}
@@ -311,7 +312,7 @@
*/
public boolean isConsumed() {
return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed
- && mCutoutConsumed;
+ && mDisplayCutoutConsumed;
}
/**
@@ -530,7 +531,7 @@
return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets
+ " windowDecorInsets=" + mWindowDecorInsets
+ " stableInsets=" + mStableInsets
- + (mDisplayCutout.hasCutout() ? " cutout=" + mDisplayCutout : "")
+ + (mDisplayCutout != null ? " cutout=" + mDisplayCutout : "")
+ (isRound() ? " round" : "")
+ "}";
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 012e864..cbe012a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1286,7 +1286,6 @@
* The window must correctly position its contents to take the display cutout into account.
*
* @see DisplayCutout
- * @hide for now
*/
public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001;
@@ -1294,7 +1293,6 @@
* Various behavioral options/flags. Default is none.
*
* @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA
- * @hide for now
*/
@Flags2 public long flags2;
@@ -2249,6 +2247,7 @@
out.writeInt(y);
out.writeInt(type);
out.writeInt(flags);
+ out.writeLong(flags2);
out.writeInt(privateFlags);
out.writeInt(softInputMode);
out.writeInt(gravity);
@@ -2304,6 +2303,7 @@
y = in.readInt();
type = in.readInt();
flags = in.readInt();
+ flags2 = in.readLong();
privateFlags = in.readInt();
softInputMode = in.readInt();
gravity = in.readInt();
@@ -2436,6 +2436,10 @@
flags = o.flags;
changes |= FLAGS_CHANGED;
}
+ if (flags2 != o.flags2) {
+ flags2 = o.flags2;
+ changes |= FLAGS_CHANGED;
+ }
if (privateFlags != o.privateFlags) {
privateFlags = o.privateFlags;
changes |= PRIVATE_FLAGS_CHANGED;
@@ -2689,6 +2693,11 @@
sb.append(System.lineSeparator());
sb.append(prefix).append(" fl=").append(
ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
+ if (flags2 != 0) {
+ sb.append(System.lineSeparator());
+ // TODO(roosa): add a long overload for ViewDebug.flagsToString.
+ sb.append(prefix).append(" fl2=0x").append(Long.toHexString(flags2));
+ }
if (privateFlags != 0) {
sb.append(System.lineSeparator());
sb.append(prefix).append(" pfl=").append(ViewDebug.flagsToString(
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 9c2f6bb..28ef697 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2325,7 +2325,7 @@
/**
* Returns whether the node is explicitly marked as a focusable unit by a screen reader. Note
* that {@code false} indicates that it is not explicitly marked, not that the node is not
- * a focusable unit. Screen readers should generally used other signals, such as
+ * a focusable unit. Screen readers should generally use other signals, such as
* {@link #isFocusable()}, or the presence of text in a node, to determine what should receive
* focus.
*
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index f11767d..ef1a3f3 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -87,6 +87,7 @@
private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;
private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1;
private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2;
+ private static final int BOOLEAN_PROPERTY_PICTURE_IN_PICTURE = 1 << 3;
// Housekeeping.
private static final int MAX_POOL_SIZE = 10;
@@ -103,8 +104,7 @@
private final Rect mBoundsInScreen = new Rect();
private LongArray mChildIds;
private CharSequence mTitle;
- private int mAnchorId = UNDEFINED_WINDOW_ID;
- private boolean mInPictureInPicture;
+ private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
private int mConnectionId = UNDEFINED_WINDOW_ID;
@@ -202,7 +202,7 @@
*
* @hide
*/
- public void setAnchorId(int anchorId) {
+ public void setAnchorId(long anchorId) {
mAnchorId = anchorId;
}
@@ -212,7 +212,8 @@
* @return The anchor node, or {@code null} if none exists.
*/
public AccessibilityNodeInfo getAnchor() {
- if ((mConnectionId == UNDEFINED_WINDOW_ID) || (mAnchorId == UNDEFINED_WINDOW_ID)
+ if ((mConnectionId == UNDEFINED_WINDOW_ID)
+ || (mAnchorId == AccessibilityNodeInfo.UNDEFINED_NODE_ID)
|| (mParentId == UNDEFINED_WINDOW_ID)) {
return null;
}
@@ -224,17 +225,7 @@
/** @hide */
public void setPictureInPicture(boolean pictureInPicture) {
- mInPictureInPicture = pictureInPicture;
- }
-
- /**
- * Check if the window is in picture-in-picture mode.
- *
- * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
- * @removed
- */
- public boolean inPictureInPicture() {
- return isInPictureInPictureMode();
+ setBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE, pictureInPicture);
}
/**
@@ -243,7 +234,7 @@
* @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
*/
public boolean isInPictureInPictureMode() {
- return mInPictureInPicture;
+ return getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE);
}
/**
@@ -463,7 +454,6 @@
infoClone.mBoundsInScreen.set(info.mBoundsInScreen);
infoClone.mTitle = info.mTitle;
infoClone.mAnchorId = info.mAnchorId;
- infoClone.mInPictureInPicture = info.mInPictureInPicture;
if (info.mChildIds != null && info.mChildIds.size() > 0) {
if (infoClone.mChildIds == null) {
@@ -520,8 +510,7 @@
parcel.writeInt(mParentId);
mBoundsInScreen.writeToParcel(parcel, flags);
parcel.writeCharSequence(mTitle);
- parcel.writeInt(mAnchorId);
- parcel.writeInt(mInPictureInPicture ? 1 : 0);
+ parcel.writeLong(mAnchorId);
final LongArray childIds = mChildIds;
if (childIds == null) {
@@ -545,8 +534,7 @@
mParentId = parcel.readInt();
mBoundsInScreen.readFromParcel(parcel);
mTitle = parcel.readCharSequence();
- mAnchorId = parcel.readInt();
- mInPictureInPicture = parcel.readInt() == 1;
+ mAnchorId = parcel.readLong();
final int childCount = parcel.readInt();
if (childCount > 0) {
@@ -593,7 +581,7 @@
builder.append(", bounds=").append(mBoundsInScreen);
builder.append(", focused=").append(isFocused());
builder.append(", active=").append(isActive());
- builder.append(", pictureInPicture=").append(inPictureInPicture());
+ builder.append(", pictureInPicture=").append(isInPictureInPictureMode());
if (DEBUG) {
builder.append(", parent=").append(mParentId);
builder.append(", children=[");
@@ -611,7 +599,8 @@
builder.append(']');
} else {
builder.append(", hasParent=").append(mParentId != UNDEFINED_WINDOW_ID);
- builder.append(", isAnchored=").append(mAnchorId != UNDEFINED_WINDOW_ID);
+ builder.append(", isAnchored=")
+ .append(mAnchorId != AccessibilityNodeInfo.UNDEFINED_NODE_ID);
builder.append(", hasChildren=").append(mChildIds != null
&& mChildIds.size() > 0);
}
@@ -633,8 +622,7 @@
mChildIds.clear();
}
mConnectionId = UNDEFINED_WINDOW_ID;
- mAnchorId = UNDEFINED_WINDOW_ID;
- mInPictureInPicture = false;
+ mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
mTitle = null;
}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index fdc9f92..ed60430 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -16,17 +16,23 @@
package android.view.textclassifier;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.annotation.WorkerThread;
import android.os.LocaleList;
+import android.util.ArraySet;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
/**
* Interface for providing text classification related features.
@@ -58,6 +64,20 @@
})
@interface EntityType {}
+ /** Designates that the TextClassifier should identify all entity types it can. **/
+ int ENTITY_PRESET_ALL = 0;
+ /** Designates that the TextClassifier should identify no entities. **/
+ int ENTITY_PRESET_NONE = 1;
+ /** Designates that the TextClassifier should identify a base set of entities determined by the
+ * TextClassifier. **/
+ int ENTITY_PRESET_BASE = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "ENTITY_CONFIG_" },
+ value = {ENTITY_PRESET_ALL, ENTITY_PRESET_NONE, ENTITY_PRESET_BASE})
+ @interface EntityPreset {}
+
/**
* No-op TextClassifier.
* This may be used to turn off TextClassifier features.
@@ -217,6 +237,8 @@
* Returns a {@link TextLinks} that may be applied to the text to annotate it with links
* information.
*
+ * If no options are supplied, default values will be used, determined by the TextClassifier.
+ *
* @param text the text to generate annotations for
* @param options configuration for link generation
*
@@ -251,6 +273,16 @@
}
/**
+ * Returns a {@link Collection} of the entity types in the specified preset.
+ *
+ * @see #ENTITIES_ALL
+ * @see #ENTITIES_NONE
+ */
+ default Collection<String> getEntitiesForPreset(@EntityPreset int entityPreset) {
+ return Collections.EMPTY_LIST;
+ }
+
+ /**
* Logs a TextClassifier event.
*
* @param source the text classifier used to generate this event
@@ -268,6 +300,62 @@
return TextClassifierConstants.DEFAULT;
}
+ /**
+ * Configuration object for specifying what entities to identify.
+ *
+ * Configs are initially based on a predefined preset, and can be modified from there.
+ */
+ final class EntityConfig {
+ private final @TextClassifier.EntityPreset int mEntityPreset;
+ private final Collection<String> mExcludedEntityTypes;
+ private final Collection<String> mIncludedEntityTypes;
+
+ public EntityConfig(@TextClassifier.EntityPreset int mEntityPreset) {
+ this.mEntityPreset = mEntityPreset;
+ mExcludedEntityTypes = new ArraySet<>();
+ mIncludedEntityTypes = new ArraySet<>();
+ }
+
+ /**
+ * Specifies an entity to include in addition to any specified by the enity preset.
+ *
+ * Note that if an entity has been excluded, the exclusion will take precedence.
+ */
+ public EntityConfig includeEntities(String... entities) {
+ for (String entity : entities) {
+ mIncludedEntityTypes.add(entity);
+ }
+ return this;
+ }
+
+ /**
+ * Specifies an entity to be excluded.
+ */
+ public EntityConfig excludeEntities(String... entities) {
+ for (String entity : entities) {
+ mExcludedEntityTypes.add(entity);
+ }
+ return this;
+ }
+
+ /**
+ * Returns an unmodifiable list of the final set of entities to find.
+ */
+ public List<String> getEntities(TextClassifier textClassifier) {
+ ArrayList<String> entities = new ArrayList<>();
+ for (String entity : textClassifier.getEntitiesForPreset(mEntityPreset)) {
+ if (!mExcludedEntityTypes.contains(entity)) {
+ entities.add(entity);
+ }
+ }
+ for (String entity : mIncludedEntityTypes) {
+ if (!mExcludedEntityTypes.contains(entity) && !entities.contains(entity)) {
+ entities.add(entity);
+ }
+ }
+ return Collections.unmodifiableList(entities);
+ }
+ }
/**
* Utility functions for TextClassifier methods.
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index d7aaee7..aea3cb0 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -42,6 +42,9 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -66,6 +69,18 @@
private static final String MODEL_FILE_REGEX = "textclassifier\\.smartselection\\.(.*)\\.model";
private static final String UPDATED_MODEL_FILE_PATH =
"/data/misc/textclassifier/textclassifier.smartselection.model";
+ private static final List<String> ENTITY_TYPES_ALL =
+ Collections.unmodifiableList(Arrays.asList(
+ TextClassifier.TYPE_ADDRESS,
+ TextClassifier.TYPE_EMAIL,
+ TextClassifier.TYPE_PHONE,
+ TextClassifier.TYPE_URL));
+ private static final List<String> ENTITY_TYPES_BASE =
+ Collections.unmodifiableList(Arrays.asList(
+ TextClassifier.TYPE_ADDRESS,
+ TextClassifier.TYPE_EMAIL,
+ TextClassifier.TYPE_PHONE,
+ TextClassifier.TYPE_URL));
private final Context mContext;
@@ -168,17 +183,23 @@
@Override
public TextLinks generateLinks(
- @NonNull CharSequence text, @NonNull TextLinks.Options options) {
+ @NonNull CharSequence text, @Nullable TextLinks.Options options) {
Utils.validateInput(text);
final String textString = text.toString();
final TextLinks.Builder builder = new TextLinks.Builder(textString);
try {
- LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
+ final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
+ final Collection<String> entitiesToIdentify =
+ options != null && options.getEntityConfig() != null
+ ? options.getEntityConfig().getEntities(this) : ENTITY_TYPES_ALL;
final SmartSelection smartSelection = getSmartSelection(defaultLocales);
final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString);
for (SmartSelection.AnnotatedSpan span : annotations) {
- final Map<String, Float> entityScores = new HashMap<>();
final SmartSelection.ClassificationResult[] results = span.getClassification();
+ if (results.length == 0 || !entitiesToIdentify.contains(results[0].mCollection)) {
+ continue;
+ }
+ final Map<String, Float> entityScores = new HashMap<>();
for (int i = 0; i < results.length; i++) {
entityScores.put(results[i].mCollection, results[i].mScore);
}
@@ -193,6 +214,20 @@
}
@Override
+ public Collection<String> getEntitiesForPreset(@TextClassifier.EntityPreset int entityPreset) {
+ switch (entityPreset) {
+ case TextClassifier.ENTITY_PRESET_NONE:
+ return Collections.emptyList();
+ case TextClassifier.ENTITY_PRESET_BASE:
+ return ENTITY_TYPES_BASE;
+ case TextClassifier.ENTITY_PRESET_ALL:
+ // fall through
+ default:
+ return ENTITY_TYPES_ALL;
+ }
+ }
+
+ @Override
public void logEvent(String source, String event) {
if (LOG_TAG.equals(source)) {
mMetricsLogger.count(event, 1);
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index 4fe5662..6c587cf 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -161,11 +161,12 @@
public static final class Options {
private LocaleList mDefaultLocales;
+ private TextClassifier.EntityConfig mEntityConfig;
/**
- * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
- * the provided text. If no locale preferences exist, set this to null or an empty
- * locale list.
+ * @param defaultLocales ordered list of locale preferences that may be used to
+ * disambiguate the provided text. If no locale preferences exist,
+ * set this to null or an empty locale list.
*/
public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
mDefaultLocales = defaultLocales;
@@ -173,6 +174,17 @@
}
/**
+ * Sets the entity configuration to use. This determines what types of entities the
+ * TextClassifier will look for.
+ *
+ * @param entityConfig EntityConfig to use
+ */
+ public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
+ mEntityConfig = entityConfig;
+ return this;
+ }
+
+ /**
* @return ordered list of locale preferences that can be used to disambiguate
* the provided text.
*/
@@ -180,6 +192,15 @@
public LocaleList getDefaultLocales() {
return mDefaultLocales;
}
+
+ /**
+ * @return The config representing the set of entities to look for.
+ * @see #setEntityConfig(TextClassifier.EntityConfig)
+ */
+ @Nullable
+ public TextClassifier.EntityConfig getEntityConfig() {
+ return mEntityConfig;
+ }
}
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index e985923..de5a822 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2081,6 +2081,48 @@
}
/**
+ * Define the directory used to store WebView data for the current process.
+ * The provided suffix will be used when constructing data and cache
+ * directory paths. If this API is not called, no suffix will be used.
+ * Each directory can be used by only one process in the application. If more
+ * than one process in an app wishes to use WebView, only one process can use
+ * the default directory, and other processes must call this API to define
+ * a unique suffix.
+ * <p>
+ * This API must be called before any instances of WebView are created in
+ * this process and before any other methods in the android.webkit package
+ * are called by this process.
+ *
+ * @param suffix The directory name suffix to be used for the current
+ * process. Must not contain a path separator.
+ * @throws IllegalStateException if WebView has already been initialized
+ * in the current process.
+ * @throws IllegalArgumentException if the suffix contains a path separator.
+ */
+ public static void setDataDirectorySuffix(String suffix) {
+ WebViewFactory.setDataDirectorySuffix(suffix);
+ }
+
+ /**
+ * Indicate that the current process does not intend to use WebView, and
+ * that an exception should be thrown if a WebView is created or any other
+ * methods in the android.webkit package are used.
+ * <p>
+ * Applications with multiple processes may wish to call this in processes
+ * which are not intended to use WebView to prevent potential data directory
+ * conflicts (see {@link #setDataDirectorySuffix}) and to avoid accidentally
+ * incurring the memory usage of initializing WebView in long-lived
+ * processes which have no need for it.
+ *
+ * @throws IllegalStateException if WebView has already been initialized
+ * in the current process.
+ */
+ public static void disableWebView() {
+ WebViewFactory.disableWebView();
+ }
+
+
+ /**
* @deprecated This was used for Gears, which has been deprecated.
* @hide
*/
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 7339931..f067091 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -218,4 +218,11 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Returns the data directory suffix to use, or null for none.
+ */
+ public String getDataDirectorySuffix() {
+ return WebViewFactory.getDataDirectorySuffix();
+ }
}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 9db0e8d..e3efad0 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -33,6 +33,7 @@
import android.util.ArraySet;
import android.util.Log;
+import java.io.File;
import java.lang.reflect.Method;
/**
@@ -63,6 +64,8 @@
private static final Object sProviderLock = new Object();
private static PackageInfo sPackageInfo;
private static Boolean sWebViewSupported;
+ private static boolean sWebViewDisabled;
+ private static String sDataDirectorySuffix; // stored here so it can be set without loading WV
// Error codes for loadWebViewNativeLibraryFromPackage
public static final int LIBLOAD_SUCCESS = 0;
@@ -115,6 +118,45 @@
/**
* @hide
*/
+ static void disableWebView() {
+ synchronized (sProviderLock) {
+ if (sProviderInstance != null) {
+ throw new IllegalStateException(
+ "Can't disable WebView: WebView already initialized");
+ }
+ sWebViewDisabled = true;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ static void setDataDirectorySuffix(String suffix) {
+ synchronized (sProviderLock) {
+ if (sProviderInstance != null) {
+ throw new IllegalStateException(
+ "Can't set data directory suffix: WebView already initialized");
+ }
+ if (suffix.indexOf(File.separatorChar) >= 0) {
+ throw new IllegalArgumentException("Suffix " + suffix
+ + " contains a path separator");
+ }
+ sDataDirectorySuffix = suffix;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ static String getDataDirectorySuffix() {
+ synchronized (sProviderLock) {
+ return sDataDirectorySuffix;
+ }
+ }
+
+ /**
+ * @hide
+ */
public static String getWebViewLibrary(ApplicationInfo ai) {
if (ai.metaData != null)
return ai.metaData.getString("com.android.webview.WebViewLibrary");
@@ -204,6 +246,11 @@
throw new UnsupportedOperationException();
}
+ if (sWebViewDisabled) {
+ throw new IllegalStateException(
+ "WebView.disableWebView() was called: WebView is disabled");
+ }
+
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
try {
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
new file mode 100644
index 0000000..8f0d02f
--- /dev/null
+++ b/core/java/android/widget/OWNERS
@@ -0,0 +1,12 @@
+per-file TextView.java = siyamed@google.com
+per-file TextView.java = nona@google.com
+per-file TextView.java = clarabayarri@google.com
+per-file TextView.java = toki@google.com
+per-file EditText.java = siyamed@google.com
+per-file EditText.java = nona@google.com
+per-file EditText.java = clarabayarri@google.com
+per-file EditText.java = toki@google.com
+per-file Editor.java = siyamed@google.com
+per-file Editor.java = nona@google.com
+per-file Editor.java = clarabayarri@google.com
+per-file Editor.java = toki@google.com
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9ac443b..1e17f34 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4867,6 +4867,10 @@
* Sets line spacing for this TextView. Each line other than the last line will have its height
* multiplied by {@code mult} and have {@code add} added to it.
*
+ * @param add The value in pixels that should be added to each line other than the last line.
+ * This will be applied after the multiplier
+ * @param mult The value by which each line height other than the last line will be multiplied
+ * by
*
* @attr ref android.R.styleable#TextView_lineSpacingExtra
* @attr ref android.R.styleable#TextView_lineSpacingMultiplier
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 6fb02b1..efc9c02 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -91,14 +91,14 @@
STATE_TOP, // ActivityManager.PROCESS_STATE_TOP
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- STATE_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
STATE_BACKUP, // ActivityManager.PROCESS_STATE_BACKUP
- STATE_HEAVY_WEIGHT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
STATE_SERVICE, // ActivityManager.PROCESS_STATE_SERVICE
STATE_RECEIVER, // ActivityManager.PROCESS_STATE_RECEIVER
+ STATE_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+ STATE_HEAVY_WEIGHT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
STATE_HOME, // ActivityManager.PROCESS_STATE_HOME
STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 2ce7936..96ba2b0 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -81,10 +81,10 @@
public static final int STATE_IMPORTANT_FOREGROUND = 2;
public static final int STATE_IMPORTANT_BACKGROUND = 3;
public static final int STATE_BACKUP = 4;
- public static final int STATE_HEAVY_WEIGHT = 5;
- public static final int STATE_SERVICE = 6;
- public static final int STATE_SERVICE_RESTARTING = 7;
- public static final int STATE_RECEIVER = 8;
+ public static final int STATE_SERVICE = 5;
+ public static final int STATE_SERVICE_RESTARTING = 6;
+ public static final int STATE_RECEIVER = 7;
+ public static final int STATE_HEAVY_WEIGHT = 8;
public static final int STATE_HOME = 9;
public static final int STATE_LAST_ACTIVITY = 10;
public static final int STATE_CACHED_ACTIVITY = 11;
@@ -141,8 +141,8 @@
public static final int[] NON_CACHED_PROC_STATES = new int[] {
STATE_PERSISTENT, STATE_TOP, STATE_IMPORTANT_FOREGROUND,
- STATE_IMPORTANT_BACKGROUND, STATE_BACKUP, STATE_HEAVY_WEIGHT,
- STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER
+ STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
+ STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, STATE_HEAVY_WEIGHT
};
public static final int[] BACKGROUND_PROC_STATES = new int[] {
@@ -152,13 +152,13 @@
public static final int[] ALL_PROC_STATES = new int[] { STATE_PERSISTENT,
STATE_TOP, STATE_IMPORTANT_FOREGROUND, STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
- STATE_HEAVY_WEIGHT, STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER,
- STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED_ACTIVITY,
+ STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER,
+ STATE_HEAVY_WEIGHT, STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED_ACTIVITY,
STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY
};
// Current version of the parcel format.
- private static final int PARCEL_VERSION = 22;
+ private static final int PARCEL_VERSION = 23;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0x50535454;
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index d49d572..a075705 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -236,6 +236,7 @@
moveInMediaStore(visibleFileBefore, getFileForDocId(afterDocId, true));
if (!TextUtils.equals(docId, afterDocId)) {
+ scanFile(after);
return afterDocId;
} else {
return null;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index bef247f..6510a70 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -120,7 +120,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 170 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 171 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -8678,13 +8678,15 @@
} else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
// Persistent and other foreground states go here.
uidRunningState = PROCESS_STATE_FOREGROUND_SERVICE;
- } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
- uidRunningState = PROCESS_STATE_TOP_SLEEPING;
} else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
// Persistent and other foreground states go here.
uidRunningState = PROCESS_STATE_FOREGROUND;
} else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
uidRunningState = PROCESS_STATE_BACKGROUND;
+ } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+ uidRunningState = PROCESS_STATE_TOP_SLEEPING;
+ } else if (procState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+ uidRunningState = PROCESS_STATE_HEAVY_WEIGHT;
} else {
uidRunningState = PROCESS_STATE_CACHED;
}
diff --git a/core/java/com/android/internal/os/ByteTransferPipe.java b/core/java/com/android/internal/os/ByteTransferPipe.java
new file mode 100644
index 0000000..6489894
--- /dev/null
+++ b/core/java/com/android/internal/os/ByteTransferPipe.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Helper class to get byte data through a pipe from a client app. Also {@see TransferPipe}.
+ */
+public class ByteTransferPipe extends TransferPipe {
+ static final String TAG = "ByteTransferPipe";
+
+ private ByteArrayOutputStream mOutputStream;
+
+ public ByteTransferPipe() throws IOException {
+ super();
+ }
+
+ public ByteTransferPipe(String bufferPrefix) throws IOException {
+ super(bufferPrefix, "ByteTransferPipe");
+ }
+
+ @Override
+ protected OutputStream getNewOutputStream() {
+ mOutputStream = new ByteArrayOutputStream();
+ return mOutputStream;
+ }
+
+ public byte[] get() throws IOException {
+ go(null);
+ return mOutputStream.toByteArray();
+ }
+}
diff --git a/core/java/com/android/internal/os/TransferPipe.java b/core/java/com/android/internal/os/TransferPipe.java
index 738ecc0b..1c09bd6 100644
--- a/core/java/com/android/internal/os/TransferPipe.java
+++ b/core/java/com/android/internal/os/TransferPipe.java
@@ -34,11 +34,12 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
/**
* Helper for transferring data through a pipe from a client app.
*/
-public final class TransferPipe implements Runnable, Closeable {
+public class TransferPipe implements Runnable, Closeable {
static final String TAG = "TransferPipe";
static final boolean DEBUG = false;
@@ -64,7 +65,11 @@
}
public TransferPipe(String bufferPrefix) throws IOException {
- mThread = new Thread(this, "TransferPipe");
+ this(bufferPrefix, "TransferPipe");
+ }
+
+ protected TransferPipe(String bufferPrefix, String threadName) throws IOException {
+ mThread = new Thread(this, threadName);
mFds = ParcelFileDescriptor.createPipe();
mBufferPrefix = bufferPrefix;
}
@@ -234,11 +239,15 @@
}
}
+ protected OutputStream getNewOutputStream() {
+ return new FileOutputStream(mOutFd);
+ }
+
@Override
public void run() {
final byte[] buffer = new byte[1024];
final FileInputStream fis;
- final FileOutputStream fos;
+ final OutputStream fos;
synchronized (this) {
ParcelFileDescriptor readFd = getReadFd();
@@ -247,7 +256,7 @@
return;
}
fis = new FileInputStream(readFd.getFileDescriptor());
- fos = new FileOutputStream(mOutFd);
+ fos = getNewOutputStream();
}
if (DEBUG) Slog.i(TAG, "Ready to read pipe...");
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index f983de1..7985e57 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -290,11 +290,11 @@
if (cur instanceof ArraySet) {
ArraySet<T> arraySet = (ArraySet<T>) cur;
for (int i = 0; i < size; i++) {
- action.accept(arraySet.valueAt(i));
+ action.acceptOrThrow(arraySet.valueAt(i));
}
} else {
for (T t : cur) {
- action.accept(t);
+ action.acceptOrThrow(t);
}
}
} catch (Exception e) {
diff --git a/core/java/com/android/internal/util/FunctionalUtils.java b/core/java/com/android/internal/util/FunctionalUtils.java
index eb92c1c..82ac241 100644
--- a/core/java/com/android/internal/util/FunctionalUtils.java
+++ b/core/java/com/android/internal/util/FunctionalUtils.java
@@ -16,6 +16,9 @@
package com.android.internal.util;
+import android.os.RemoteException;
+
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -25,6 +28,21 @@
private FunctionalUtils() {}
/**
+ * Converts a lambda expression that throws a checked exception(s) into a regular
+ * {@link Consumer} by propagating any checked exceptions as {@link RuntimeException}
+ */
+ public static <T> Consumer<T> uncheckExceptions(ThrowingConsumer<T> action) {
+ return action;
+ }
+
+ /**
+ *
+ */
+ public static <T> Consumer<T> ignoreRemoteException(RemoteExceptionIgnoringConsumer<T> action) {
+ return action;
+ }
+
+ /**
* An equivalent of {@link Runnable} that allows throwing checked exceptions
*
* This can be used to specify a lambda argument without forcing all the checked exceptions
@@ -47,13 +65,43 @@
}
/**
- * An equivalent of {@link java.util.function.Consumer} that allows throwing checked exceptions
+ * A {@link Consumer} that allows throwing checked exceptions from its single abstract method.
*
- * This can be used to specify a lambda argument without forcing all the checked exceptions
- * to be handled within it
+ * Can be used together with {@link #uncheckExceptions} to effectively turn a lambda expression
+ * that throws a checked exception into a regular {@link Consumer}
*/
@FunctionalInterface
- public interface ThrowingConsumer<T> {
- void accept(T t) throws Exception;
+ @SuppressWarnings("FunctionalInterfaceMethodChanged")
+ public interface ThrowingConsumer<T> extends Consumer<T> {
+ void acceptOrThrow(T t) throws Exception;
+
+ @Override
+ default void accept(T t) {
+ try {
+ acceptOrThrow(t);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+
+ /**
+ * A {@link Consumer} that automatically ignores any {@link RemoteException}s.
+ *
+ * Used by {@link #ignoreRemoteException}
+ */
+ @FunctionalInterface
+ @SuppressWarnings("FunctionalInterfaceMethodChanged")
+ public interface RemoteExceptionIgnoringConsumer<T> extends Consumer<T> {
+ void acceptOrThrow(T t) throws RemoteException;
+
+ @Override
+ default void accept(T t) {
+ try {
+ acceptOrThrow(t);
+ } catch (RemoteException ex) {
+ // ignore
+ }
+ }
}
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 8f80bfe..0d2b29b 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -50,6 +50,7 @@
private TextView mShortcutView;
private ImageView mSubMenuArrowView;
private ImageView mGroupDivider;
+ private LinearLayout mContent;
private Drawable mBackground;
private int mTextAppearance;
@@ -114,6 +115,8 @@
mSubMenuArrowView.setImageDrawable(mSubMenuArrow);
}
mGroupDivider = findViewById(com.android.internal.R.id.group_divider);
+
+ mContent = findViewById(com.android.internal.R.id.content);
}
public void initialize(MenuItemImpl itemData, int menuType) {
@@ -131,6 +134,18 @@
setContentDescription(itemData.getContentDescription());
}
+ private void addContentView(View v) {
+ addContentView(v, -1);
+ }
+
+ private void addContentView(View v, int index) {
+ if (mContent != null) {
+ mContent.addView(v, index);
+ } else {
+ addView(v, index);
+ }
+ }
+
public void setForceShowIcon(boolean forceShow) {
mPreserveIconSpacing = mForceShowIcon = forceShow;
}
@@ -270,7 +285,7 @@
LayoutInflater inflater = getInflater();
mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
this, false);
- addView(mIconView, 0);
+ addContentView(mIconView, 0);
}
private void insertRadioButton() {
@@ -278,7 +293,7 @@
mRadioButton =
(RadioButton) inflater.inflate(com.android.internal.R.layout.list_menu_item_radio,
this, false);
- addView(mRadioButton);
+ addContentView(mRadioButton);
}
private void insertCheckBox() {
@@ -286,7 +301,7 @@
mCheckBox =
(CheckBox) inflater.inflate(com.android.internal.R.layout.list_menu_item_checkbox,
this, false);
- addView(mCheckBox);
+ addContentView(mCheckBox);
}
public boolean prefersCondensedTitle() {
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index ee16ab6..164a745 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -17,6 +17,10 @@
package com.android.internal.widget;
import android.app.trust.IStrongAuthTracker;
+import android.os.Bundle;
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -52,4 +56,22 @@
boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
in byte[] token, int requestedQuality, int userId);
void unlockUserWithToken(long tokenHandle, in byte[] token, int userId);
+
+ // RecoverableKeyStoreLoader methods.
+ // {@code ServiceSpecificException} may be thrown to signal an error, which caller can
+ // convert to {@code RecoverableKeyStoreLoader}.
+ void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList,
+ int userId);
+ KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId);
+ void setServerParameters(long serverParameters, int userId);
+ void setRecoveryStatus(in String packageName, in String[] aliases, int status, int userId);
+ void setRecoverySecretTypes(in int[] secretTypes, int userId);
+ int[] getRecoverySecretTypes(int userId);
+ int[] getPendingRecoverySecretTypes(int userId);
+ void recoverySecretAvailable(in KeyStoreRecoveryMetadata recoverySecret, int userId);
+ byte[] startRecoverySession(in String sessionId,
+ in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge,
+ in List<KeyStoreRecoveryMetadata> secrets, int userId);
+ void recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
+ in List<KeyEntryRecoveryData> applicationKeys, int userId);
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8df0fb8..297bae2 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -123,6 +123,7 @@
"android_graphics_Picture.cpp",
"android/graphics/Bitmap.cpp",
"android/graphics/BitmapFactory.cpp",
+ "android/graphics/ByteBufferStreamAdaptor.cpp",
"android/graphics/Camera.cpp",
"android/graphics/CanvasProperty.cpp",
"android/graphics/ColorFilter.cpp",
@@ -134,6 +135,7 @@
"android/graphics/GraphicBuffer.cpp",
"android/graphics/Graphics.cpp",
"android/graphics/HarfBuzzNGFaceSkia.cpp",
+ "android/graphics/ImageDecoder.cpp",
"android/graphics/Interpolator.cpp",
"android/graphics/MaskFilter.cpp",
"android/graphics/Matrix.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 9e907bd..b5d1868 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -57,10 +57,12 @@
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
+extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Camera(JNIEnv* env);
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_GraphicBuffer(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
+extern int register_android_graphics_ImageDecoder(JNIEnv*);
extern int register_android_graphics_Interpolator(JNIEnv* env);
extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_Movie(JNIEnv* env);
@@ -644,6 +646,7 @@
char methodTraceFileBuf[sizeof("-Xmethod-trace-file:") + PROPERTY_VALUE_MAX];
char methodTraceFileSizeBuf[sizeof("-Xmethod-trace-file-size:") + PROPERTY_VALUE_MAX];
std::string fingerprintBuf;
+ char jdwpProviderBuf[sizeof("-XjdwpProvider:") - 1 + PROPERTY_VALUE_MAX];
bool checkJni = false;
property_get("dalvik.vm.checkjni", propBuf, "");
@@ -766,9 +769,15 @@
* Set suspend=y to pause during VM init and use android ADB transport.
*/
if (zygote) {
- addOption("-agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y");
+ addOption("-XjdwpOptions:suspend=n,server=y");
}
+ // Set the JDWP provider. By default let the runtime choose.
+ parseRuntimeOption("dalvik.vm.jdwp-provider",
+ jdwpProviderBuf,
+ "-XjdwpProvider:",
+ "default");
+
parseRuntimeOption("dalvik.vm.lockprof.threshold",
lockProfThresholdBuf,
"-Xlockprofthreshold:");
@@ -1380,6 +1389,7 @@
REG_JNI(register_android_graphics_Bitmap),
REG_JNI(register_android_graphics_BitmapFactory),
REG_JNI(register_android_graphics_BitmapRegionDecoder),
+ REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
REG_JNI(register_android_graphics_Camera),
REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
REG_JNI(register_android_graphics_CanvasProperty),
@@ -1387,6 +1397,7 @@
REG_JNI(register_android_graphics_DrawFilter),
REG_JNI(register_android_graphics_FontFamily),
REG_JNI(register_android_graphics_GraphicBuffer),
+ REG_JNI(register_android_graphics_ImageDecoder),
REG_JNI(register_android_graphics_Interpolator),
REG_JNI(register_android_graphics_MaskFilter),
REG_JNI(register_android_graphics_Matrix),
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 2e8c27a..79aa5ac 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -47,9 +47,6 @@
jfieldID gBitmap_ninePatchInsetsFieldID;
-jclass gInsetStruct_class;
-jmethodID gInsetStruct_constructorMethodID;
-
jclass gBitmapConfig_class;
jmethodID gBitmapConfig_nativeToConfigMethodID;
@@ -99,43 +96,6 @@
return jstr;
}
-static void scaleDivRange(int32_t* divs, int count, float scale, int maxValue) {
- for (int i = 0; i < count; i++) {
- divs[i] = int32_t(divs[i] * scale + 0.5f);
- if (i > 0 && divs[i] == divs[i - 1]) {
- divs[i]++; // avoid collisions
- }
- }
-
- if (CC_UNLIKELY(divs[count - 1] > maxValue)) {
- // if the collision avoidance above put some divs outside the bounds of the bitmap,
- // slide outer stretchable divs inward to stay within bounds
- int highestAvailable = maxValue;
- for (int i = count - 1; i >= 0; i--) {
- divs[i] = highestAvailable;
- if (i > 0 && divs[i] <= divs[i-1]){
- // keep shifting
- highestAvailable = divs[i] - 1;
- } else {
- break;
- }
- }
- }
-}
-
-static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale,
- int scaledWidth, int scaledHeight) {
- chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
- chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
- chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
- chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
-
- // The max value for the divRange is one pixel less than the actual max to ensure that the size
- // of the last div is not zero. A div of size 0 is considered invalid input and will not render.
- scaleDivRange(chunk->getXDivs(), chunk->numXDivs, scale, scaledWidth - 1);
- scaleDivRange(chunk->getYDivs(), chunk->numYDivs, scale, scaledHeight - 1);
-}
-
class ScaleCheckingAllocator : public SkBitmap::HeapAllocator {
public:
ScaleCheckingAllocator(float scale, int size)
@@ -425,10 +385,18 @@
return nullObjectReturn("codec->getAndroidPixels() failed.");
}
+ // This is weird so let me explain: we could use the scale parameter
+ // directly, but for historical reasons this is how the corresponding
+ // Dalvik code has always behaved. We simply recreate the behavior here.
+ // The result is slightly different from simply using scale because of
+ // the 0.5f rounding bias applied when computing the target image size
+ const float scaleX = scaledWidth / float(decodingBitmap.width());
+ const float scaleY = scaledHeight / float(decodingBitmap.height());
+
jbyteArray ninePatchChunk = NULL;
if (peeker.mPatch != NULL) {
if (willScale) {
- scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight);
+ peeker.scale(scaleX, scaleY, scaledWidth, scaledHeight);
}
size_t ninePatchArraySize = peeker.mPatch->serializedSize();
@@ -448,12 +416,7 @@
jobject ninePatchInsets = NULL;
if (peeker.mHasInsets) {
- ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
- peeker.mOpticalInsets[0], peeker.mOpticalInsets[1],
- peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
- peeker.mOutlineInsets[0], peeker.mOutlineInsets[1],
- peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
- peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
+ ninePatchInsets = peeker.createNinePatchInsets(env, scale);
if (ninePatchInsets == NULL) {
return nullObjectReturn("nine patch insets == null");
}
@@ -464,14 +427,6 @@
SkBitmap outputBitmap;
if (willScale) {
- // This is weird so let me explain: we could use the scale parameter
- // directly, but for historical reasons this is how the corresponding
- // Dalvik code has always behaved. We simply recreate the behavior here.
- // The result is slightly different from simply using scale because of
- // the 0.5f rounding bias applied when computing the target image size
- const float sx = scaledWidth / float(decodingBitmap.width());
- const float sy = scaledHeight / float(decodingBitmap.height());
-
// Set the allocator for the outputBitmap.
SkBitmap::Allocator* outputAllocator;
if (javaBitmap != nullptr) {
@@ -501,20 +456,14 @@
paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy);
- canvas.scale(sx, sy);
+ canvas.scale(scaleX, scaleY);
canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
} else {
outputBitmap.swap(decodingBitmap);
}
if (padding) {
- if (peeker.mPatch != NULL) {
- GraphicsJNI::set_jrect(env, padding,
- peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
- peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
- } else {
- GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
- }
+ peeker.getPadding(env, padding);
}
// If we get here, the outputBitmap should have an installed pixelref.
@@ -705,11 +654,6 @@
gBitmap_ninePatchInsetsFieldID = GetFieldIDOrDie(env, bitmap_class, "mNinePatchInsets",
"Landroid/graphics/NinePatch$InsetStruct;");
- gInsetStruct_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
- "android/graphics/NinePatch$InsetStruct"));
- gInsetStruct_constructorMethodID = GetMethodIDOrDie(env, gInsetStruct_class, "<init>",
- "(IIIIIIIIFIF)V");
-
gBitmapConfig_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
"android/graphics/Bitmap$Config"));
gBitmapConfig_nativeToConfigMethodID = GetStaticMethodIDOrDie(env, gBitmapConfig_class,
diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
new file mode 100644
index 0000000..115edd4
--- /dev/null
+++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
@@ -0,0 +1,323 @@
+#include "ByteBufferStreamAdaptor.h"
+#include "core_jni_helpers.h"
+
+#include <SkStream.h>
+
+using namespace android;
+
+static jmethodID gByteBuffer_getMethodID;
+static jmethodID gByteBuffer_setPositionMethodID;
+
+static JNIEnv* get_env_or_die(JavaVM* jvm) {
+ JNIEnv* env;
+ if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm);
+ }
+ return env;
+}
+
+class ByteBufferStream : public SkStreamAsset {
+private:
+ ByteBufferStream(JavaVM* jvm, jobject jbyteBuffer, size_t initialPosition, size_t length,
+ jbyteArray storage)
+ : mJvm(jvm)
+ , mByteBuffer(jbyteBuffer)
+ , mPosition(0)
+ , mInitialPosition(initialPosition)
+ , mLength(length)
+ , mStorage(storage) {}
+
+public:
+ static ByteBufferStream* Create(JavaVM* jvm, JNIEnv* env, jobject jbyteBuffer,
+ size_t position, size_t length) {
+ // This object outlives its native method call.
+ jbyteBuffer = env->NewGlobalRef(jbyteBuffer);
+ if (!jbyteBuffer) {
+ return nullptr;
+ }
+
+ jbyteArray storage = env->NewByteArray(kStorageSize);
+ if (!storage) {
+ env->DeleteGlobalRef(jbyteBuffer);
+ return nullptr;
+ }
+
+ // This object outlives its native method call.
+ storage = static_cast<jbyteArray>(env->NewGlobalRef(storage));
+ if (!storage) {
+ env->DeleteGlobalRef(jbyteBuffer);
+ return nullptr;
+ }
+
+ return new ByteBufferStream(jvm, jbyteBuffer, position, length, storage);
+ }
+
+ ~ByteBufferStream() override {
+ auto* env = get_env_or_die(mJvm);
+ env->DeleteGlobalRef(mByteBuffer);
+ env->DeleteGlobalRef(mStorage);
+ }
+
+ size_t read(void* buffer, size_t size) override {
+ if (size > mLength - mPosition) {
+ size = mLength - mPosition;
+ }
+ if (!size) {
+ return 0;
+ }
+
+ if (!buffer) {
+ return this->setPosition(mPosition + size);
+ }
+
+ auto* env = get_env_or_die(mJvm);
+ size_t bytesRead = 0;
+ do {
+ const size_t requested = (size > kStorageSize) ? kStorageSize : size;
+ const jint jrequested = static_cast<jint>(requested);
+ env->CallObjectMethod(mByteBuffer, gByteBuffer_getMethodID, mStorage, 0, jrequested);
+ if (env->ExceptionCheck()) {
+ ALOGE("Error in ByteBufferStream::read - was the ByteBuffer modified externally?");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ mPosition = mLength;
+ return bytesRead;
+ }
+
+ env->GetByteArrayRegion(mStorage, 0, requested, reinterpret_cast<jbyte*>(buffer));
+ if (env->ExceptionCheck()) {
+ ALOGE("Internal error in ByteBufferStream::read");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ mPosition = mLength;
+ return bytesRead;
+ }
+
+ mPosition += requested;
+ buffer = reinterpret_cast<void*>(reinterpret_cast<char*>(buffer) + requested);
+ bytesRead += requested;
+ size -= requested;
+ } while (size);
+ return bytesRead;
+ }
+
+ bool isAtEnd() const override { return mLength == mPosition; }
+
+ // SkStreamRewindable overrides
+ bool rewind() override { return this->setPosition(0); }
+
+ SkStreamAsset* onDuplicate() const override {
+ // SkStreamRewindable requires overriding this, but it is not called by
+ // decoders, so does not need a true implementation. A proper
+ // implementation would require duplicating the ByteBuffer, which has
+ // its own internal position state.
+ return nullptr;
+ }
+
+ // SkStreamSeekable overrides
+ size_t getPosition() const override { return mPosition; }
+
+ bool seek(size_t position) override {
+ return this->setPosition(position > mLength ? mLength : position);
+ }
+
+ bool move(long offset) override {
+ long newPosition = mPosition + offset;
+ if (newPosition < 0) {
+ return this->setPosition(0);
+ }
+ return this->seek(static_cast<size_t>(newPosition));
+ }
+
+ SkStreamAsset* onFork() const override {
+ // SkStreamSeekable requires overriding this, but it is not called by
+ // decoders, so does not need a true implementation. A proper
+ // implementation would require duplicating the ByteBuffer, which has
+ // its own internal position state.
+ return nullptr;
+ }
+
+ // SkStreamAsset overrides
+ size_t getLength() const override { return mLength; }
+
+private:
+ JavaVM* mJvm;
+ jobject mByteBuffer;
+ // Logical position of the SkStream, between 0 and mLength.
+ size_t mPosition;
+ // Initial position of mByteBuffer, treated as mPosition 0.
+ const size_t mInitialPosition;
+ // Logical length of the SkStream, from mInitialPosition to
+ // mByteBuffer.limit().
+ const size_t mLength;
+
+ // Range has already been checked by the caller.
+ bool setPosition(size_t newPosition) {
+ auto* env = get_env_or_die(mJvm);
+ env->CallObjectMethod(mByteBuffer, gByteBuffer_setPositionMethodID,
+ newPosition + mInitialPosition);
+ if (env->ExceptionCheck()) {
+ ALOGE("Internal error in ByteBufferStream::setPosition");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ mPosition = mLength;
+ return false;
+ }
+ mPosition = newPosition;
+ return true;
+ }
+
+ // FIXME: This is an arbitrary storage size, which should be plenty for
+ // some formats (png, gif, many bmps). But for jpeg, the more we can supply
+ // in one call the better, and webp really wants all of the data. How to
+ // best choose the amount of storage used?
+ static constexpr size_t kStorageSize = 4096;
+ jbyteArray mStorage;
+};
+
+class ByteArrayStream : public SkStreamAsset {
+private:
+ ByteArrayStream(JavaVM* jvm, jbyteArray jarray, size_t offset, size_t length)
+ : mJvm(jvm), mByteArray(jarray), mOffset(offset), mPosition(0), mLength(length) {}
+
+public:
+ static ByteArrayStream* Create(JavaVM* jvm, JNIEnv* env, jbyteArray jarray, size_t offset,
+ size_t length) {
+ // This object outlives its native method call.
+ jarray = static_cast<jbyteArray>(env->NewGlobalRef(jarray));
+ if (!jarray) {
+ return nullptr;
+ }
+ return new ByteArrayStream(jvm, jarray, offset, length);
+ }
+
+ ~ByteArrayStream() override {
+ auto* env = get_env_or_die(mJvm);
+ env->DeleteGlobalRef(mByteArray);
+ }
+
+ size_t read(void* buffer, size_t size) override {
+ if (size > mLength - mPosition) {
+ size = mLength - mPosition;
+ }
+ if (!size) {
+ return 0;
+ }
+
+ auto* env = get_env_or_die(mJvm);
+ if (buffer) {
+ env->GetByteArrayRegion(mByteArray, mPosition + mOffset, size,
+ reinterpret_cast<jbyte*>(buffer));
+ if (env->ExceptionCheck()) {
+ ALOGE("Internal error in ByteArrayStream::read");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ mPosition = mLength;
+ return 0;
+ }
+ }
+
+ mPosition += size;
+ return size;
+ }
+
+ bool isAtEnd() const override { return mLength == mPosition; }
+
+ // SkStreamRewindable overrides
+ bool rewind() override {
+ mPosition = 0;
+ return true;
+ }
+ SkStreamAsset* onDuplicate() const override {
+ // SkStreamRewindable requires overriding this, but it is not called by
+ // decoders, so does not need a true implementation. Note that a proper
+ // implementation is fairly straightforward
+ return nullptr;
+ }
+
+ // SkStreamSeekable overrides
+ size_t getPosition() const override { return mPosition; }
+
+ bool seek(size_t position) override {
+ mPosition = (position > mLength) ? mLength : position;
+ return true;
+ }
+
+ bool move(long offset) override {
+ long newPosition = mPosition + offset;
+ if (newPosition < 0) {
+ return this->seek(0);
+ }
+ return this->seek(static_cast<size_t>(newPosition));
+ }
+
+ SkStreamAsset* onFork() const override {
+ // SkStreamSeekable requires overriding this, but it is not called by
+ // decoders, so does not need a true implementation. Note that a proper
+ // implementation is fairly straightforward
+ return nullptr;
+ }
+
+ // SkStreamAsset overrides
+ size_t getLength() const override { return mLength; }
+
+private:
+ JavaVM* mJvm;
+ jbyteArray mByteArray;
+ // Offset in mByteArray. Only used when communicating with Java.
+ const size_t mOffset;
+ // Logical position of the SkStream, between 0 and mLength.
+ size_t mPosition;
+ const size_t mLength;
+};
+
+struct release_proc_context {
+ JavaVM* jvm;
+ jobject jbyteBuffer;
+};
+
+std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv* env, jobject jbyteBuffer,
+ size_t position, size_t limit) {
+ JavaVM* jvm;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);
+
+ const size_t length = limit - position;
+ void* addr = env->GetDirectBufferAddress(jbyteBuffer);
+ if (addr) {
+ addr = reinterpret_cast<void*>(reinterpret_cast<char*>(addr) + position);
+ jbyteBuffer = env->NewGlobalRef(jbyteBuffer);
+ if (!jbyteBuffer) {
+ return nullptr;
+ }
+
+ auto* context = new release_proc_context{jvm, jbyteBuffer};
+ auto releaseProc = [](const void*, void* context) {
+ auto* c = reinterpret_cast<release_proc_context*>(context);
+ JNIEnv* env = get_env_or_die(c->jvm);
+ env->DeleteGlobalRef(c->jbyteBuffer);
+ delete c;
+ };
+ auto data = SkData::MakeWithProc(addr, length, releaseProc, context);
+ // The new SkMemoryStream will read directly from addr.
+ return std::unique_ptr<SkStream>(new SkMemoryStream(std::move(data)));
+ }
+
+ // Non-direct, or direct access is not supported.
+ return std::unique_ptr<SkStream>(ByteBufferStream::Create(jvm, env, jbyteBuffer, position,
+ length));
+}
+
+std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv* env, jbyteArray array, size_t offset,
+ size_t length) {
+ JavaVM* jvm;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);
+
+ return std::unique_ptr<SkStream>(ByteArrayStream::Create(jvm, env, array, offset, length));
+}
+
+int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env) {
+ jclass byteBuffer_class = FindClassOrDie(env, "java/nio/ByteBuffer");
+ gByteBuffer_getMethodID = GetMethodIDOrDie(env, byteBuffer_class, "get", "([BII)Ljava/nio/ByteBuffer;");
+ gByteBuffer_setPositionMethodID = GetMethodIDOrDie(env, byteBuffer_class, "position", "(I)Ljava/nio/Buffer;");
+ return true;
+}
diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.h b/core/jni/android/graphics/ByteBufferStreamAdaptor.h
new file mode 100644
index 0000000..367a48f
--- /dev/null
+++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.h
@@ -0,0 +1,37 @@
+#ifndef _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
+#define _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
+
+#include <jni.h>
+#include <memory>
+
+class SkStream;
+
+/**
+ * Create an adaptor for treating a java.nio.ByteBuffer as an SkStream.
+ *
+ * This will special case direct ByteBuffers, but not the case where a byte[]
+ * can be used directly. For that, use CreateByteArrayStreamAdaptor.
+ *
+ * @param jbyteBuffer corresponding to the java ByteBuffer. This method will
+ * add a global ref.
+ * @param initialPosition returned by ByteBuffer.position(). Decoding starts
+ * from here.
+ * @param limit returned by ByteBuffer.limit().
+ *
+ * Returns null on failure.
+ */
+std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv*, jobject jbyteBuffer,
+ size_t initialPosition, size_t limit);
+
+/**
+ * Create an adaptor for treating a Java byte[] as an SkStream.
+ *
+ * @param offset into the byte[] of the beginning of the data to use.
+ * @param length of data to use, starting from offset.
+ *
+ * Returns null on failure.
+ */
+std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv*, jbyteArray array, size_t offset,
+ size_t length);
+
+#endif // _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
new file mode 100644
index 0000000..bacab2a
--- /dev/null
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Bitmap.h"
+#include "ByteBufferStreamAdaptor.h"
+#include "GraphicsJNI.h"
+#include "NinePatchPeeker.h"
+#include "Utils.h"
+#include "core_jni_helpers.h"
+
+#include <hwui/Bitmap.h>
+#include <hwui/Canvas.h>
+
+#include <SkAndroidCodec.h>
+#include <SkEncodedImageFormat.h>
+#include <SkStream.h>
+
+#include <androidfw/Asset.h>
+#include <jni.h>
+
+using namespace android;
+
+static jclass gImageDecoder_class;
+static jclass gPoint_class;
+static jclass gIncomplete_class;
+static jclass gCorrupt_class;
+static jclass gCanvas_class;
+static jmethodID gImageDecoder_constructorMethodID;
+static jmethodID gPoint_constructorMethodID;
+static jmethodID gIncomplete_constructorMethodID;
+static jmethodID gCorrupt_constructorMethodID;
+static jmethodID gCallback_onExceptionMethodID;
+static jmethodID gPostProcess_postProcessMethodID;
+static jmethodID gCanvas_constructorMethodID;
+static jmethodID gCanvas_releaseMethodID;
+
+struct ImageDecoder {
+ // These need to stay in sync with ImageDecoder.java's Allocator constants.
+ enum Allocator {
+ kDefault_Allocator = 0,
+ kSoftware_Allocator = 1,
+ kSharedMemory_Allocator = 2,
+ kHardware_Allocator = 3,
+ };
+
+ // These need to stay in sync with PixelFormat.java's Format constants.
+ enum PixelFormat {
+ kUnknown = 0,
+ kTranslucent = -3,
+ kOpaque = -1,
+ };
+
+ std::unique_ptr<SkAndroidCodec> mCodec;
+ NinePatchPeeker mPeeker;
+};
+
+static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
+ if (!stream.get()) {
+ return nullObjectReturn("Failed to create a stream");
+ }
+ std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
+ decoder->mCodec = SkAndroidCodec::MakeFromStream(std::move(stream), &decoder->mPeeker);
+ if (!decoder->mCodec.get()) {
+ // FIXME: Add an error code to SkAndroidCodec::MakeFromStream, like
+ // SkCodec? Then this can print a more informative error message.
+ // (Or we can print one from within SkCodec.)
+ ALOGE("Failed to create an SkCodec");
+ return nullptr;
+ }
+
+ const auto& info = decoder->mCodec->getInfo();
+ const int width = info.width();
+ const int height = info.height();
+ return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
+ reinterpret_cast<jlong>(decoder.release()), width, height);
+}
+
+static jobject ImageDecoder_nCreate(JNIEnv* env, jobject /*clazz*/, jlong assetPtr) {
+ Asset* asset = reinterpret_cast<Asset*>(assetPtr);
+ std::unique_ptr<SkStream> stream(new AssetStreamAdaptor(asset));
+ return native_create(env, std::move(stream));
+}
+
+static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/, jobject jbyteBuffer,
+ jint initialPosition, jint limit) {
+ std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer,
+ initialPosition, limit);
+ if (!stream) {
+ return nullptr;
+ }
+ return native_create(env, std::move(stream));
+}
+
+static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jbyteArray byteArray,
+ jint offset, jint length) {
+ std::unique_ptr<SkStream> stream(CreateByteArrayStreamAdaptor(env, byteArray, offset, length));
+ return native_create(env, std::move(stream));
+}
+
+static bool supports_any_down_scale(const SkAndroidCodec* codec) {
+ return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP;
+}
+
+static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jobject jcallback, jobject jpostProcess,
+ jint desiredWidth, jint desiredHeight, jobject jsubset,
+ jboolean requireMutable, jint allocator,
+ jboolean requireUnpremul, jboolean preferRamOverQuality,
+ jboolean asAlphaMask) {
+ auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+ SkAndroidCodec* codec = decoder->mCodec.get();
+ SkImageInfo decodeInfo = codec->getInfo();
+ bool scale = false;
+ int sampleSize = 1;
+ if (desiredWidth != decodeInfo.width() || desiredHeight != decodeInfo.height()) {
+ bool match = false;
+ if (desiredWidth < decodeInfo.width() && desiredHeight < decodeInfo.height()) {
+ if (supports_any_down_scale(codec)) {
+ match = true;
+ decodeInfo = decodeInfo.makeWH(desiredWidth, desiredHeight);
+ } else {
+ int sampleX = decodeInfo.width() / desiredWidth;
+ int sampleY = decodeInfo.height() / desiredHeight;
+ sampleSize = std::min(sampleX, sampleY);
+ SkISize sampledSize = codec->getSampledDimensions(sampleSize);
+ decodeInfo = decodeInfo.makeWH(sampledSize.width(), sampledSize.height());
+ if (decodeInfo.width() == desiredWidth && decodeInfo.height() == desiredHeight) {
+ match = true;
+ }
+ }
+ }
+ if (!match) {
+ scale = true;
+ if (requireUnpremul && kOpaque_SkAlphaType != decodeInfo.alphaType()) {
+ doThrowISE(env, "Cannot scale unpremultiplied pixels!");
+ return nullptr;
+ }
+ }
+ }
+
+ switch (decodeInfo.alphaType()) {
+ case kUnpremul_SkAlphaType:
+ if (!requireUnpremul) {
+ decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType);
+ }
+ break;
+ case kPremul_SkAlphaType:
+ if (requireUnpremul) {
+ decodeInfo = decodeInfo.makeAlphaType(kUnpremul_SkAlphaType);
+ }
+ break;
+ case kOpaque_SkAlphaType:
+ break;
+ case kUnknown_SkAlphaType:
+ return nullObjectReturn("Unknown alpha type");
+ }
+
+ SkColorType colorType = kN32_SkColorType;
+ if (asAlphaMask && decodeInfo.colorType() == kGray_8_SkColorType) {
+ // We have to trick Skia to decode this to a single channel.
+ colorType = kGray_8_SkColorType;
+ } else if (preferRamOverQuality) {
+ // FIXME: The post-process might add alpha, which would make a 565
+ // result incorrect. If we call the postProcess before now and record
+ // to a picture, we can know whether alpha was added, and if not, we
+ // can still use 565.
+ if (decodeInfo.alphaType() == kOpaque_SkAlphaType && !jpostProcess) {
+ // If the final result will be hardware, decoding to 565 and then
+ // uploading to the gpu as 8888 will not save memory. This still
+ // may save us from using F16, but do not go down to 565.
+ if (allocator != ImageDecoder::kHardware_Allocator &&
+ (allocator != ImageDecoder::kDefault_Allocator || requireMutable)) {
+ colorType = kRGB_565_SkColorType;
+ }
+ }
+ // Otherwise, stick with N32
+ } else {
+ // This is currently the only way to know that we should decode to F16.
+ colorType = codec->computeOutputColorType(colorType);
+ }
+ sk_sp<SkColorSpace> colorSpace = codec->computeOutputColorSpace(colorType);
+ decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace);
+
+ SkBitmap bm;
+ auto bitmapInfo = decodeInfo;
+ if (asAlphaMask && colorType == kGray_8_SkColorType) {
+ bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
+ }
+ if (!bm.setInfo(bitmapInfo)) {
+ return nullObjectReturn("Failed to setInfo properly");
+ }
+
+ sk_sp<Bitmap> nativeBitmap;
+ // If we are going to scale or subset, we will create a new bitmap later on,
+ // so use the heap for the temporary.
+ // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
+ if (allocator == ImageDecoder::kSharedMemory_Allocator && !scale && !jsubset) {
+ nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
+ } else {
+ nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+ }
+ if (!nativeBitmap) {
+ ALOGE("OOM allocating Bitmap with dimensions %i x %i",
+ decodeInfo.width(), decodeInfo.height());
+ doThrowOOME(env);
+ return nullptr;
+ }
+
+ jobject jexception = nullptr;
+ SkAndroidCodec::AndroidOptions options;
+ options.fSampleSize = sampleSize;
+ auto result = codec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes(), &options);
+ switch (result) {
+ case SkCodec::kSuccess:
+ break;
+ case SkCodec::kIncompleteInput:
+ if (jcallback) {
+ jexception = env->NewObject(gIncomplete_class, gIncomplete_constructorMethodID);
+ }
+ break;
+ case SkCodec::kErrorInInput:
+ if (jcallback) {
+ jexception = env->NewObject(gCorrupt_class, gCorrupt_constructorMethodID);
+ }
+ break;
+ default:
+ ALOGE("getPixels failed with error %i", result);
+ return nullptr;
+ }
+
+ if (jexception) {
+ if (!env->CallBooleanMethod(jcallback, gCallback_onExceptionMethodID, jexception) ||
+ env->ExceptionCheck()) {
+ return nullptr;
+ }
+ }
+
+ float scaleX = 1.0f;
+ float scaleY = 1.0f;
+ if (scale) {
+ scaleX = (float) desiredWidth / decodeInfo.width();
+ scaleY = (float) desiredHeight / decodeInfo.height();
+ }
+
+ jbyteArray ninePatchChunk = nullptr;
+ jobject ninePatchInsets = nullptr;
+
+ // Ignore ninepatch when post-processing.
+ if (!jpostProcess) {
+ // FIXME: Share more code with BitmapFactory.cpp.
+ if (decoder->mPeeker.mPatch != nullptr) {
+ if (scale) {
+ decoder->mPeeker.scale(scaleX, scaleY, desiredWidth, desiredHeight);
+ }
+ size_t ninePatchArraySize = decoder->mPeeker.mPatch->serializedSize();
+ ninePatchChunk = env->NewByteArray(ninePatchArraySize);
+ if (ninePatchChunk == nullptr) {
+ return nullObjectReturn("ninePatchChunk == null");
+ }
+
+ env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker.mPatchSize,
+ reinterpret_cast<jbyte*>(decoder->mPeeker.mPatch));
+ }
+
+ if (decoder->mPeeker.mHasInsets) {
+ ninePatchInsets = decoder->mPeeker.createNinePatchInsets(env, 1.0f);
+ if (ninePatchInsets == nullptr) {
+ return nullObjectReturn("nine patch insets == null");
+ }
+ }
+ }
+
+ if (scale || jsubset) {
+ int translateX = 0;
+ int translateY = 0;
+ if (jsubset) {
+ SkIRect subset;
+ GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
+
+ // FIXME: If there is no scale, should this instead call
+ // SkBitmap::extractSubset? If we could upload a subset
+ // (b/70626068), this would save memory and time. Even for a
+ // software Bitmap, the extra speed might be worth the memory
+ // tradeoff if the subset is large?
+ translateX = -subset.fLeft;
+ translateY = -subset.fTop;
+ desiredWidth = subset.width();
+ desiredHeight = subset.height();
+ }
+ SkImageInfo scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight);
+ SkBitmap scaledBm;
+ if (!scaledBm.setInfo(scaledInfo)) {
+ nullObjectReturn("Failed scaled setInfo");
+ }
+
+ sk_sp<Bitmap> scaledPixelRef;
+ if (allocator == ImageDecoder::kSharedMemory_Allocator) {
+ scaledPixelRef = Bitmap::allocateAshmemBitmap(&scaledBm);
+ } else {
+ scaledPixelRef = Bitmap::allocateHeapBitmap(&scaledBm);
+ }
+ if (!scaledPixelRef) {
+ ALOGE("OOM allocating scaled Bitmap with dimensions %i x %i",
+ desiredWidth, desiredHeight);
+ doThrowOOME(env);
+ return nullptr;
+ }
+
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc);
+ paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
+
+ SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy);
+ canvas.translate(translateX, translateY);
+ canvas.scale(scaleX, scaleY);
+ canvas.drawBitmap(bm, 0.0f, 0.0f, &paint);
+
+ bm.swap(scaledBm);
+ nativeBitmap = scaledPixelRef;
+ }
+
+ if (jpostProcess) {
+ std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm));
+ if (!canvas) {
+ return nullObjectReturn("Failed to create Canvas for PostProcess!");
+ }
+ jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
+ reinterpret_cast<jlong>(canvas.get()));
+ if (!jcanvas) {
+ return nullObjectReturn("Failed to create Java Canvas for PostProcess!");
+ }
+ // jcanvas will now own canvas.
+ canvas.release();
+
+ jint pixelFormat = env->CallIntMethod(jpostProcess, gPostProcess_postProcessMethodID,
+ jcanvas, bm.width(), bm.height());
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ // The Canvas objects are no longer needed, and will not remain valid.
+ env->CallVoidMethod(jcanvas, gCanvas_releaseMethodID);
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ SkAlphaType newAlphaType = bm.alphaType();
+ switch (pixelFormat) {
+ case ImageDecoder::kUnknown:
+ break;
+ case ImageDecoder::kTranslucent:
+ newAlphaType = kPremul_SkAlphaType;
+ break;
+ case ImageDecoder::kOpaque:
+ newAlphaType = kOpaque_SkAlphaType;
+ break;
+ default:
+ ALOGE("invalid return from postProcess: %i", pixelFormat);
+ doThrowIAE(env);
+ return nullptr;
+ }
+
+ if (newAlphaType != bm.alphaType()) {
+ if (!bm.setAlphaType(newAlphaType)) {
+ ALOGE("incompatible return from postProcess: %i", pixelFormat);
+ doThrowIAE(env);
+ return nullptr;
+ }
+ nativeBitmap->setAlphaType(newAlphaType);
+ }
+ }
+
+ int bitmapCreateFlags = 0x0;
+ if (!requireUnpremul) {
+ // Even if the image is opaque, setting this flag means that
+ // if alpha is added (e.g. by PostProcess), it will be marked as
+ // premultiplied.
+ bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Premultiplied;
+ }
+
+ if (requireMutable) {
+ bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Mutable;
+ } else {
+ if ((allocator == ImageDecoder::kDefault_Allocator ||
+ allocator == ImageDecoder::kHardware_Allocator)
+ && bm.colorType() != kAlpha_8_SkColorType)
+ {
+ sk_sp<Bitmap> hwBitmap = Bitmap::allocateHardwareBitmap(bm);
+ if (hwBitmap) {
+ hwBitmap->setImmutable();
+ return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
+ ninePatchChunk, ninePatchInsets);
+ }
+ if (allocator == ImageDecoder::kHardware_Allocator) {
+ return nullObjectReturn("failed to allocate hardware Bitmap!");
+ }
+ // If we failed to create a hardware bitmap, go ahead and create a
+ // software one.
+ }
+
+ nativeBitmap->setImmutable();
+ }
+ return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk,
+ ninePatchInsets);
+}
+
+static jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jint sampleSize) {
+ auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+ SkISize size = decoder->mCodec->getSampledDimensions(sampleSize);
+ return env->NewObject(gPoint_class, gPoint_constructorMethodID, size.width(), size.height());
+}
+
+static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jobject outPadding) {
+ auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+ decoder->mPeeker.getPadding(env, outPadding);
+}
+
+static void ImageDecoder_nRecycle(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
+ delete reinterpret_cast<ImageDecoder*>(nativePtr);
+}
+
+static const JNINativeMethod gImageDecoderMethods[] = {
+ { "nCreate", "(J)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreate },
+ { "nCreate", "(Ljava/nio/ByteBuffer;II)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer },
+ { "nCreate", "([BII)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray },
+ { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder$OnExceptionListener;Landroid/graphics/PostProcess;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;",
+ (void*) ImageDecoder_nDecodeBitmap },
+ { "nGetSampledSize","(JI)Landroid/graphics/Point;", (void*) ImageDecoder_nGetSampledSize },
+ { "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding },
+ { "nRecycle", "(J)V", (void*) ImageDecoder_nRecycle},
+};
+
+int register_android_graphics_ImageDecoder(JNIEnv* env) {
+ gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder"));
+ gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JII)V");
+
+ gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point"));
+ gPoint_constructorMethodID = GetMethodIDOrDie(env, gPoint_class, "<init>", "(II)V");
+
+ gIncomplete_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$IncompleteException"));
+ gIncomplete_constructorMethodID = GetMethodIDOrDie(env, gIncomplete_class, "<init>", "()V");
+
+ gCorrupt_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$CorruptException"));
+ gCorrupt_constructorMethodID = GetMethodIDOrDie(env, gCorrupt_class, "<init>", "()V");
+
+ jclass callback_class = FindClassOrDie(env, "android/graphics/ImageDecoder$OnExceptionListener");
+ gCallback_onExceptionMethodID = GetMethodIDOrDie(env, callback_class, "onException", "(Ljava/lang/Exception;)Z");
+
+ jclass postProcess_class = FindClassOrDie(env, "android/graphics/PostProcess");
+ gPostProcess_postProcessMethodID = GetMethodIDOrDie(env, postProcess_class, "postProcess", "(Landroid/graphics/Canvas;II)I");
+
+ gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas"));
+ gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "<init>", "(J)V");
+ gCanvas_releaseMethodID = GetMethodIDOrDie(env, gCanvas_class, "release", "()V");
+
+ return android::RegisterMethodsOrDie(env, "android/graphics/ImageDecoder", gImageDecoderMethods,
+ NELEM(gImageDecoderMethods));
+}
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index 564afeb..2619107 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -29,11 +29,15 @@
#include "SkLatticeIter.h"
#include "SkRegion.h"
#include "GraphicsJNI.h"
+#include "NinePatchPeeker.h"
#include "NinePatchUtils.h"
#include <nativehelper/JNIHelp.h>
#include "core_jni_helpers.h"
+jclass gInsetStruct_class;
+jmethodID gInsetStruct_constructorMethodID;
+
using namespace android;
/**
@@ -128,6 +132,30 @@
};
+jobject NinePatchPeeker::createNinePatchInsets(JNIEnv* env, float scale) const {
+ if (!mHasInsets) {
+ return nullptr;
+ }
+
+ return env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
+ mOpticalInsets[0], mOpticalInsets[1],
+ mOpticalInsets[2], mOpticalInsets[3],
+ mOutlineInsets[0], mOutlineInsets[1],
+ mOutlineInsets[2], mOutlineInsets[3],
+ mOutlineRadius, mOutlineAlpha, scale);
+}
+
+void NinePatchPeeker::getPadding(JNIEnv* env, jobject outPadding) const {
+ if (mPatch) {
+ GraphicsJNI::set_jrect(env, outPadding,
+ mPatch->paddingLeft, mPatch->paddingTop,
+ mPatch->paddingRight, mPatch->paddingBottom);
+
+ } else {
+ GraphicsJNI::set_jrect(env, outPadding, -1, -1, -1, -1);
+ }
+}
+
/////////////////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gNinePatchMethods[] = {
@@ -140,6 +168,10 @@
};
int register_android_graphics_NinePatch(JNIEnv* env) {
+ gInsetStruct_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
+ "android/graphics/NinePatch$InsetStruct"));
+ gInsetStruct_constructorMethodID = GetMethodIDOrDie(env, gInsetStruct_class, "<init>",
+ "(IIIIIIIIFIF)V");
return android::RegisterMethodsOrDie(env,
"android/graphics/NinePatch", gNinePatchMethods, NELEM(gNinePatchMethods));
}
diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp
index 1ea5650..9171fc6 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -16,7 +16,8 @@
#include "NinePatchPeeker.h"
-#include "SkBitmap.h"
+#include <SkBitmap.h>
+#include <cutils/compiler.h>
using namespace android;
@@ -46,3 +47,47 @@
}
return true; // keep on decoding
}
+
+static void scaleDivRange(int32_t* divs, int count, float scale, int maxValue) {
+ for (int i = 0; i < count; i++) {
+ divs[i] = int32_t(divs[i] * scale + 0.5f);
+ if (i > 0 && divs[i] == divs[i - 1]) {
+ divs[i]++; // avoid collisions
+ }
+ }
+
+ if (CC_UNLIKELY(divs[count - 1] > maxValue)) {
+ // if the collision avoidance above put some divs outside the bounds of the bitmap,
+ // slide outer stretchable divs inward to stay within bounds
+ int highestAvailable = maxValue;
+ for (int i = count - 1; i >= 0; i--) {
+ divs[i] = highestAvailable;
+ if (i > 0 && divs[i] <= divs[i-1]) {
+ // keep shifting
+ highestAvailable = divs[i] - 1;
+ } else {
+ break;
+ }
+ }
+ }
+}
+
+void NinePatchPeeker::scale(float scaleX, float scaleY, int scaledWidth, int scaledHeight) {
+ if (!mPatch) {
+ return;
+ }
+
+ // The max value for the divRange is one pixel less than the actual max to ensure that the size
+ // of the last div is not zero. A div of size 0 is considered invalid input and will not render.
+ if (!SkScalarNearlyEqual(scaleX, 1.0f)) {
+ mPatch->paddingLeft = int(mPatch->paddingLeft * scaleX + 0.5f);
+ mPatch->paddingRight = int(mPatch->paddingRight * scaleX + 0.5f);
+ scaleDivRange(mPatch->getXDivs(), mPatch->numXDivs, scaleX, scaledWidth - 1);
+ }
+
+ if (!SkScalarNearlyEqual(scaleY, 1.0f)) {
+ mPatch->paddingTop = int(mPatch->paddingTop * scaleY + 0.5f);
+ mPatch->paddingBottom = int(mPatch->paddingBottom * scaleY + 0.5f);
+ scaleDivRange(mPatch->getYDivs(), mPatch->numYDivs, scaleY, scaledHeight - 1);
+ }
+}
diff --git a/core/jni/android/graphics/NinePatchPeeker.h b/core/jni/android/graphics/NinePatchPeeker.h
index 126eab2..e4e58dd 100644
--- a/core/jni/android/graphics/NinePatchPeeker.h
+++ b/core/jni/android/graphics/NinePatchPeeker.h
@@ -20,7 +20,7 @@
#include "SkPngChunkReader.h"
#include <androidfw/ResourceTypes.h>
-class SkImageDecoder;
+#include <jni.h>
using namespace android;
@@ -42,9 +42,14 @@
bool readChunk(const char tag[], const void* data, size_t length) override;
+ jobject createNinePatchInsets(JNIEnv*, float scale) const;
+ void getPadding(JNIEnv*, jobject outPadding) const;
+ void scale(float scaleX, float scaleY, int scaledWidth, int scaledHeight);
+
Res_png_9patch* mPatch;
size_t mPatchSize;
bool mHasInsets;
+private:
int32_t mOpticalInsets[4];
int32_t mOutlineInsets[4];
float mOutlineRadius;
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 1522c20..1676d4b 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -25,7 +25,8 @@
#include <assert.h>
#include <dlfcn.h>
-#include <GLES/gl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
#include <ETC1/etc1.h>
#include <SkBitmap.h>
@@ -639,6 +640,10 @@
return 0;
}
break;
+ case kRGBA_F16_SkColorType:
+ if (type == GL_HALF_FLOAT_OES && format == PIXEL_FORMAT_RGBA_FP16)
+ return 0;
+ break;
default:
break;
}
@@ -656,6 +661,8 @@
return GL_RGBA;
case kRGB_565_SkColorType:
return GL_RGB;
+ case kRGBA_F16_SkColorType:
+ return PIXEL_FORMAT_RGBA_FP16;
default:
return -1;
}
@@ -672,6 +679,8 @@
return GL_UNSIGNED_BYTE;
case kRGB_565_SkColorType:
return GL_UNSIGNED_SHORT_5_6_5;
+ case kRGBA_F16_SkColorType:
+ return GL_HALF_FLOAT_OES;
default:
return -1;
}
diff --git a/core/proto/android/internal/processstats.proto b/core/proto/android/internal/processstats.proto
new file mode 100644
index 0000000..5629c2d
--- /dev/null
+++ b/core/proto/android/internal/processstats.proto
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package com.android.internal.app.procstats;
+
+message ProcessStatsProto {
+ enum MemoryFactor {
+ MEM_FACTOR_NORMAL = 0;
+ MEM_FACTOR_MODERATE = 1;
+ MEM_FACTOR_LOW = 2;
+ MEM_FACTOR_CRITICAL = 3;
+ }
+}
diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto
index a95fa57..522ff24 100644
--- a/core/proto/android/os/cpuinfo.proto
+++ b/core/proto/android/os/cpuinfo.proto
@@ -18,8 +18,6 @@
option java_multiple_files = true;
option java_outer_classname = "CpuInfoProto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
package android.os;
/**
@@ -31,8 +29,6 @@
message CpuInfo {
message TaskStats {
- option (stream_proto.stream_msg).enable_fields_mapping = true;
-
optional int32 total = 1; // total number of cpu tasks
optional int32 running = 2; // number of running tasks
optional int32 sleeping = 3; // number of sleeping tasks
@@ -42,8 +38,6 @@
optional TaskStats task_stats = 1;
message MemStats { // unit in kB
- option (stream_proto.stream_msg).enable_fields_mapping = true;
-
optional int32 total = 1;
optional int32 used = 2;
optional int32 free = 3;
@@ -54,8 +48,6 @@
optional MemStats swap = 3;
message CpuUsage { // unit is percentage %
- option (stream_proto.stream_msg).enable_fields_mapping = true;
-
optional int32 cpu = 1; // 400% cpu indicates 4 cores
optional int32 user = 2;
optional int32 nice = 3;
@@ -70,8 +62,6 @@
// Next Tag: 13
message Task {
- option (stream_proto.stream_msg).enable_fields_mapping = true;
-
optional int32 pid = 1;
optional int32 tid = 2;
optional string user = 3;
@@ -80,8 +70,6 @@
optional float cpu = 6; // precentage of cpu usage of the task
enum Status {
- option (stream_proto.stream_enum).enable_enums_mapping = true;
-
STATUS_UNKNOWN = 0;
STATUS_D = 1; // uninterruptible sleep
STATUS_R = 2; // running
@@ -95,8 +83,6 @@
// How Android memory manager will treat the task
enum Policy {
- option (stream_proto.stream_enum).enable_enums_mapping = true;
-
POLICY_UNKNOWN = 0;
POLICY_fg = 1; // foreground, the name is lower case for parsing the value
POLICY_bg = 2; // background, the name is lower case for parsing the value
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 4015a7e..ac8f26d 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -28,13 +28,14 @@
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto";
-import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
import "frameworks/base/core/proto/android/server/fingerprint.proto";
import "frameworks/base/core/proto/android/server/powermanagerservice.proto";
+import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
import "frameworks/base/core/proto/android/service/appwidget.proto";
import "frameworks/base/core/proto/android/service/battery.proto";
import "frameworks/base/core/proto/android/service/batterystats.proto";
import "frameworks/base/core/proto/android/service/diskstats.proto";
+import "frameworks/base/core/proto/android/service/graphicsstats.proto";
import "frameworks/base/core/proto/android/service/netstats.proto";
import "frameworks/base/core/proto/android/service/notification.proto";
import "frameworks/base/core/proto/android/service/package.proto";
@@ -176,6 +177,11 @@
optional com.android.server.am.proto.MemInfoProto meminfo = 3018 [
(section).type = SECTION_DUMPSYS,
- (section).args = "meminfo --proto"
+ (section).args = "meminfo -a --proto"
+ ];
+
+ optional android.service.GraphicsStatsServiceDumpProto graphicsstats = 3019 [
+ (section).type = SECTION_DUMPSYS,
+ (section).args = "graphicsstats --proto"
];
}
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index eaad37a..7e5be9d 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -18,8 +18,6 @@
option java_multiple_files = true;
option java_outer_classname = "WakeupSourcesProto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
package android.os;
message KernelWakeSources {
@@ -29,8 +27,6 @@
// Next Tag: 11
message WakeupSourceProto {
- option (stream_proto.stream_msg).enable_fields_mapping = true;
-
// Name of the event which triggers application processor
optional string name = 1;
diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto
index b86ee01..f82ea76 100644
--- a/core/proto/android/os/pagetypeinfo.proto
+++ b/core/proto/android/os/pagetypeinfo.proto
@@ -18,8 +18,6 @@
option java_multiple_files = true;
option java_outer_classname = "PageTypeInfoProto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
package android.os;
/*
@@ -63,7 +61,6 @@
// Next tag: 9
message BlockProto {
- option (stream_proto.stream_msg).enable_fields_mapping = true;
optional int32 node = 1;
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
index f684c84..204a5af 100644
--- a/core/proto/android/os/procrank.proto
+++ b/core/proto/android/os/procrank.proto
@@ -19,7 +19,6 @@
option java_outer_classname = "ProcrankProto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
package android.os;
@@ -36,7 +35,6 @@
// Next Tag: 11
message ProcessProto {
- option (stream_proto.stream_msg).enable_fields_mapping = true;
option (android.msg_privacy).dest = DEST_AUTOMATIC;
// ID of the process
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index 76a108b..a51253f 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -19,14 +19,12 @@
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
package android.os;
// Android Platform Exported System Properties
// TODO: This is not the completed list, new properties need to be whitelisted.
message SystemPropertiesProto {
- option (stream_proto.stream_msg).enable_fields_mapping_recursively = true;
// Properties that are not specified below would be appended here.
// These values stay on device only.
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 3af6f51..d3ca496 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -21,6 +21,7 @@
import "frameworks/base/core/proto/android/app/notification.proto";
import "frameworks/base/core/proto/android/content/intent.proto";
import "frameworks/base/core/proto/android/graphics/rect.proto";
+import "frameworks/base/core/proto/android/internal/processstats.proto";
import "frameworks/base/core/proto/android/os/looper.proto";
import "frameworks/base/core/proto/android/server/intentresolver.proto";
import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
@@ -163,7 +164,7 @@
optional int64 uptime_duration_ms = 1;
optional int64 elapsed_realtime_ms = 2;
- message NativeProcess {
+ message ProcessMemory {
optional int32 pid = 1;
optional string process_name = 2;
@@ -197,7 +198,7 @@
optional HeapInfo native_heap = 3;
optional HeapInfo dalvik_heap = 4;
repeated MemoryInfo other_heaps = 5;
- optional HeapInfo unknown_heap = 6;
+ optional MemoryInfo unknown_heap = 6;
// Summation of native_heap, dalvik_heap, and other_heaps.
optional HeapInfo total_heap = 7;
@@ -219,7 +220,113 @@
}
optional AppSummary app_summary = 9;
}
- repeated NativeProcess native_processes = 3;
+ repeated ProcessMemory native_processes = 3;
+
+ message AppData {
+ optional ProcessMemory process_memory = 1;
+
+ message ObjectStats {
+ optional int32 view_instance_count = 1;
+ optional int32 view_root_instance_count = 2;
+ optional int32 app_context_instance_count = 3;
+ optional int32 activity_instance_count = 4;
+ optional int32 global_asset_count = 5;
+ optional int32 global_asset_manager_count = 6;
+ optional int32 local_binder_object_count = 7;
+ optional int32 proxy_binder_object_count = 8;
+ optional int64 parcel_memory_kb = 9;
+ optional int32 parcel_count = 10;
+ optional int32 binder_object_death_count = 11;
+ optional int32 open_ssl_socket_count = 12;
+ optional int32 webview_instance_count = 13;
+ }
+ optional ObjectStats objects = 2;
+
+ message SqlStats {
+ optional int32 memory_used_kb = 1;
+ optional int32 pagecache_overflow_kb = 2;
+ optional int32 malloc_size_kb = 3;
+
+ message Database {
+ optional string name = 1;
+ optional int32 page_size = 2;
+ optional int32 db_size = 3;
+ // Number of lookaside slots:
+ // http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html
+ optional int32 lookaside_b = 4;
+ // Statement cache stats: hits/misses/cachesize
+ optional string cache = 5;
+ }
+ repeated Database databases = 4;
+ }
+ optional SqlStats sql = 3;
+
+ optional string asset_allocations = 4;
+ optional string unreachable_memory = 5;
+ }
+ repeated AppData app_processes = 4;
+
+ message MemItem {
+ optional string tag = 1;
+ optional string label = 2;
+ optional int32 id = 3;
+ optional bool is_proc = 4;
+ optional bool has_activities = 5;
+ optional int64 pss_kb = 6;
+ optional int64 swap_pss_kb = 7;
+ repeated MemItem sub_items = 8;
+ }
+ repeated MemItem total_pss_by_process = 5;
+ repeated MemItem total_pss_by_oom_adjustment = 6;
+ repeated MemItem total_pss_by_category = 7;
+
+ optional int64 total_ram_kb = 8;
+ optional .com.android.internal.app.procstats.ProcessStatsProto.MemoryFactor status = 9;
+ // Total free RAM = cached_pss_kb + cached_kernel_kb + free_kb.
+ optional int64 cached_pss_kb = 10;
+ optional int64 cached_kernel_kb = 11;
+ optional int64 free_kb = 12;
+ // Total used RAM = used_pss_kb + used_kernel_kb.
+ optional int64 used_pss_kb = 13;
+ optional int64 used_kernel_kb = 14;
+
+ optional int64 lost_ram_kb = 15;
+
+ optional int64 total_zram_kb = 16;
+ optional int64 zram_physical_used_in_swap_kb = 17;
+ optional int64 total_zram_swap_kb = 18;
+
+ optional int64 ksm_sharing_kb = 19;
+ optional int64 ksm_shared_kb = 20;
+ optional int64 ksm_unshared_kb = 21;
+ optional int64 ksm_volatile_kb = 22;
+
+ // The approximate per-application memory class of the current device. This
+ // gives developers an idea of how hard a memory limit you should impose on
+ // their application to let the overall system work best. The value is in
+ // megabytes; the baseline Android memory class is 16 (which happens to be the
+ // Java heap limit of those devices); some devices with more memory may have
+ // 24 or even higher numbers.
+ optional int32 tuning_mb = 23;
+ // The approximate per-application memory class of the current device when an
+ // application is running with a large heap. This is the space available for
+ // memory-intensive applications; most applications should not need this
+ // amount of memory, and should instead stay with the tuning_mb limit. The
+ // value is in megabytes. This may be the same size as tuning_mb on memory
+ // constrained devices, or it may be significantly larger on devices with a
+ // large amount of available RAM.
+ // This is the size of the application's Dalvik heap if it has specified
+ // 'android:largeHeap="true"' in its manifest.
+ optional int32 tuning_large_mb = 24;
+
+ optional int64 oom_kb = 25;
+
+ // The maximum pss size in kb that we consider a process acceptable to restore
+ // from its cached state for running in the background when RAM is low.
+ optional int64 restore_limit_kb = 26;
+
+ optional bool is_low_ram_device = 27;
+ optional bool is_high_end_gfx = 28;
}
message StickyBroadcastProto {
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index 801411d..f422065 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -19,7 +19,6 @@
option java_multiple_files = true;
option java_outer_classname = "GraphicsStatsServiceProto";
-option optimize_for = LITE_RUNTIME;
message GraphicsStatsServiceDumpProto {
repeated GraphicsStatsProto stats = 1;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 15e439e..13fedfe 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1620,6 +1620,11 @@
<permission android:name="android.permission.ACCESS_PDB_STATE"
android:protectionLevel="signature" />
+ <!-- Allows testing if a passwords is forbidden by the admins.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.TEST_BLACKLISTED_PASSWORD"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows system update service to notify device owner about pending updates.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE"
diff --git a/core/res/res/drawable/ic_wifi_settings.xml b/core/res/res/drawable/ic_wifi_settings.xml
new file mode 100644
index 0000000..c678ad4
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_settings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M12.584,15.93c0.026-0.194,0.044-0.397,0.044-0.608c0-0.211-0.018-0.405-0.044-0.608l1.304-1.022
+c0.115-0.088,0.15-0.256,0.071-0.397l-1.234-2.133c-0.071-0.132-0.238-0.185-0.379-0.132l-1.533,0.617
+c-0.317-0.247-0.67-0.449-1.04-0.608L9.535,9.4c-0.018-0.132-0.141-0.247-0.3-0.247H6.768c-0.15,0-0.282,0.115-0.3,0.256
+L6.23,11.048c-0.379,0.159-0.723,0.361-1.04,0.608l-1.533-0.617c-0.141-0.053-0.3,0-0.379,0.132l-1.234,2.133
+c-0.079,0.132-0.044,0.3,0.07,0.397l1.304,1.022c-0.026,0.194-0.044,0.405-0.044,0.608s0.018,0.405,0.044,0.608l-1.304,1.022
+c-0.115,0.088-0.15,0.256-0.07,0.397l1.234,2.133c0.07,0.132,0.238,0.185,0.379,0.132l1.533-0.617
+c0.317,0.247,0.67,0.449,1.04,0.608l0.238,1.639c0.018,0.15,0.15,0.256,0.3,0.256h2.467c0.159,0,0.282-0.115,0.3-0.256
+l0.238-1.639c0.379-0.15,0.723-0.361,1.04-0.608l1.533,0.617c0.141,0.053,0.3,0,0.379-0.132l1.234-2.133
+c0.071-0.132,0.044-0.3-0.07-0.397L12.584,15.93z
+M8.002,17.481c-1.19,0-2.159-0.969-2.159-2.159s0.969-2.159,2.159-2.159
+s2.159,0.969,2.159,2.159C10.161,16.512,9.191,17.481,8.002,17.481z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M16.003,12.026l5.995-7.474c-0.229-0.172-2.537-2.06-6-2.06s-5.771,1.889-6,2.06l5.995,7.469l0.005,0.01L16.003,12.026z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml
index 865685f..435289d 100644
--- a/core/res/res/layout/notification_template_material_ambient.xml
+++ b/core/res/res/layout/notification_template_material_ambient.xml
@@ -72,7 +72,7 @@
android:textColor="#eeffffff"
android:layout_marginTop="4dp"
android:ellipsize="end"
- android:maxLines="7"
+ android:maxLines="3"
/>
</LinearLayout>
</LinearLayout>
diff --git a/core/res/res/layout/popup_menu_item_layout.xml b/core/res/res/layout/popup_menu_item_layout.xml
index 3eecb36..3b89f0d 100644
--- a/core/res/res/layout/popup_menu_item_layout.xml
+++ b/core/res/res/layout/popup_menu_item_layout.xml
@@ -33,6 +33,7 @@
<!-- The title and summary have some gap between them,
and this 'group' should be centered vertically. -->
<LinearLayout
+ android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="?attr/dropdownListPreferredItemHeight"
android:paddingEnd="16dip"
diff --git a/core/res/res/values-af/required_apps_managed_device.xml b/core/res/res/values-af/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-af/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-af/required_apps_managed_profile.xml b/core/res/res/values-af/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-af/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-af/required_apps_managed_user.xml b/core/res/res/values-af/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-af/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-am/required_apps_managed_device.xml b/core/res/res/values-am/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-am/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-am/required_apps_managed_profile.xml b/core/res/res/values-am/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-am/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-am/required_apps_managed_user.xml b/core/res/res/values-am/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-am/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ar/required_apps_managed_device.xml b/core/res/res/values-ar/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ar/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ar/required_apps_managed_profile.xml b/core/res/res/values-ar/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ar/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ar/required_apps_managed_user.xml b/core/res/res/values-ar/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ar/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index c9611e4..2f69bd5 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -66,15 +66,15 @@
<string name="PwdMmi" msgid="7043715687905254199">"تغيير كلمة المرور"</string>
<string name="PinMmi" msgid="3113117780361190304">"تغيير رمز PIN"</string>
<string name="CnipMmi" msgid="3110534680557857162">"رقم الاتصال موجود"</string>
- <string name="CnirMmi" msgid="3062102121430548731">"رقم الاتصال مقيّد"</string>
+ <string name="CnirMmi" msgid="3062102121430548731">"رقم الاتصال محظور"</string>
<string name="ThreeWCMmi" msgid="9051047170321190368">"اتصال ثلاثي"</string>
<string name="RuacMmi" msgid="7827887459138308886">"رفض المكالمات المزعجة غير المرغوب فيها"</string>
<string name="CndMmi" msgid="3116446237081575808">"تسليم رقم الاتصال"</string>
<string name="DndMmi" msgid="1265478932418334331">"عدم الإزعاج"</string>
- <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"الإعداد الافتراضي لمعرف المتصل هو مقيّد. الاتصال التالي: مقيّد"</string>
- <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"الإعداد الافتراضي لمعرف المتصل هو مقيّد. الاتصال التالي: غير مقيّد"</string>
- <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"الإعداد الافتراضي لمعرف المتصل هو غير مقيّد. الاتصال التالي: مقيّد"</string>
- <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"الإعداد الافتراضي لمعرف المتصل هو غير مقيّد. الاتصال التالي: غير مقيّد"</string>
+ <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"الإعداد الافتراضي لمعرف المتصل هو محظور . الاتصال التالي: محظور"</string>
+ <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"الإعداد الافتراضي لمعرف المتصل هو محظور . الاتصال التالي: غير محظور"</string>
+ <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"الإعداد الافتراضي لمعرف المتصل هو غير محظور . الاتصال التالي: محظور"</string>
+ <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"الإعداد الافتراضي لمعرف المتصل هو غير محظور . الاتصال التالي: غير محظور"</string>
<string name="serviceNotProvisioned" msgid="8614830180508686666">"الخدمة غير متوفرة."</string>
<string name="CLIRPermanent" msgid="3377371145926835671">"لا يمكنك تغيير إعداد معرّف المتصل."</string>
<string name="RestrictedOnDataTitle" msgid="1322504692764166532">"ليست هناك خدمة بيانات"</string>
diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-be/required_apps_managed_device.xml b/core/res/res/values-be/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-be/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-be/required_apps_managed_profile.xml b/core/res/res/values-be/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-be/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-be/required_apps_managed_user.xml b/core/res/res/values-be/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-be/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-bg/required_apps_managed_device.xml b/core/res/res/values-bg/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-bg/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-bg/required_apps_managed_profile.xml b/core/res/res/values-bg/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-bg/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-bg/required_apps_managed_user.xml b/core/res/res/values-bg/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-bg/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-bn/required_apps_managed_device.xml b/core/res/res/values-bn/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-bn/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-bn/required_apps_managed_profile.xml b/core/res/res/values-bn/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-bn/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-bn/required_apps_managed_user.xml b/core/res/res/values-bn/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-bn/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 073fa12..fa3c9b1d 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1695,11 +1695,9 @@
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD অনুরোধটিকে ডায়াল অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD অনুরোধটিকে SS অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD অনুরোধটিকে নতুন USSD অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
- <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
- <skip />
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD অনুরোধটিকে ভিডিও DIAL অনুরোধে পরিবর্তন করা হয়েছে।"</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS অনুরোধটিকে ডায়াল অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
- <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
- <skip />
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS অনুরোধটিকে ভিডিও DIAL অনুরোধে পরিবর্তন করা হয়েছে।"</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS অনুরোধটিকে নতুন USSD অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS অনুরোধটিকে নতুন SS অনুরোধে রুপান্তরিত করা হয়েছে৷"</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"কর্মস্থলের প্রোফাইল"</string>
diff --git a/core/res/res/values-ca/required_apps_managed_device.xml b/core/res/res/values-ca/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ca/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ca/required_apps_managed_profile.xml b/core/res/res/values-ca/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ca/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ca/required_apps_managed_user.xml b/core/res/res/values-ca/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ca/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-cs/required_apps_managed_device.xml b/core/res/res/values-cs/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-cs/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-cs/required_apps_managed_profile.xml b/core/res/res/values-cs/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-cs/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-cs/required_apps_managed_user.xml b/core/res/res/values-cs/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-cs/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-da/required_apps_managed_device.xml b/core/res/res/values-da/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-da/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-da/required_apps_managed_profile.xml b/core/res/res/values-da/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-da/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-da/required_apps_managed_user.xml b/core/res/res/values-da/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-da/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-de/required_apps_managed_device.xml b/core/res/res/values-de/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-de/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-de/required_apps_managed_profile.xml b/core/res/res/values-de/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-de/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-de/required_apps_managed_user.xml b/core/res/res/values-de/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-de/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-el/required_apps_managed_device.xml b/core/res/res/values-el/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-el/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-el/required_apps_managed_profile.xml b/core/res/res/values-el/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-el/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-el/required_apps_managed_user.xml b/core/res/res/values-el/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-el/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-es-rUS/required_apps_managed_device.xml b/core/res/res/values-es-rUS/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-es-rUS/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-es-rUS/required_apps_managed_profile.xml b/core/res/res/values-es-rUS/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-es-rUS/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-es-rUS/required_apps_managed_user.xml b/core/res/res/values-es-rUS/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-es-rUS/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-et/required_apps_managed_device.xml b/core/res/res/values-et/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-et/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-et/required_apps_managed_profile.xml b/core/res/res/values-et/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-et/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-et/required_apps_managed_user.xml b/core/res/res/values-et/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-et/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-eu/required_apps_managed_device.xml b/core/res/res/values-eu/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-eu/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-eu/required_apps_managed_profile.xml b/core/res/res/values-eu/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-eu/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-eu/required_apps_managed_user.xml b/core/res/res/values-eu/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-eu/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-fa/required_apps_managed_device.xml b/core/res/res/values-fa/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-fa/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-fa/required_apps_managed_profile.xml b/core/res/res/values-fa/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-fa/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-fa/required_apps_managed_user.xml b/core/res/res/values-fa/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-fa/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-fi/required_apps_managed_device.xml b/core/res/res/values-fi/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-fi/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-fi/required_apps_managed_profile.xml b/core/res/res/values-fi/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-fi/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-fi/required_apps_managed_user.xml b/core/res/res/values-fi/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-fi/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-fr-rCA/required_apps_managed_device.xml b/core/res/res/values-fr-rCA/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-fr-rCA/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-fr-rCA/required_apps_managed_profile.xml b/core/res/res/values-fr-rCA/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-fr-rCA/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-fr-rCA/required_apps_managed_user.xml b/core/res/res/values-fr-rCA/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-fr-rCA/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-gl/required_apps_managed_device.xml b/core/res/res/values-gl/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-gl/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-gl/required_apps_managed_profile.xml b/core/res/res/values-gl/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-gl/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-gl/required_apps_managed_user.xml b/core/res/res/values-gl/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-gl/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index b5aab39..b4e3904 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -977,7 +977,7 @@
<string name="textSelectionCABTitle" msgid="5236850394370820357">"Selección de texto"</string>
<string name="addToDictionary" msgid="4352161534510057874">"Engadir ao dicionario"</string>
<string name="deleteText" msgid="6979668428458199034">"Eliminar"</string>
- <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
+ <string name="inputMethod" msgid="1653630062304567879">"Método de introdución de texto"</string>
<string name="editTextMenuTitle" msgid="4909135564941815494">"Accións de texto"</string>
<string name="email" msgid="4560673117055050403">"Correo electrónico"</string>
<string name="dial" msgid="1253998302767701559">"Chamar"</string>
@@ -1288,7 +1288,7 @@
<string name="permission_request_notification_with_subtitle" msgid="8530393139639560189">"Permiso solicitado\npara a conta <xliff:g id="ACCOUNT">%s</xliff:g>."</string>
<string name="forward_intent_to_owner" msgid="1207197447013960896">"Estás usando esta aplicación fóra do teu perfil de traballo"</string>
<string name="forward_intent_to_work" msgid="621480743856004612">"Estás usando esta aplicación no teu perfil de traballo"</string>
- <string name="input_method_binding_label" msgid="1283557179944992649">"Método de entrada"</string>
+ <string name="input_method_binding_label" msgid="1283557179944992649">"Método de introdución de texto"</string>
<string name="sync_binding_label" msgid="3687969138375092423">"Sincronizar"</string>
<string name="accessibility_binding_label" msgid="4148120742096474641">"Accesibilidade"</string>
<string name="wallpaper_binding_label" msgid="1240087844304687662">"Fondo de pantalla"</string>
diff --git a/core/res/res/values-gu/required_apps_managed_device.xml b/core/res/res/values-gu/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-gu/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-gu/required_apps_managed_profile.xml b/core/res/res/values-gu/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-gu/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-gu/required_apps_managed_user.xml b/core/res/res/values-gu/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-gu/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index cff1a85..eada633 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1695,11 +1695,9 @@
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD વિનંતીને DIAL વિનંતી પર સંશોધિત કરી."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD વિનંતીને SS વિનંતી પર સંશોધિત કરી."</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD વિનંતીને નવી USSD વિનંતી પર સંશોધિત કરી."</string>
- <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
- <skip />
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD વિનંતીને વીડિઓ DIAL વિનંતીમાં સંશોધિત કરી."</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS વિનંતીને DIAL વિનંતી પર સંશોધિત કરી."</string>
- <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
- <skip />
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS વિનંતીને વીડિઓ DIAL વિનંતીમાં સંશોધિત કરી."</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS વિનંતીને USSD વિનંતી પર સંશોધિત કરી."</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS વિનંતીને નવી SS વિનંતી પર સંશોધિત કરી."</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"કાર્યાલયની પ્રોફાઇલ"</string>
diff --git a/core/res/res/values-hi/required_apps_managed_device.xml b/core/res/res/values-hi/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-hi/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-hi/required_apps_managed_profile.xml b/core/res/res/values-hi/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-hi/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-hi/required_apps_managed_user.xml b/core/res/res/values-hi/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-hi/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-hr/required_apps_managed_device.xml b/core/res/res/values-hr/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-hr/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-hr/required_apps_managed_profile.xml b/core/res/res/values-hr/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-hr/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-hr/required_apps_managed_user.xml b/core/res/res/values-hr/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-hr/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-hu/required_apps_managed_device.xml b/core/res/res/values-hu/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-hu/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-hu/required_apps_managed_profile.xml b/core/res/res/values-hu/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-hu/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-hu/required_apps_managed_user.xml b/core/res/res/values-hu/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-hu/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-hy/required_apps_managed_device.xml b/core/res/res/values-hy/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-hy/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-hy/required_apps_managed_profile.xml b/core/res/res/values-hy/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-hy/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-hy/required_apps_managed_user.xml b/core/res/res/values-hy/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-hy/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-is/required_apps_managed_device.xml b/core/res/res/values-is/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-is/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-is/required_apps_managed_profile.xml b/core/res/res/values-is/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-is/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-is/required_apps_managed_user.xml b/core/res/res/values-is/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-is/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-iw/required_apps_managed_device.xml b/core/res/res/values-iw/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-iw/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-iw/required_apps_managed_profile.xml b/core/res/res/values-iw/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-iw/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-iw/required_apps_managed_user.xml b/core/res/res/values-iw/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-iw/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ja/required_apps_managed_device.xml b/core/res/res/values-ja/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ja/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ja/required_apps_managed_profile.xml b/core/res/res/values-ja/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ja/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ja/required_apps_managed_user.xml b/core/res/res/values-ja/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ja/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-kk/required_apps_managed_device.xml b/core/res/res/values-kk/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-kk/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-kk/required_apps_managed_profile.xml b/core/res/res/values-kk/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-kk/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-kk/required_apps_managed_user.xml b/core/res/res/values-kk/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-kk/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-km/required_apps_managed_device.xml b/core/res/res/values-km/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-km/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-km/required_apps_managed_profile.xml b/core/res/res/values-km/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-km/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-km/required_apps_managed_user.xml b/core/res/res/values-km/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-km/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index b2778a1..7be15f1 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1696,9 +1696,9 @@
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"សំណើរ USSD ត្រូវបានកែសម្រួលទៅតាមសំណើរការហៅទូរស័ព្ទ។"</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"សំណើរ USSD ត្រូវបានកែសម្រួលទៅតាមសំណើរ SS។"</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"សំណើរ USSD ត្រូវបានកែសម្រួលទៅតាមសំណើរ USSD ថ្មី។្"</string>
- <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"សំណើ USSD ត្រូវបានកែសម្រួលទៅជាសំណើ Video DIAL ។"</string>
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"សំណើ USSD ត្រូវបានកែប្រែទៅជាសំណើ DIAL ជាវីដេអូ។"</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"សំណើរ SS ត្រូវបានកែសម្រួលទៅតាមសំណើរការហៅទូរស័ព្ទ។"</string>
- <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"សំណើ SS ត្រូវបានកែសម្រួលទៅជាសំណើ Video DIAL ។"</string>
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"សំណើ SS ត្រូវបានកែប្រែទៅជាសំណើ DIAL ជាវីដេអូ។"</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"សំណើរ SS ត្រូវបានកែសម្រួលទៅតាមសំណើរ USSD។"</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"សំណើរ SS ត្រូវបានកែសម្រួលទៅតាមសំណើរ SS ថ្មី។"</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"ប្រវត្តិរូបការងារ"</string>
diff --git a/core/res/res/values-kn/required_apps_managed_device.xml b/core/res/res/values-kn/required_apps_managed_device.xml
new file mode 100644
index 0000000..e15b0b0
--- /dev/null
+++ b/core/res/res/values-kn/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.ಸೆಟ್ಟಿಂಗ್ಗಳು"</item>
+ <item msgid="7004798084799227194">"com.android.ಸಂಪರ್ಕಗಳು"</item>
+ <item msgid="5782220690863647256">"com.android.ಡಯಲರ್"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್ಲೋಡ್ಗಳು"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-kn/required_apps_managed_profile.xml b/core/res/res/values-kn/required_apps_managed_profile.xml
new file mode 100644
index 0000000..92ab682
--- /dev/null
+++ b/core/res/res/values-kn/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.ಸಂಪರ್ಕಗಳು"</item>
+ <item msgid="4633145750237794002">"com.android.ಸೆಟ್ಟಿಂಗ್ಗಳು"</item>
+ <item msgid="6518205098643077579">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್ಲೋಡ್ಗಳು"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-kn/required_apps_managed_user.xml b/core/res/res/values-kn/required_apps_managed_user.xml
new file mode 100644
index 0000000..0c59edd
--- /dev/null
+++ b/core/res/res/values-kn/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.ಸೆಟ್ಟಿಂಗ್ಗಳು"</item>
+ <item msgid="2250259015310893915">"com.android.ಸಂಪರ್ಕಗಳು"</item>
+ <item msgid="7166574999426592423">"com.android.ಡಯಲರ್"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್ಲೋಡ್ಗಳು"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 4485561..99db9e6 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1695,11 +1695,9 @@
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD ವಿನಂತಿಯನ್ನು DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD ವಿನಂತಿಯನ್ನು SS ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD ವಿನಂತಿಯನ್ನು ಹೊಸ USSD ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
- <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
- <skip />
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD ವಿನಂತಿಯನ್ನು ವೀಡಿಯೊ DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS ವಿನಂತಿಯನ್ನು DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
- <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
- <skip />
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS ವಿನಂತಿಯನ್ನು ವೀಡಿಯೊ DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS ವಿನಂತಿಯನ್ನು USSD ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS ವಿನಂತಿಯನ್ನು ಹೊಸ SS ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್"</string>
diff --git a/core/res/res/values-ko/required_apps_managed_device.xml b/core/res/res/values-ko/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ko/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ko/required_apps_managed_profile.xml b/core/res/res/values-ko/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ko/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ko/required_apps_managed_user.xml b/core/res/res/values-ko/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ko/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ky/required_apps_managed_device.xml b/core/res/res/values-ky/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ky/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ky/required_apps_managed_profile.xml b/core/res/res/values-ky/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ky/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ky/required_apps_managed_user.xml b/core/res/res/values-ky/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ky/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index e580a59..cfa05de 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -212,7 +212,7 @@
<string name="global_action_power_off" msgid="4471879440839879722">"Кубатын өчүрүү"</string>
<string name="global_action_emergency" msgid="7112311161137421166">"Тез жардам"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Ката тууралуу билдирүү"</string>
- <string name="global_action_logout" msgid="935179188218826050">"Сессияны аяктоо"</string>
+ <string name="global_action_logout" msgid="935179188218826050">"Сеансты бүтүрүү"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Ката тууралуу билдирүү түзүү"</string>
<string name="bugreport_message" msgid="398447048750350456">"Ушуну менен түзмөгүңүздүн учурдагы абалы тууралуу маалымат топтолуп, электрондук почта аркылуу жөнөтүлөт. Отчет даяр болгуча бир аз күтө туруңуз."</string>
<string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Интерактивдүү кабар"</string>
@@ -1502,7 +1502,7 @@
<string name="disable_accessibility_shortcut" msgid="627625354248453445">"Кыска жолду өчүрүү"</string>
<string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Кыска жолду колдонуу"</string>
<string name="color_inversion_feature_name" msgid="4231186527799958644">"Түстү инверсиялоо"</string>
- <string name="color_correction_feature_name" msgid="6779391426096954933">"Түстү түзөтүү"</string>
+ <string name="color_correction_feature_name" msgid="6779391426096954933">"Түсүн тууралоо"</string>
<string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Атайын мүмкүнчүлүктөр кыска жолу <xliff:g id="SERVICE_NAME">%1$s</xliff:g> кызматын күйгүздү"</string>
<string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Атайын мүмкүнчүлүктөр кыска жолу <xliff:g id="SERVICE_NAME">%1$s</xliff:g> кызматын өчүрдү"</string>
<string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Атайын мүмкүнчүлүктөр баскычын таптаганыңызда иштетиле турган функцияны тандаңыз:"</string>
diff --git a/core/res/res/values-lo/required_apps_managed_device.xml b/core/res/res/values-lo/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-lo/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-lo/required_apps_managed_profile.xml b/core/res/res/values-lo/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-lo/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-lo/required_apps_managed_user.xml b/core/res/res/values-lo/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-lo/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-lt/required_apps_managed_device.xml b/core/res/res/values-lt/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-lt/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-lt/required_apps_managed_profile.xml b/core/res/res/values-lt/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-lt/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-lt/required_apps_managed_user.xml b/core/res/res/values-lt/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-lt/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-lv/required_apps_managed_device.xml b/core/res/res/values-lv/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-lv/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-lv/required_apps_managed_profile.xml b/core/res/res/values-lv/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-lv/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-lv/required_apps_managed_user.xml b/core/res/res/values-lv/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-lv/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-mk/required_apps_managed_device.xml b/core/res/res/values-mk/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-mk/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-mk/required_apps_managed_profile.xml b/core/res/res/values-mk/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-mk/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-mk/required_apps_managed_user.xml b/core/res/res/values-mk/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-mk/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ml/required_apps_managed_device.xml b/core/res/res/values-ml/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ml/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ml/required_apps_managed_profile.xml b/core/res/res/values-ml/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ml/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ml/required_apps_managed_user.xml b/core/res/res/values-ml/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ml/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 2595f94..fc958fa 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1695,11 +1695,9 @@
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD അഭ്യർത്ഥന, DIAL അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD അഭ്യർത്ഥന, SS അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD അഭ്യർത്ഥന, പുതിയ USSD അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string>
- <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
- <skip />
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD അഭ്യർത്ഥന, വീഡിയോ DIAL അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS അഭ്യർത്ഥന, DIAL അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string>
- <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
- <skip />
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS അഭ്യർത്ഥന, വീഡിയോ DIAL അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS അഭ്യർത്ഥന, USSD അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS അഭ്യർത്ഥന, പുതിയ SS അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string>
diff --git a/core/res/res/values-mn/required_apps_managed_device.xml b/core/res/res/values-mn/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-mn/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-mn/required_apps_managed_profile.xml b/core/res/res/values-mn/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-mn/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-mn/required_apps_managed_user.xml b/core/res/res/values-mn/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-mn/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-mr/required_apps_managed_device.xml b/core/res/res/values-mr/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-mr/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-mr/required_apps_managed_profile.xml b/core/res/res/values-mr/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-mr/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-mr/required_apps_managed_user.xml b/core/res/res/values-mr/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-mr/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ms/required_apps_managed_device.xml b/core/res/res/values-ms/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ms/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ms/required_apps_managed_profile.xml b/core/res/res/values-ms/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ms/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ms/required_apps_managed_user.xml b/core/res/res/values-ms/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ms/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-nb/required_apps_managed_device.xml b/core/res/res/values-nb/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-nb/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-nb/required_apps_managed_profile.xml b/core/res/res/values-nb/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-nb/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-nb/required_apps_managed_user.xml b/core/res/res/values-nb/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-nb/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ne/required_apps_managed_device.xml b/core/res/res/values-ne/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ne/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ne/required_apps_managed_profile.xml b/core/res/res/values-ne/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ne/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ne/required_apps_managed_user.xml b/core/res/res/values-ne/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ne/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 9223c76..f0a45a3 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1232,8 +1232,8 @@
<string name="ext_media_unmountable_notification_message" msgid="2343202057122495773">"<xliff:g id="NAME">%s</xliff:g> त्रुटिपूर्ण छ। समाधान गर्न ट्याप गर्नुहोस्।"</string>
<string name="ext_media_unmountable_notification_message" product="tv" msgid="3941179940297874950">"<xliff:g id="NAME">%s</xliff:g> बिग्रेको छ। समाधान गर्न चयन गर्नुहोस्।"</string>
<string name="ext_media_unsupported_notification_title" msgid="3797642322958803257">"असमर्थित <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"यस यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेट अप गर्न ट्याप गर्नुहोस्।"</string>
- <string name="ext_media_unsupported_notification_message" product="tv" msgid="3725436899820390906">"यो यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेट अप गर्न चयन गर्नुहोस्।"</string>
+ <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"यस यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेटअप गर्न ट्याप गर्नुहोस्।"</string>
+ <string name="ext_media_unsupported_notification_message" product="tv" msgid="3725436899820390906">"यो यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेटअप गर्न चयन गर्नुहोस्।"</string>
<string name="ext_media_badremoval_notification_title" msgid="3206248947375505416">"<xliff:g id="NAME">%s</xliff:g> अप्रत्याशित रूपमा निकालियो"</string>
<string name="ext_media_badremoval_notification_message" msgid="380176703346946313">"डेटा हराउनबाट जोगाउन निकाल्नु अघि <xliff:g id="NAME">%s</xliff:g> अनमाउन्ट गर्नुहोस्"</string>
<string name="ext_media_nomedia_notification_title" msgid="1704840188641749091">"निकालियो <xliff:g id="NAME">%s</xliff:g>"</string>
@@ -1318,7 +1318,7 @@
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"कार मोड सक्षम पारियो।"</string>
<string name="car_mode_disable_notification_message" msgid="6301524980144350051">"कार मोडबाट बाहिर निस्कन ट्याप गर्नुहोस्।"</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"टेथर गर्ने वा हटस्पट सक्रिय"</string>
- <string name="tethered_notification_message" msgid="2113628520792055377">"सेट अप गर्न ट्याप गर्नुहोस्।"</string>
+ <string name="tethered_notification_message" msgid="2113628520792055377">"सेटअप गर्न ट्याप गर्नुहोस्।"</string>
<string name="disable_tether_notification_title" msgid="7526977944111313195">"टेदरिङलाई असक्षम पारिएको छ"</string>
<string name="disable_tether_notification_message" msgid="2913366428516852495">"विवरणहरूका लागि आफ्ना प्रशासकलाई सम्पर्क गर्नुहोस्"</string>
<string name="back_button_label" msgid="2300470004503343439">"पछाडि"</string>
diff --git a/core/res/res/values-pa/required_apps_managed_device.xml b/core/res/res/values-pa/required_apps_managed_device.xml
new file mode 100644
index 0000000..faadc50
--- /dev/null
+++ b/core/res/res/values-pa/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.download"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-pa/required_apps_managed_profile.xml b/core/res/res/values-pa/required_apps_managed_profile.xml
new file mode 100644
index 0000000..537a80c
--- /dev/null
+++ b/core/res/res/values-pa/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.download"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-pa/required_apps_managed_user.xml b/core/res/res/values-pa/required_apps_managed_user.xml
new file mode 100644
index 0000000..e69bbbc
--- /dev/null
+++ b/core/res/res/values-pa/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.download"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 283701e..b5b6a00 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1695,11 +1695,9 @@
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD ਬੇਨਤੀ DIAL ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD ਬੇਨਤੀ SS ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD ਬੇਨਤੀ ਨਵੀਂ USSD ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
- <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
- <skip />
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD ਬੇਨਤੀ ਨੂੰ ਸੋਧ ਕੇ ਵੀਡੀਓ ਡਾਇਲ ਬੇਨਤੀ ਵਿੱਚ ਬਦਲਿਆ ਗਿਆ।"</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS ਬੇਨਤੀ DIAL ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
- <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
- <skip />
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS ਬੇਨਤੀ ਨੂੰ ਸੋਧ ਕੇ ਵੀਡੀਓ ਡਾਇਲ ਬੇਨਤੀ ਵਿੱਚ ਬਦਲਿਆ ਗਿਆ।"</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS ਬੇਨਤੀ USSD ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS ਬੇਨਤੀ ਨਵੀਂ SS ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
diff --git a/core/res/res/values-pl/required_apps_managed_device.xml b/core/res/res/values-pl/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-pl/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-pl/required_apps_managed_profile.xml b/core/res/res/values-pl/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-pl/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-pl/required_apps_managed_user.xml b/core/res/res/values-pl/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-pl/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-pt-rPT/required_apps_managed_device.xml b/core/res/res/values-pt-rPT/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-pt-rPT/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-pt-rPT/required_apps_managed_profile.xml b/core/res/res/values-pt-rPT/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-pt-rPT/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-pt-rPT/required_apps_managed_user.xml b/core/res/res/values-pt-rPT/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-pt-rPT/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 434b50b..dc2d6e4 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -381,7 +381,7 @@
<string name="permdesc_readCalendar" product="tablet" msgid="4993979255403945892">"Esta aplicação pode ler todos os eventos do calendário armazenados no seu tablet e partilhar ou guardar os dados do calendário."</string>
<string name="permdesc_readCalendar" product="tv" msgid="8837931557573064315">"Esta aplicação pode ler todos os eventos do calendário armazenados na sua TV e partilhar ou guardar os dados do calendário."</string>
<string name="permdesc_readCalendar" product="default" msgid="4373978642145196715">"Esta aplicação pode ler todos os eventos do calendário armazenados no seu telemóvel e partilhar ou guardar os dados do calendário."</string>
- <string name="permlab_writeCalendar" msgid="8438874755193825647">"adicionar ou modificar eventos do calendário e enviar e-mail a convidados sem o conhecimento dos proprietários"</string>
+ <string name="permlab_writeCalendar" msgid="8438874755193825647">"adicionar ou modificar eventos do calendário e enviar email a convidados sem o conhecimento dos proprietários"</string>
<string name="permdesc_writeCalendar" product="tablet" msgid="1675270619903625982">"Esta aplicação pode adicionar, remover ou alterar eventos do calendário no seu tablet. Esta aplicação pode enviar mensagens que parecem vir de proprietários do calendário ou alterar eventos sem notificar os respetivos proprietários."</string>
<string name="permdesc_writeCalendar" product="tv" msgid="9017809326268135866">"Esta aplicação pode adicionar, remover ou alterar eventos do calendário na sua TV. Esta aplicação pode enviar mensagens que parecem vir de proprietários do calendário ou alterar eventos sem notificar os respetivos proprietários."</string>
<string name="permdesc_writeCalendar" product="default" msgid="7592791790516943173">"Esta aplicação pode adicionar, remover ou alterar eventos do calendário no seu telemóvel. Esta aplicação pode enviar mensagens que parecem vir de proprietários do calendário ou alterar eventos sem notificar os respetivos proprietários."</string>
@@ -756,7 +756,7 @@
<string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"Desbloqueio da conta"</string>
<string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"Demasiadas tentativas para desenhar sequência"</string>
<string name="lockscreen_glogin_instructions" msgid="3931816256100707784">"Para desbloquear, inicie sessão com a Conta Google."</string>
- <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"Nome de utilizador (e-mail)"</string>
+ <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"Nome de utilizador (email)"</string>
<string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"Palavra-passe"</string>
<string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Iniciar sessão"</string>
<string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Nome de utilizador ou palavra-passe inválidos."</string>
diff --git a/core/res/res/values-ro/required_apps_managed_device.xml b/core/res/res/values-ro/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ro/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ro/required_apps_managed_profile.xml b/core/res/res/values-ro/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ro/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ro/required_apps_managed_user.xml b/core/res/res/values-ro/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ro/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sk/required_apps_managed_device.xml b/core/res/res/values-sk/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sk/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sk/required_apps_managed_profile.xml b/core/res/res/values-sk/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sk/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sk/required_apps_managed_user.xml b/core/res/res/values-sk/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sk/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sl/required_apps_managed_device.xml b/core/res/res/values-sl/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sl/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sl/required_apps_managed_profile.xml b/core/res/res/values-sl/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sl/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sl/required_apps_managed_user.xml b/core/res/res/values-sl/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sl/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sq/required_apps_managed_device.xml b/core/res/res/values-sq/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sq/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sq/required_apps_managed_profile.xml b/core/res/res/values-sq/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sq/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sq/required_apps_managed_user.xml b/core/res/res/values-sq/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sq/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sr/required_apps_managed_device.xml b/core/res/res/values-sr/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sr/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sr/required_apps_managed_profile.xml b/core/res/res/values-sr/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sr/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sr/required_apps_managed_user.xml b/core/res/res/values-sr/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sr/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sv/required_apps_managed_device.xml b/core/res/res/values-sv/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sv/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sv/required_apps_managed_profile.xml b/core/res/res/values-sv/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sv/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sv/required_apps_managed_user.xml b/core/res/res/values-sv/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sv/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sw/required_apps_managed_device.xml b/core/res/res/values-sw/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-sw/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sw/required_apps_managed_profile.xml b/core/res/res/values-sw/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-sw/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-sw/required_apps_managed_user.xml b/core/res/res/values-sw/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-sw/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ta/required_apps_managed_device.xml b/core/res/res/values-ta/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ta/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ta/required_apps_managed_profile.xml b/core/res/res/values-ta/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ta/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ta/required_apps_managed_user.xml b/core/res/res/values-ta/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ta/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index c1526f3..c517223 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1695,11 +1695,9 @@
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD கோரிக்கையானது DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD கோரிக்கையானது SS கோரிக்கைக்கு மாற்றப்பட்டது."</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD கோரிக்கையானது புதிய USSD கோரிக்கைக்கு மாற்றப்பட்டது."</string>
- <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
- <skip />
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD கோரிக்கையானது வீடியோ DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS கோரிக்கையானது DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string>
- <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
- <skip />
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS கோரிக்கையானது வீடியோ DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS கோரிக்கையானது USSD கோரிக்கைக்கு மாற்றப்பட்டது."</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS கோரிக்கையானது புதிய SS கோரிக்கைக்கு மாற்றப்பட்டது."</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"பணி சுயவிவரம்"</string>
diff --git a/core/res/res/values-te/required_apps_managed_device.xml b/core/res/res/values-te/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-te/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-te/required_apps_managed_profile.xml b/core/res/res/values-te/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-te/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-te/required_apps_managed_user.xml b/core/res/res/values-te/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-te/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index a96bcaf..fd103bc 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1695,11 +1695,9 @@
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD అభ్యర్థన డయల్ అభ్యర్థనగా సవరించబడింది."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD అభ్యర్థన SS అభ్యర్థనగా సవరించబడింది."</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD అభ్యర్థన కొత్త USSD అభ్యర్థనగా సవరించబడింది."</string>
- <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
- <skip />
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD అభ్యర్థన వీడియో డయల్ అభ్యర్థనగా సవరించబడింది."</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS అభ్యర్థన డయల్ అభ్యర్థనగా సవరించబడింది."</string>
- <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
- <skip />
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS అభ్యర్థన వీడియో డయల్ అభ్యర్థనగా సవరించబడింది."</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS అభ్యర్థన USSD అభ్యర్థనగా సవరించబడింది."</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS అభ్యర్థన కొత్త SS అభ్యర్థనగా సవరించబడింది."</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"కార్యాలయ ప్రొఫైల్"</string>
diff --git a/core/res/res/values-th/required_apps_managed_device.xml b/core/res/res/values-th/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-th/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-th/required_apps_managed_profile.xml b/core/res/res/values-th/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-th/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-th/required_apps_managed_user.xml b/core/res/res/values-th/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-th/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-tl/required_apps_managed_device.xml b/core/res/res/values-tl/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-tl/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-tl/required_apps_managed_profile.xml b/core/res/res/values-tl/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-tl/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-tl/required_apps_managed_user.xml b/core/res/res/values-tl/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-tl/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-tr/required_apps_managed_device.xml b/core/res/res/values-tr/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-tr/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-tr/required_apps_managed_profile.xml b/core/res/res/values-tr/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-tr/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-tr/required_apps_managed_user.xml b/core/res/res/values-tr/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-tr/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ur/required_apps_managed_device.xml b/core/res/res/values-ur/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ur/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ur/required_apps_managed_profile.xml b/core/res/res/values-ur/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ur/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ur/required_apps_managed_user.xml b/core/res/res/values-ur/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ur/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 2155b34..2771432 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1695,11 +1695,9 @@
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD درخواست میں ترمیم کر کے DIAL درخواست بنا دی گئی ہے۔"</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD درخواست میں ترمیم کر کے SS درخواست بنا دی گئی ہے۔"</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD درخواست میں ترمیم کر کے نئی USSD درخواست بنا دی گئی ہے۔"</string>
- <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) -->
- <skip />
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD درخواست کو ویڈیو DIAL درخواست میں تبدیل کر دیا گیا ہے۔"</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS درخواست میں ترمیم کر کے DIAL درخواست بنا دی گئی ہے۔"</string>
- <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) -->
- <skip />
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS درخواست کو ویڈیو DIAL درخواست میں تبدیل کر دیا گیا ہے۔"</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS درخواست میں ترمیم کر کے USSD درخواست بنا دی گئی ہے۔"</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS درخواست میں ترمیم کر کے نئی SS درخواست بنا دی گئی ہے۔"</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"دفتری پروفائل"</string>
diff --git a/core/res/res/values-uz/required_apps_managed_device.xml b/core/res/res/values-uz/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-uz/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-uz/required_apps_managed_profile.xml b/core/res/res/values-uz/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-uz/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-uz/required_apps_managed_user.xml b/core/res/res/values-uz/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-uz/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-vi/required_apps_managed_device.xml b/core/res/res/values-vi/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-vi/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-vi/required_apps_managed_profile.xml b/core/res/res/values-vi/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-vi/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-vi/required_apps_managed_user.xml b/core/res/res/values-vi/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-vi/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rCN/required_apps_managed_device.xml b/core/res/res/values-zh-rCN/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-zh-rCN/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rCN/required_apps_managed_profile.xml b/core/res/res/values-zh-rCN/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-zh-rCN/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rCN/required_apps_managed_user.xml b/core/res/res/values-zh-rCN/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-zh-rCN/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rHK/required_apps_managed_device.xml b/core/res/res/values-zh-rHK/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-zh-rHK/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rHK/required_apps_managed_profile.xml b/core/res/res/values-zh-rHK/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-zh-rHK/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rHK/required_apps_managed_user.xml b/core/res/res/values-zh-rHK/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-zh-rHK/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rTW/required_apps_managed_device.xml b/core/res/res/values-zh-rTW/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-zh-rTW/required_apps_managed_device.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_device">
+ <item msgid="1104492179978792509">"com.android.settings"</item>
+ <item msgid="7004798084799227194">"com.android.contacts"</item>
+ <item msgid="5782220690863647256">"com.android.dialer"</item>
+ <item msgid="5746338511138092673">"com.android.stk"</item>
+ <item msgid="1712599182168590664">"com.android.providers.downloads"</item>
+ <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item>
+ <item msgid="3892021562839042708">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rTW/required_apps_managed_profile.xml b/core/res/res/values-zh-rTW/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-zh-rTW/required_apps_managed_profile.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_profile">
+ <item msgid="1457364287544474838">"com.android.contacts"</item>
+ <item msgid="4633145750237794002">"com.android.settings"</item>
+ <item msgid="6518205098643077579">"com.android.providers.downloads"</item>
+ <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item>
+ <item msgid="6106837921940099371">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-zh-rTW/required_apps_managed_user.xml b/core/res/res/values-zh-rTW/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-zh-rTW/required_apps_managed_user.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="required_apps_managed_user">
+ <item msgid="4823915868435007499">"com.android.settings"</item>
+ <item msgid="2250259015310893915">"com.android.contacts"</item>
+ <item msgid="7166574999426592423">"com.android.dialer"</item>
+ <item msgid="7306937186458176744">"com.android.stk"</item>
+ <item msgid="7415441588151512455">"com.android.providers.downloads"</item>
+ <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item>
+ <item msgid="8640522622655589373">"com.android.documentsui"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 51ffa60..64febf1 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4442,7 +4442,7 @@
<attr name="textColor" />
<!-- Size of the text. Recommended dimension type for text is "sp" for scaled-pixels (example: 15sp). -->
<attr name="textSize" />
- <!-- Style (bold, italic, bolditalic) for the text. -->
+ <!-- Style (normal, bold, italic, bold|italic) for the text. -->
<attr name="textStyle" />
<!-- Typeface (normal, sans, serif, monospace) for the text. -->
<attr name="typeface" />
@@ -4527,7 +4527,7 @@
<attr name="textScaleX" format="float" />
<!-- Typeface (normal, sans, serif, monospace) for the text. -->
<attr name="typeface" />
- <!-- Style (bold, italic, bolditalic) for the text. -->
+ <!-- Style (normal, bold, italic, bold|italic) for the text. -->
<attr name="textStyle" />
<!-- Font family (named by string or as a font resource reference) for the text. -->
<attr name="fontFamily" />
@@ -7729,6 +7729,10 @@
wallpaper. -->
<attr name="showMetadataInPreview" format="boolean" />
+ <!-- Wallpapers optimized and capable of drawing in ambient mode will return true.
+ @hide -->
+ <attr name="supportsAmbientMode" format="boolean" />
+
</declare-styleable>
<!-- Use <code>dream</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4b2424f..d962ada 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1308,6 +1308,7 @@
<java-symbol type="drawable" name="platlogo" />
<java-symbol type="drawable" name="stat_notify_sync_error" />
<java-symbol type="drawable" name="stat_notify_wifi_in_range" />
+ <java-symbol type="drawable" name="ic_wifi_settings" />
<java-symbol type="drawable" name="ic_wifi_signal_0" />
<java-symbol type="drawable" name="ic_wifi_signal_1" />
<java-symbol type="drawable" name="ic_wifi_signal_2" />
diff --git a/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
index 3c81853..e26bdf5 100644
--- a/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
@@ -15,6 +15,7 @@
*/
package android.animation;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import java.util.HashSet;
@@ -24,6 +25,7 @@
import com.android.frameworks.coretests.R;
+@LargeTest
public class AnimatorInflaterTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
Set<Integer> identityHashes = new HashSet<Integer>();
diff --git a/core/tests/coretests/src/android/animation/StateListAnimatorTest.java b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
index 38df78d..a9961e1 100644
--- a/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
+++ b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
@@ -17,6 +17,7 @@
package android.animation;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.util.StateSet;
@@ -27,7 +28,7 @@
import java.util.concurrent.atomic.AtomicInteger;
-
+@LargeTest
public class StateListAnimatorTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
public StateListAnimatorTest() {
diff --git a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
index f60bf94..063bef7 100644
--- a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
+++ b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
@@ -22,6 +22,7 @@
import android.content.pm.PackageInfo;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
+import android.support.test.filters.LargeTest;
import junit.framework.TestCase;
@@ -34,6 +35,7 @@
import static android.os.storage.VolumeInfo.STATE_MOUNTED;
import static android.os.storage.VolumeInfo.STATE_UNMOUNTED;
+@LargeTest
public class ApplicationPackageManagerTest extends TestCase {
private static final String sInternalVolPath = "/data";
private static final String sAdoptedVolPath = "/mnt/expand/123";
diff --git a/core/tests/coretests/src/android/app/InstrumentationTest.java b/core/tests/coretests/src/android/app/InstrumentationTest.java
index ee3834c..9b59da4 100644
--- a/core/tests/coretests/src/android/app/InstrumentationTest.java
+++ b/core/tests/coretests/src/android/app/InstrumentationTest.java
@@ -17,8 +17,10 @@
package android.app;
import android.os.Bundle;
+import android.support.test.filters.LargeTest;
import android.test.InstrumentationTestCase;
+@LargeTest
public class InstrumentationTest extends InstrumentationTestCase {
/**
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index c14dc90..7183934 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -214,6 +214,20 @@
assertTrue(n.allPendingIntents.contains(intent));
}
+ @Test
+ public void testMessagingStyle_isGroupConversation() {
+ Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+ .setGroupConversation(true);
+ Notification notification = new Notification.Builder(mContext, "test id")
+ .setSmallIcon(1)
+ .setContentTitle("test title")
+ .setStyle(messagingStyle)
+ .build();
+
+ assertTrue(messagingStyle.isGroupConversation());
+ assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+ }
+
private Notification.Builder getMediaNotification() {
MediaSession session = new MediaSession(mContext, "test");
return new Notification.Builder(mContext, "color")
diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
index e9e8bfc..13e70eb 100644
--- a/core/tests/coretests/src/android/app/activity/BroadcastTest.java
+++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
@@ -27,12 +27,13 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.UserHandle;
+import android.support.test.filters.LargeTest;
import android.test.FlakyTest;
-import android.test.suitebuilder.annotation.Suppress;
import android.util.Log;
import java.util.Arrays;
+@LargeTest
public class BroadcastTest extends ActivityTestsBase {
public static final int BROADCAST_TIMEOUT = 5 * 1000;
diff --git a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
index 3c30915..8c1d79b 100644
--- a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
+++ b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
@@ -20,10 +20,10 @@
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
-import android.test.suitebuilder.annotation.Suppress;
import android.os.Bundle;
-import android.test.suitebuilder.annotation.Suppress;
+import android.support.test.filters.LargeTest;
+@LargeTest
public class IntentSenderTest extends BroadcastTest {
public void testRegisteredReceivePermissionGranted() throws Exception {
diff --git a/core/tests/coretests/src/android/app/backup/BackupDataTest.java b/core/tests/coretests/src/android/app/backup/BackupDataTest.java
index 0c204e0..5b8e481 100644
--- a/core/tests/coretests/src/android/app/backup/BackupDataTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupDataTest.java
@@ -23,6 +23,7 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import android.test.InstrumentationTestCase;
import android.util.Base64;
@@ -41,6 +42,7 @@
import java.lang.Exception;
import java.nio.ByteBuffer;
+@LargeTest
public class BackupDataTest extends AndroidTestCase {
private static final String KEY1 = "key1";
private static final String KEY2 = "key2a";
diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
index 3869cd2..bc6fc15 100644
--- a/core/tests/coretests/src/android/app/backup/FullBackupTest.java
+++ b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
@@ -20,6 +20,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -36,6 +37,7 @@
import java.util.Map;
import java.util.Set;
+@LargeTest
public class FullBackupTest extends AndroidTestCase {
private XmlPullParserFactory mFactory;
private XmlPullParser mXpp;
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 4b1f2da..e4de526 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -513,6 +513,12 @@
}
@Override
+ public void dumpMemInfoProto(ParcelFileDescriptor parcelFileDescriptor,
+ Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2,
+ boolean b3, String[] strings) throws RemoteException {
+ }
+
+ @Override
public void dumpGfxInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
throws RemoteException {
}
diff --git a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
index 70a0877..e20645c 100644
--- a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
+++ b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
@@ -21,12 +21,14 @@
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import org.junit.Test;
/**
* Tests for {@link DistroFormatVersion}.
*/
+@LargeTest
public class DistroFormatVersionTest {
@Test
diff --git a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
index eecae46..b69054c 100644
--- a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
+++ b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
@@ -21,12 +21,14 @@
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import org.junit.Test;
/**
* Tests for {@link DistroRulesVersion}.
*/
+@LargeTest
public class DistroRulesVersionTest {
@Test
diff --git a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
index 99abe24..dd46240 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
@@ -23,12 +23,14 @@
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import org.junit.Test;
/**
* Tests for {@link RulesState}.
*/
+@LargeTest
public class RulesStateTest {
@Test
diff --git a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
index 91f8ebc..e4aac50 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
+import android.support.test.filters.LargeTest;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
@@ -33,6 +34,7 @@
/**
* Tests for {@link RulesUpdaterContract}.
*/
+@LargeTest
public class RulesUpdaterContractTest {
@Test
diff --git a/core/tests/coretests/src/android/content/RestrictionsManagerTest.java b/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
index d92eece..10d74f7 100644
--- a/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
+++ b/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
@@ -17,6 +17,7 @@
import android.os.Bundle;
import android.os.Parcelable;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import java.util.Arrays;
@@ -24,6 +25,7 @@
import java.util.List;
import java.util.Set;
+@LargeTest
public class RestrictionsManagerTest extends AndroidTestCase {
private RestrictionsManager mRm;
diff --git a/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
index 948e722..659f9ea 100644
--- a/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
+++ b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import java.io.ByteArrayInputStream;
@@ -27,6 +28,7 @@
import libcore.io.Streams;
+@LargeTest
public class MacAuthenticatedInputStreamTest extends AndroidTestCase {
private static final SecretKey HMAC_KEY_1 = new SecretKeySpec("test_key_1".getBytes(), "HMAC");
diff --git a/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
index a9d19b4..952bb55 100644
--- a/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
+++ b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
@@ -2,6 +2,7 @@
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.test.filters.LargeTest;
import junit.framework.TestCase;
@@ -9,6 +10,7 @@
import java.util.Collections;
import java.util.List;
+@LargeTest
public class ParceledListSliceTest extends TestCase {
public void testSmallList() throws Exception {
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 271c639..d3d1f22a 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -21,6 +21,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import android.util.AttributeSet;
import android.util.SparseArray;
@@ -43,6 +44,7 @@
/**
* Tests for {@link android.content.pm.RegisteredServicesCache}
*/
+@LargeTest
public class RegisteredServicesCacheTest extends AndroidTestCase {
private static final int U0 = 0;
private static final int U1 = 1;
diff --git a/core/tests/coretests/src/android/content/pm/SignatureTest.java b/core/tests/coretests/src/android/content/pm/SignatureTest.java
index 89d5997..a3fa1a9 100644
--- a/core/tests/coretests/src/android/content/pm/SignatureTest.java
+++ b/core/tests/coretests/src/android/content/pm/SignatureTest.java
@@ -16,8 +16,11 @@
package android.content.pm;
+import android.support.test.filters.LargeTest;
+
import junit.framework.TestCase;
+@LargeTest
public class SignatureTest extends TestCase {
/** Cert A with valid syntax */
diff --git a/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java b/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
index d963812..68942cb 100644
--- a/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
+++ b/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
@@ -19,6 +19,7 @@
import android.content.pm.VerificationParams;
import android.net.Uri;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
/**
@@ -27,6 +28,7 @@
* To test run:
* ./development/testrunner/runtest.py frameworks-core -c android.content.pm.VerificationParamsTest
*/
+@LargeTest
public class VerificationParamsTest extends AndroidTestCase {
private final static String VERIFICATION_URI_STRING = "http://verification.uri/path";
diff --git a/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java b/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
index cb13eb7..88d7a59 100644
--- a/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
+++ b/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
@@ -17,9 +17,11 @@
package android.content.pm;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import java.util.Random;
+@LargeTest
public class VerifierDeviceIdentityTest extends android.test.AndroidTestCase {
private static final long TEST_1 = 0x7A5F00FF5A55AAA5L;
diff --git a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
index 7550cb5..b28a4b5 100644
--- a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
@@ -15,6 +15,7 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import android.util.Log;
import android.util.PathParser;
@@ -23,6 +24,7 @@
import java.util.Arrays;
import org.junit.Test;
+@LargeTest
public class AdaptiveIconDrawableTest extends AndroidTestCase {
public static final String TAG = AdaptiveIconDrawableTest.class.getSimpleName();
diff --git a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
index a2e9ae8..f30b1a2 100644
--- a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
+++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
@@ -30,6 +30,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -49,6 +50,7 @@
* Contains additional tests that cannot be included in CTS because they require
* system permissions. See also the CTS version of VirtualDisplayTest.
*/
+@LargeTest
public class VirtualDisplayTest extends AndroidTestCase {
private static final String TAG = "VirtualDisplayTest";
diff --git a/core/tests/coretests/src/android/metrics/LogMakerTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java
index ada59cd..3be776d 100644
--- a/core/tests/coretests/src/android/metrics/LogMakerTest.java
+++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java
@@ -15,9 +15,13 @@
*/
package android.metrics;
+import android.support.test.filters.LargeTest;
+
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
import junit.framework.TestCase;
+@LargeTest
public class LogMakerTest extends TestCase {
public void testSerialize() {
diff --git a/core/tests/coretests/src/android/metrics/MetricsReaderTest.java b/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
index d10b351..784a12f 100644
--- a/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
+++ b/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
@@ -16,6 +16,7 @@
package android.metrics;
import android.metrics.MetricsReader.Event;
+import android.support.test.filters.LargeTest;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -23,6 +24,7 @@
import java.util.Collection;
+@LargeTest
public class MetricsReaderTest extends TestCase {
private static final int FULL_N = 10;
private static final int CHECKPOINTED_N = 4;
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
new file mode 100644
index 0000000..7350db7
--- /dev/null
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2008 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.os;
+
+import android.os.WorkSource.WorkChain;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+/**
+ * Provides unit tests for hidden / unstable WorkSource APIs that are not CTS testable.
+ *
+ * These tests will be moved to CTS when finalized.
+ */
+public class WorkSourceTest extends TestCase {
+ public void testWorkChain_add() {
+ WorkChain wc1 = new WorkChain();
+ wc1.addNode(56, null);
+
+ assertEquals(56, wc1.getUids()[0]);
+ assertEquals(null, wc1.getTags()[0]);
+ assertEquals(1, wc1.getSize());
+
+ wc1.addNode(57, "foo");
+ assertEquals(56, wc1.getUids()[0]);
+ assertEquals(null, wc1.getTags()[0]);
+ assertEquals(57, wc1.getUids()[1]);
+ assertEquals("foo", wc1.getTags()[1]);
+
+ assertEquals(2, wc1.getSize());
+ }
+
+ public void testWorkChain_equalsHashCode() {
+ WorkChain wc1 = new WorkChain();
+ WorkChain wc2 = new WorkChain();
+
+ assertEquals(wc1, wc2);
+ assertEquals(wc1.hashCode(), wc2.hashCode());
+
+ wc1.addNode(1, null);
+ wc2.addNode(1, null);
+ assertEquals(wc1, wc2);
+ assertEquals(wc1.hashCode(), wc2.hashCode());
+
+ wc1.addNode(2, "tag");
+ wc2.addNode(2, "tag");
+ assertEquals(wc1, wc2);
+ assertEquals(wc1.hashCode(), wc2.hashCode());
+
+ wc1 = new WorkChain();
+ wc2 = new WorkChain();
+ wc1.addNode(5, null);
+ wc2.addNode(6, null);
+ assertFalse(wc1.equals(wc2));
+ assertFalse(wc1.hashCode() == wc2.hashCode());
+
+ wc1 = new WorkChain();
+ wc2 = new WorkChain();
+ wc1.addNode(5, "tag1");
+ wc2.addNode(5, "tag2");
+ assertFalse(wc1.equals(wc2));
+ assertFalse(wc1.hashCode() == wc2.hashCode());
+ }
+
+ public void testWorkChain_constructor() {
+ WorkChain wc1 = new WorkChain();
+ wc1.addNode(1, "foo")
+ .addNode(2, null)
+ .addNode(3, "baz");
+
+ WorkChain wc2 = new WorkChain(wc1);
+ assertEquals(wc1, wc2);
+
+ wc1.addNode(4, "baz");
+ assertFalse(wc1.equals(wc2));
+ }
+
+ public void testDiff_workChains() {
+ WorkSource ws1 = new WorkSource();
+ ws1.add(50);
+ ws1.createWorkChain().addNode(52, "foo");
+ WorkSource ws2 = new WorkSource();
+ ws2.add(50);
+ ws2.createWorkChain().addNode(60, "bar");
+
+ // Diffs don't take WorkChains into account for the sake of backward compatibility.
+ assertFalse(ws1.diff(ws2));
+ assertFalse(ws2.diff(ws1));
+ }
+
+ public void testEquals_workChains() {
+ WorkSource ws1 = new WorkSource();
+ ws1.add(50);
+ ws1.createWorkChain().addNode(52, "foo");
+
+ WorkSource ws2 = new WorkSource();
+ ws2.add(50);
+ ws2.createWorkChain().addNode(52, "foo");
+
+ assertEquals(ws1, ws2);
+
+ // Unequal number of WorkChains.
+ ws2.createWorkChain().addNode(53, "baz");
+ assertFalse(ws1.equals(ws2));
+
+ // Different WorkChain contents.
+ WorkSource ws3 = new WorkSource();
+ ws3.add(50);
+ ws3.createWorkChain().addNode(60, "bar");
+
+ assertFalse(ws1.equals(ws3));
+ assertFalse(ws3.equals(ws1));
+ }
+
+ public void testWorkSourceParcelling() {
+ WorkSource ws = new WorkSource();
+
+ WorkChain wc = ws.createWorkChain();
+ wc.addNode(56, "foo");
+ wc.addNode(75, "baz");
+ WorkChain wc2 = ws.createWorkChain();
+ wc2.addNode(20, "foo2");
+ wc2.addNode(30, "baz2");
+
+ Parcel p = Parcel.obtain();
+ ws.writeToParcel(p, 0);
+ p.setDataPosition(0);
+
+ WorkSource unparcelled = WorkSource.CREATOR.createFromParcel(p);
+
+ assertEquals(unparcelled, ws);
+ }
+
+ public void testSet_workChains() {
+ WorkSource ws1 = new WorkSource();
+ ws1.add(50);
+
+ WorkSource ws2 = new WorkSource();
+ ws2.add(60);
+ WorkChain wc = ws2.createWorkChain();
+ wc.addNode(75, "tag");
+
+ ws1.set(ws2);
+
+ // Assert that the WorkChains are copied across correctly to the new WorkSource object.
+ List<WorkChain> workChains = ws1.getWorkChains();
+ assertEquals(1, workChains.size());
+
+ assertEquals(1, workChains.get(0).getSize());
+ assertEquals(75, workChains.get(0).getUids()[0]);
+ assertEquals("tag", workChains.get(0).getTags()[0]);
+
+ // Also assert that a deep copy of workchains is made, so the addition of a new WorkChain
+ // or the modification of an existing WorkChain has no effect.
+ ws2.createWorkChain();
+ assertEquals(1, ws1.getWorkChains().size());
+
+ wc.addNode(50, "tag2");
+ assertEquals(1, ws1.getWorkChains().size());
+ assertEquals(1, ws1.getWorkChains().get(0).getSize());
+ }
+
+ public void testSet_nullWorkChain() {
+ WorkSource ws = new WorkSource();
+ ws.add(60);
+ WorkChain wc = ws.createWorkChain();
+ wc.addNode(75, "tag");
+
+ ws.set(null);
+ assertEquals(0, ws.getWorkChains().size());
+ }
+
+ public void testAdd_workChains() {
+ WorkSource ws = new WorkSource();
+ ws.createWorkChain().addNode(70, "foo");
+
+ WorkSource ws2 = new WorkSource();
+ ws2.createWorkChain().addNode(60, "tag");
+
+ ws.add(ws2);
+
+ // Check that the new WorkChain is added to the end of the list.
+ List<WorkChain> workChains = ws.getWorkChains();
+ assertEquals(2, workChains.size());
+ assertEquals(1, workChains.get(1).getSize());
+ assertEquals(60, ws.getWorkChains().get(1).getUids()[0]);
+ assertEquals("tag", ws.getWorkChains().get(1).getTags()[0]);
+
+ // Adding the same WorkChain twice should be a no-op.
+ ws.add(ws2);
+ assertEquals(2, workChains.size());
+ }
+}
diff --git a/core/tests/coretests/src/android/preference/ListPreferenceTest.java b/core/tests/coretests/src/android/preference/ListPreferenceTest.java
index 41f8e03..72f62f1 100644
--- a/core/tests/coretests/src/android/preference/ListPreferenceTest.java
+++ b/core/tests/coretests/src/android/preference/ListPreferenceTest.java
@@ -17,8 +17,10 @@
package android.preference;
import android.preference.ListPreference;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
+@LargeTest
public class ListPreferenceTest extends AndroidTestCase {
public void testListPreferenceSummaryFromEntries() {
String[] entries = { "one", "two", "three" };
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutTest.java b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
index aa9aed8..ea954f6 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
@@ -30,6 +30,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.style.ReplacementSpan;
+import android.util.ArraySet;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -190,6 +191,27 @@
}
@Test
+ public void testReflow_afterSpannableEdit() {
+ final String text = "a\nb:\uD83C\uDF1A c \n\uD83C\uDF1A";
+ final int length = text.length();
+ final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+ spannable.setSpan(new MockReplacementSpan(), 4, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spannable.setSpan(new MockReplacementSpan(), 10, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ final DynamicLayout layout = new DynamicLayout(spannable, new TextPaint(), WIDTH,
+ ALIGN_NORMAL, 1.0f /*spacingMultiplier*/, 0f /*spacingAdd*/, false /*includepad*/);
+
+ spannable.delete(8, 9);
+ spannable.replace(7, 8, "ch");
+
+ layout.reflow(spannable, 0, length, length);
+ final ArraySet<Integer> blocks = layout.getBlocksAlwaysNeedToBeRedrawn();
+ for (Integer value : blocks) {
+ assertTrue("Block index should not be negative", value >= 0);
+ }
+ }
+
+ @Test
public void testFallbackLineSpacing() {
// All glyphs in the fonts are 1em wide.
final String[] testFontFiles = {
diff --git a/core/tests/coretests/src/android/text/OWNERS b/core/tests/coretests/src/android/text/OWNERS
new file mode 100644
index 0000000..0f85e1f
--- /dev/null
+++ b/core/tests/coretests/src/android/text/OWNERS
@@ -0,0 +1,4 @@
+siyamed@google.com
+nona@google.com
+clarabayarri@google.com
+toki@google.com
diff --git a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
index f1a730a..d0f2d46 100644
--- a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.style.BulletSpan;
@@ -30,6 +31,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SpannableStringBuilderTest extends SpannableTest {
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index f4514b5..d817278 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -697,9 +697,13 @@
public void testGetOffset_UNICODE_Hebrew() {
String testString = "\u05DE\u05E1\u05E2\u05D3\u05D4"; // Hebrew Characters
for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) {
- StaticLayout layout = new StaticLayout(seq, mDefaultPaint,
- DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN,
- TextDirectionHeuristics.RTL, SPACE_MULTI, SPACE_ADD, true);
+ StaticLayout.Builder b = StaticLayout.Builder.obtain(
+ seq, 0, seq.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH)
+ .setAlignment(DEFAULT_ALIGN)
+ .setTextDirection(TextDirectionHeuristics.RTL)
+ .setLineSpacing(SPACE_ADD, SPACE_MULTI)
+ .setIncludePad(true);
+ StaticLayout layout = b.build();
String testLabel = buildTestMessage(seq);
diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java
index 2d94016..9c544f4 100644
--- a/core/tests/coretests/src/android/text/format/FormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/FormatterTest.java
@@ -23,6 +23,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.icu.util.MeasureUnit;
+import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -36,7 +37,7 @@
import java.util.Locale;
import java.util.Set;
-
+@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class FormatterTest {
diff --git a/core/tests/coretests/src/android/transition/TransitionTest.java b/core/tests/coretests/src/android/transition/TransitionTest.java
index ab4320c..7e629f9 100644
--- a/core/tests/coretests/src/android/transition/TransitionTest.java
+++ b/core/tests/coretests/src/android/transition/TransitionTest.java
@@ -19,6 +19,7 @@
import android.animation.AnimatorSetActivity;
import android.app.Activity;
import android.graphics.Rect;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import android.transition.Transition.EpicenterCallback;
import android.util.ArrayMap;
@@ -30,6 +31,7 @@
import java.lang.reflect.Field;
+@LargeTest
public class TransitionTest extends ActivityInstrumentationTestCase2<AnimatorSetActivity> {
Activity mActivity;
public TransitionTest() {
diff --git a/core/tests/coretests/src/android/util/ArrayMapTest.java b/core/tests/coretests/src/android/util/ArrayMapTest.java
index 32aa29f..f0cc7f7 100644
--- a/core/tests/coretests/src/android/util/ArrayMapTest.java
+++ b/core/tests/coretests/src/android/util/ArrayMapTest.java
@@ -14,6 +14,7 @@
package android.util;
+import android.support.test.filters.LargeTest;
import android.util.ArrayMap;
import junit.framework.TestCase;
@@ -24,6 +25,7 @@
/**
* Unit tests for ArrayMap that don't belong in CTS.
*/
+@LargeTest
public class ArrayMapTest extends TestCase {
private static final String TAG = "ArrayMapTest";
ArrayMap<String, String> map = new ArrayMap<>();
diff --git a/core/tests/coretests/src/android/util/Base64Test.java b/core/tests/coretests/src/android/util/Base64Test.java
index 53368d4..3aee583 100644
--- a/core/tests/coretests/src/android/util/Base64Test.java
+++ b/core/tests/coretests/src/android/util/Base64Test.java
@@ -16,15 +16,17 @@
package android.util;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import junit.framework.TestCase;
+import android.support.test.filters.LargeTest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
import java.util.Random;
+import junit.framework.TestCase;
+@LargeTest
public class Base64Test extends TestCase {
private static final String TAG = "Base64Test";
diff --git a/core/tests/coretests/src/android/util/LocalLogTest.java b/core/tests/coretests/src/android/util/LocalLogTest.java
index a63c8a0..5144c85e 100644
--- a/core/tests/coretests/src/android/util/LocalLogTest.java
+++ b/core/tests/coretests/src/android/util/LocalLogTest.java
@@ -16,6 +16,8 @@
package android.util;
+import android.support.test.filters.LargeTest;
+
import junit.framework.TestCase;
import java.io.PrintWriter;
@@ -24,7 +26,7 @@
import java.util.Collections;
import java.util.List;
-
+@LargeTest
public class LocalLogTest extends TestCase {
public void testA() {
diff --git a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
index cb468bc..3f03db9 100644
--- a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
+++ b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
@@ -16,6 +16,8 @@
package android.util;
+import android.support.test.filters.LargeTest;
+
import junit.framework.TestCase;
import java.util.HashMap;
@@ -26,6 +28,7 @@
/**
* Tests for {@link LongSparseLongArray}.
*/
+@LargeTest
public class LongSparseLongArrayTest extends TestCase {
private static final String TAG = "LongSparseLongArrayTest";
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index 6dd787d..0d8c679 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -25,6 +25,7 @@
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
@@ -34,7 +35,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
@@ -45,19 +45,14 @@
/** This is not a consistent cutout. Useful for verifying insets in one go though. */
final DisplayCutout mCutoutNumbers = new DisplayCutout(
new Rect(1, 2, 3, 4),
- new Rect(5, 6, 7, 8),
- Arrays.asList(
- new Point(9, 10),
- new Point(11, 12),
- new Point(13, 14),
- new Point(15, 16)));
+ new Region(5, 6, 7, 8));
final DisplayCutout mCutoutTop = createCutoutTop();
@Test
public void hasCutout() throws Exception {
- assertFalse(NO_CUTOUT.hasCutout());
- assertTrue(mCutoutTop.hasCutout());
+ assertTrue(NO_CUTOUT.isEmpty());
+ assertFalse(mCutoutTop.isEmpty());
}
@Test
@@ -67,30 +62,12 @@
assertEquals(3, mCutoutNumbers.getSafeInsetRight());
assertEquals(4, mCutoutNumbers.getSafeInsetBottom());
- Rect safeInsets = new Rect();
- mCutoutNumbers.getSafeInsets(safeInsets);
-
- assertEquals(new Rect(1, 2, 3, 4), safeInsets);
+ assertEquals(new Rect(1, 2, 3, 4), mCutoutNumbers.getSafeInsets());
}
@Test
public void getBoundingRect() throws Exception {
- Rect boundingRect = new Rect();
- mCutoutTop.getBoundingRect(boundingRect);
-
- assertEquals(new Rect(50, 0, 75, 100), boundingRect);
- }
-
- @Test
- public void getBoundingPolygon() throws Exception {
- ArrayList<Point> boundingPolygon = new ArrayList<>();
- mCutoutTop.getBoundingPolygon(boundingPolygon);
-
- assertEquals(Arrays.asList(
- new Point(75, 0),
- new Point(50, 0),
- new Point(75, 100),
- new Point(50, 100)), boundingPolygon);
+ assertEquals(new Rect(50, 0, 75, 100), mCutoutTop.getBoundingRect());
}
@Test
@@ -167,104 +144,61 @@
assertEquals(cutout.getSafeInsetRight(), 0);
assertEquals(cutout.getSafeInsetBottom(), 0);
- assertFalse(cutout.hasCutout());
+ assertTrue(cutout.isEmpty());
}
@Test
public void inset_bounds() throws Exception {
DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4);
- Rect boundingRect = new Rect();
- cutout.getBoundingRect(boundingRect);
-
- assertEquals(new Rect(49, -2, 74, 98), boundingRect);
-
- ArrayList<Point> boundingPolygon = new ArrayList<>();
- cutout.getBoundingPolygon(boundingPolygon);
-
- assertEquals(Arrays.asList(
- new Point(74, -2),
- new Point(49, -2),
- new Point(74, 98),
- new Point(49, 98)), boundingPolygon);
+ assertEquals(new Rect(49, -2, 74, 98), cutout.getBoundingRect());
}
@Test
public void calculateRelativeTo_top() throws Exception {
DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 200, 400));
- Rect insets = new Rect();
- cutout.getSafeInsets(insets);
-
- assertEquals(new Rect(0, 100, 0, 0), insets);
+ assertEquals(new Rect(0, 100, 0, 0), cutout.getSafeInsets());
}
@Test
public void calculateRelativeTo_left() throws Exception {
DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 400, 200));
- Rect insets = new Rect();
- cutout.getSafeInsets(insets);
-
- assertEquals(new Rect(75, 0, 0, 0), insets);
+ assertEquals(new Rect(75, 0, 0, 0), cutout.getSafeInsets());
}
@Test
public void calculateRelativeTo_bottom() throws Exception {
DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, -300, 200, 100));
- Rect insets = new Rect();
- cutout.getSafeInsets(insets);
-
- assertEquals(new Rect(0, 0, 0, 100), insets);
+ assertEquals(new Rect(0, 0, 0, 100), cutout.getSafeInsets());
}
@Test
public void calculateRelativeTo_right() throws Exception {
DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-400, -200, 100, 100));
- Rect insets = new Rect();
- cutout.getSafeInsets(insets);
-
- assertEquals(new Rect(0, 0, 50, 0), insets);
+ assertEquals(new Rect(0, 0, 50, 0), cutout.getSafeInsets());
}
@Test
public void calculateRelativeTo_bounds() throws Exception {
DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-1000, -2000, 100, 200));
-
- Rect boundingRect = new Rect();
- cutout.getBoundingRect(boundingRect);
- assertEquals(new Rect(1050, 2000, 1075, 2100), boundingRect);
-
- ArrayList<Point> boundingPolygon = new ArrayList<>();
- cutout.getBoundingPolygon(boundingPolygon);
-
- assertEquals(Arrays.asList(
- new Point(1075, 2000),
- new Point(1050, 2000),
- new Point(1075, 2100),
- new Point(1050, 2100)), boundingPolygon);
+ assertEquals(new Rect(1050, 2000, 1075, 2100), cutout.getBoundingRect());
}
@Test
public void fromBoundingPolygon() throws Exception {
assertEquals(
- new DisplayCutout(
- new Rect(0, 0, 0, 0), // fromBoundingPolygon won't calculate safe insets.
- new Rect(50, 0, 75, 100),
- Arrays.asList(
- new Point(75, 0),
- new Point(50, 0),
- new Point(75, 100),
- new Point(50, 100))),
+ new Rect(50, 0, 75, 100),
DisplayCutout.fromBoundingPolygon(
Arrays.asList(
new Point(75, 0),
new Point(50, 0),
new Point(75, 100),
- new Point(50, 100))));
+ new Point(50, 100))).getBounds().getBounds());
}
@Test
@@ -324,24 +258,12 @@
}
private static DisplayCutout createCutoutTop() {
- return new DisplayCutout(
- new Rect(0, 100, 0, 0),
- new Rect(50, 0, 75, 100),
- Arrays.asList(
- new Point(75, 0),
- new Point(50, 0),
- new Point(75, 100),
- new Point(50, 100)));
+ return createCutoutWithInsets(0, 100, 0, 0);
}
private static DisplayCutout createCutoutWithInsets(int left, int top, int right, int bottom) {
return new DisplayCutout(
new Rect(left, top, right, bottom),
- new Rect(50, 0, 75, 100),
- Arrays.asList(
- new Point(75, 0),
- new Point(50, 0),
- new Point(75, 100),
- new Point(50, 100)));
+ new Region(50, 0, 75, 100));
}
}
diff --git a/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java b/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
index 23d0251..fba8eae 100644
--- a/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
+++ b/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
@@ -17,6 +17,7 @@
package android.view;
import android.content.Context;
+import android.support.test.filters.LargeTest;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.ActivityInstrumentationTestCase2;
@@ -36,6 +37,7 @@
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.Espresso.onView;
+@LargeTest
public class ScaleGestureDetectorTest extends ActivityInstrumentationTestCase2<ScaleGesture> {
private ScaleGesture mScaleGestureActivity;
diff --git a/core/tests/coretests/src/android/view/ViewAttachTest.java b/core/tests/coretests/src/android/view/ViewAttachTest.java
index 44fcd13..aa8f8d8 100644
--- a/core/tests/coretests/src/android/view/ViewAttachTest.java
+++ b/core/tests/coretests/src/android/view/ViewAttachTest.java
@@ -20,6 +20,7 @@
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.view.View;
@@ -28,6 +29,7 @@
import com.android.frameworks.coretests.R;
+@LargeTest
public class ViewAttachTest extends
ActivityInstrumentationTestCase2<ViewAttachTestActivity> {
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 2f32d13..4de8155 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;
@@ -42,7 +43,7 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
-
+@LargeTest
@RunWith(AndroidJUnit4.class)
public class AccessibilityCacheTest {
private static final int WINDOW_ID_1 = 0xBEEF;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index c1b2309..318d122 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -22,6 +22,7 @@
import android.os.Bundle;
import android.os.RemoteException;
+import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import libcore.util.EmptyArray;
@@ -36,6 +37,7 @@
/**
* Tests for AccessibilityInteractionClient
*/
+@LargeTest
@RunWith(AndroidJUnit4.class)
public class AccessibilityInteractionClientTest {
private static final int MOCK_CONNECTION_ID = 0xabcd;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 79e6316..b135025 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.fail;
+import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.ArraySet;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -29,6 +30,7 @@
import java.util.ArrayList;
+@LargeTest
@RunWith(AndroidJUnit4.class)
public class AccessibilityNodeInfoTest {
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
index e3c91e6..9edbf3e 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -26,6 +26,7 @@
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
@@ -34,6 +35,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+@LargeTest
@RunWith(AndroidJUnit4.class)
public class InputMethodInfoTest {
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
index 9347b27..8ed0d86 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
@@ -21,8 +21,10 @@
import com.android.internal.view.menu.MenuBuilder;
import android.content.pm.ActivityInfo;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase;
+@LargeTest
public class MenuLayoutLandscapeTest extends ActivityInstrumentationTestCase<MenuLayoutLandscape> {
private static final String LONG_TITLE = "Really really really really really really really really really really long title";
private static final String SHORT_TITLE = "Item";
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
index b053699..ccf1264 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
@@ -16,13 +16,12 @@
package android.view.menu;
-import android.util.KeyUtils;
-import com.android.internal.view.menu.IconMenuView;
-import com.android.internal.view.menu.MenuBuilder;
-
import android.content.pm.ActivityInfo;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase;
+import android.util.KeyUtils;
+@LargeTest
public class MenuLayoutPortraitTest extends ActivityInstrumentationTestCase<MenuLayoutPortrait> {
private static final String LONG_TITLE = "Really really really really really really really really really really long title";
private static final String SHORT_TITLE = "Item";
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index 9092c85..a8de374 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -16,10 +16,9 @@
package android.view.textclassifier;
+import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import android.os.LocaleList;
@@ -34,8 +33,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Collection;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TextClassificationManagerTest {
@@ -166,20 +163,50 @@
}
@Test
- public void testGenerateLinks() {
+ public void testGenerateLinks_phone() {
if (isTextClassifierDisabled()) return;
+ String text = "The number is +12122537077. See you tonight!";
+ assertThat(mClassifier.generateLinks(text, null),
+ isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE));
+ }
- checkGenerateLinksFindsLink(
- "The number is +12122537077. See you tonight!",
- "+12122537077",
- TextClassifier.TYPE_PHONE);
+ @Test
+ public void testGenerateLinks_exclude() {
+ if (isTextClassifierDisabled()) return;
+ String text = "The number is +12122537077. See you tonight!";
+ assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
+ new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_ALL)
+ .excludeEntities(TextClassifier.TYPE_PHONE))),
+ not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)));
+ }
- checkGenerateLinksFindsLink(
- "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you tonight!",
- "1600 Amphitheater Parkway, Mountain View, CA",
- TextClassifier.TYPE_ADDRESS);
+ @Test
+ public void testGenerateLinks_none_config() {
+ if (isTextClassifierDisabled()) return;
+ String text = "The number is +12122537077. See you tonight!";
+ assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
+ new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE))),
+ not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)));
+ }
- // TODO: Add more entity types when the model supports them.
+ @Test
+ public void testGenerateLinks_address() {
+ if (isTextClassifierDisabled()) return;
+ String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!";
+ assertThat(mClassifier.generateLinks(text, null),
+ isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA",
+ TextClassifier.TYPE_ADDRESS));
+ }
+
+ @Test
+ public void testGenerateLinks_include() {
+ if (isTextClassifierDisabled()) return;
+ String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!";
+ assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
+ new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE)
+ .includeEntities(TextClassifier.TYPE_ADDRESS))),
+ isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA",
+ TextClassifier.TYPE_ADDRESS));
}
@Test
@@ -193,25 +220,6 @@
return mClassifier == TextClassifier.NO_OP;
}
- private void checkGenerateLinksFindsLink(String text, String classifiedText, String type) {
- assertTrue(text.contains(classifiedText));
- int startIndex = text.indexOf(classifiedText);
- int endIndex = startIndex + classifiedText.length();
-
- Collection<TextLinks.TextLink> links = mClassifier.generateLinks(text, mLinksOptions)
- .getLinks();
- for (TextLinks.TextLink link : links) {
- if (text.subSequence(link.getStart(), link.getEnd()).equals(classifiedText)) {
- assertEquals(type, link.getEntity(0));
- assertEquals(startIndex, link.getStart());
- assertEquals(endIndex, link.getEnd());
- assertTrue(link.getConfidenceScore(type) > 0);
- return;
- }
- }
- fail(); // Subsequence was not identified.
- }
-
private static Matcher<TextSelection> isTextSelection(
final int startIndex, final int endIndex, final String type) {
return new BaseMatcher<TextSelection>() {
@@ -240,6 +248,31 @@
};
}
+ private static Matcher<TextLinks> isTextLinksContaining(
+ final String text, final String substring, final String type) {
+ return new BaseMatcher<TextLinks>() {
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("text=").appendValue(text)
+ .appendText(", substring=").appendValue(substring)
+ .appendText(", type=").appendValue(type);
+ }
+
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof TextLinks) {
+ for (TextLinks.TextLink link : ((TextLinks) o).getLinks()) {
+ if (text.subSequence(link.getStart(), link.getEnd()).equals(substring)) {
+ return type.equals(link.getEntity(0));
+ }
+ }
+ }
+ return false;
+ }
+ };
+ }
+
private static Matcher<TextClassification> isTextClassification(
final String text, final String type, final String intentUri) {
return new BaseMatcher<TextClassification>() {
diff --git a/core/tests/coretests/src/android/widget/DatePickerFocusTest.java b/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
index 513e40f..be85450 100644
--- a/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
+++ b/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.app.Instrumentation;
import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import android.view.KeyEvent;
import android.view.View;
@@ -28,6 +29,7 @@
/**
* Test {@link DatePicker} focus changes.
*/
+@LargeTest
public class DatePickerFocusTest extends ActivityInstrumentationTestCase2<DatePickerActivity> {
private Activity mActivity;
diff --git a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
index 28a3b67..2add221 100644
--- a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
+++ b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
@@ -22,6 +22,7 @@
import android.graphics.PointF;
import android.graphics.RectF;
+import android.support.test.filters.LargeTest;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,6 +32,7 @@
import java.util.Arrays;
import java.util.List;
+@LargeTest
@RunWith(JUnit4.class)
public final class SelectionActionModeHelperTest {
diff --git a/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
index b591e5f..43986ee 100644
--- a/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
+++ b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
@@ -18,6 +18,7 @@
import android.widget.focus.HorizontalFocusSearch;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase;
import android.test.suitebuilder.annotation.Suppress;
import android.widget.LinearLayout;
@@ -32,6 +33,7 @@
* various widths and vertical placements.
*/
// Suppress until bug http://b/issue?id=1416545 is fixed.
+@LargeTest
@Suppress
public class HorizontalFocusSearchTest extends ActivityInstrumentationTestCase<HorizontalFocusSearch> {
diff --git a/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
index f05d83a..f01422e 100644
--- a/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
+++ b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
@@ -18,6 +18,7 @@
import android.widget.focus.VerticalFocusSearch;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase;
import android.test.suitebuilder.annotation.Suppress;
import android.view.FocusFinder;
@@ -31,6 +32,7 @@
* various widths and horizontal placements.
*/
// Suppress until bug http://b/issue?id=1416545 is fixed
+@LargeTest
@Suppress
public class VerticalFocusSearchTest extends ActivityInstrumentationTestCase<VerticalFocusSearch> {
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
index 400fd7d..9a8e634 100644
--- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
@@ -16,12 +16,14 @@
package android.widget.listview.arrowscroll;
-import android.widget.listview.ListWithFirstScreenUnSelectable;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import android.view.KeyEvent;
import android.widget.ListView;
+import android.widget.listview.ListWithFirstScreenUnSelectable;
import android.widget.AdapterView;
+@LargeTest
public class ListWithFirstScreenUnSelectableTest
extends ActivityInstrumentationTestCase2<ListWithFirstScreenUnSelectable> {
private ListView mListView;
diff --git a/core/tests/packagemanagertests/Android.mk b/core/tests/packagemanagertests/Android.mk
index c1e8c98..5bfde78 100644
--- a/core/tests/packagemanagertests/Android.mk
+++ b/core/tests/packagemanagertests/Android.mk
@@ -10,7 +10,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
- frameworks-base-testutils
+ frameworks-base-testutils \
+ mockito-target-minus-junit4
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksCorePackageManagerTests
diff --git a/core/tests/privacytests/Android.mk b/core/tests/privacytests/Android.mk
new file mode 100644
index 0000000..7b11225
--- /dev/null
+++ b/core/tests/privacytests/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# Include all test java files.
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := junit rappor-tests android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_PACKAGE_NAME := FrameworksPrivacyLibraryTests
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/privacytests/AndroidManifest.xml b/core/tests/privacytests/AndroidManifest.xml
new file mode 100644
index 0000000..a0e5281
--- /dev/null
+++ b/core/tests/privacytests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.privacy">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.coretests.privacy"
+ android:label="Frameworks Privacy Library Tests" />
+
+</manifest>
diff --git a/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java
new file mode 100644
index 0000000..9166438
--- /dev/null
+++ b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.privacy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.privacy.internal.longitudinalreporting.LongitudinalReportingConfig;
+import android.privacy.internal.longitudinalreporting.LongitudinalReportingEncoder;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+/**
+ * Unit test for the {@link LongitudinalReportingEncoder}.
+ *
+ * As {@link LongitudinalReportingEncoder} is based on Rappor,
+ * most cases are covered by Rappor tests already.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LongitudinalReportingEncoderTest {
+
+ @Test
+ public void testLongitudinalReportingEncoder_config() throws Exception {
+ final LongitudinalReportingConfig config = new LongitudinalReportingConfig(
+ "Foo", // encoderId
+ 0.4, // probabilityF
+ 0.25, // probabilityP
+ 1); // probabilityQ
+ final LongitudinalReportingEncoder encoder =
+ LongitudinalReportingEncoder.createInsecureEncoderForTest(
+ config);
+ assertEquals("LongitudinalReporting", encoder.getConfig().getAlgorithm());
+ assertEquals(
+ "EncoderId: Foo, ProbabilityF: 0.400, ProbabilityP: 0.250, ProbabilityQ: 1.000",
+ encoder.getConfig().toString());
+ }
+
+ @Test
+ public void testLongitudinalReportingEncoder_basicIRRTest() throws Exception {
+ // Test if IRR can generate expected result when seed is fixed (insecure encoder)
+ final LongitudinalReportingConfig config = new LongitudinalReportingConfig(
+ "Foo", // encoderId
+ 0.4, // probabilityF
+ 0, // probabilityP
+ 0); // probabilityQ
+ // Use insecure encoder here to make sure seed is set.
+ final LongitudinalReportingEncoder encoder =
+ LongitudinalReportingEncoder.createInsecureEncoderForTest(
+ config);
+ assertEquals(1, encoder.encodeBoolean(true)[0]);
+ assertEquals(0, encoder.encodeBoolean(true)[0]);
+ assertEquals(1, encoder.encodeBoolean(true)[0]);
+ assertEquals(1, encoder.encodeBoolean(true)[0]);
+ assertEquals(1, encoder.encodeBoolean(true)[0]);
+ assertEquals(1, encoder.encodeBoolean(true)[0]);
+ assertEquals(0, encoder.encodeBoolean(true)[0]);
+ assertEquals(1, encoder.encodeBoolean(true)[0]);
+ assertEquals(1, encoder.encodeBoolean(true)[0]);
+ assertEquals(1, encoder.encodeBoolean(true)[0]);
+
+ assertEquals(0, encoder.encodeBoolean(false)[0]);
+ assertEquals(1, encoder.encodeBoolean(false)[0]);
+ assertEquals(1, encoder.encodeBoolean(false)[0]);
+ assertEquals(0, encoder.encodeBoolean(false)[0]);
+ assertEquals(0, encoder.encodeBoolean(false)[0]);
+ assertEquals(0, encoder.encodeBoolean(false)[0]);
+ assertEquals(1, encoder.encodeBoolean(false)[0]);
+ assertEquals(0, encoder.encodeBoolean(false)[0]);
+ assertEquals(0, encoder.encodeBoolean(false)[0]);
+ assertEquals(1, encoder.encodeBoolean(false)[0]);
+
+ // Test if IRR returns original result when f = 0
+ final LongitudinalReportingConfig config2 = new LongitudinalReportingConfig(
+ "Foo", // encoderId
+ 0, // probabilityF
+ 0, // probabilityP
+ 0); // probabilityQ
+ final LongitudinalReportingEncoder encoder2
+ = LongitudinalReportingEncoder.createEncoder(
+ config2, makeTestingUserSecret("secret2"));
+ for (int i = 0; i < 10; i++) {
+ assertEquals(1, encoder2.encodeBoolean(true)[0]);
+ }
+ for (int i = 0; i < 10; i++) {
+ assertEquals(0, encoder2.encodeBoolean(false)[0]);
+ }
+
+ // Test if IRR returns opposite result when f = 1
+ final LongitudinalReportingConfig config3 = new LongitudinalReportingConfig(
+ "Foo", // encoderId
+ 1, // probabilityF
+ 0, // probabilityP
+ 0); // probabilityQ
+ final LongitudinalReportingEncoder encoder3
+ = LongitudinalReportingEncoder.createEncoder(
+ config3, makeTestingUserSecret("secret3"));
+ for (int i = 0; i < 10; i++) {
+ assertEquals(1, encoder3.encodeBoolean(false)[0]);
+ }
+ for (int i = 0; i < 10; i++) {
+ assertEquals(0, encoder3.encodeBoolean(true)[0]);
+ }
+ }
+
+ @Test
+ public void testLongitudinalReportingEncoder_basicPRRTest() throws Exception {
+ // Should always return original value when p = 0
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ final LongitudinalReportingConfig config1 = new LongitudinalReportingConfig(
+ "Foo" + i, // encoderId
+ 0, // probabilityF
+ 0, // probabilityP
+ 0); // probabilityQ
+ final LongitudinalReportingEncoder encoder1
+ = LongitudinalReportingEncoder.createEncoder(
+ config1, makeTestingUserSecret("encoder" + j));
+ assertEquals(0, encoder1.encodeBoolean(false)[0]);
+ assertEquals(0, encoder1.encodeBoolean(false)[0]);
+ assertEquals(0, encoder1.encodeBoolean(false)[0]);
+ assertEquals(0, encoder1.encodeBoolean(false)[0]);
+ assertEquals(0, encoder1.encodeBoolean(false)[0]);
+ assertEquals(1, encoder1.encodeBoolean(true)[0]);
+ assertEquals(1, encoder1.encodeBoolean(true)[0]);
+ assertEquals(1, encoder1.encodeBoolean(true)[0]);
+ assertEquals(1, encoder1.encodeBoolean(true)[0]);
+ assertEquals(1, encoder1.encodeBoolean(true)[0]);
+ }
+ }
+
+ // Should always return false when p = 1, q = 0
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ final LongitudinalReportingConfig config2 = new LongitudinalReportingConfig(
+ "Foo" + i, // encoderId
+ 0, // probabilityF
+ 1, // probabilityP
+ 0); // probabilityQ
+ final LongitudinalReportingEncoder encoder2
+ = LongitudinalReportingEncoder.createEncoder(
+ config2, makeTestingUserSecret("encoder" + j));
+ assertEquals(0, encoder2.encodeBoolean(false)[0]);
+ assertEquals(0, encoder2.encodeBoolean(false)[0]);
+ assertEquals(0, encoder2.encodeBoolean(false)[0]);
+ assertEquals(0, encoder2.encodeBoolean(false)[0]);
+ assertEquals(0, encoder2.encodeBoolean(false)[0]);
+ assertEquals(0, encoder2.encodeBoolean(true)[0]);
+ assertEquals(0, encoder2.encodeBoolean(true)[0]);
+ assertEquals(0, encoder2.encodeBoolean(true)[0]);
+ assertEquals(0, encoder2.encodeBoolean(true)[0]);
+ assertEquals(0, encoder2.encodeBoolean(true)[0]);
+ }
+ }
+
+ // Should always return true when p = 1, q = 1
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ final LongitudinalReportingConfig config3 = new LongitudinalReportingConfig(
+ "Foo" + i, // encoderId
+ 0, // probabilityF
+ 1, // probabilityP
+ 1); // probabilityQ
+ final LongitudinalReportingEncoder encoder3
+ = LongitudinalReportingEncoder.createEncoder(
+ config3, makeTestingUserSecret("encoder" + j));
+ assertEquals(1, encoder3.encodeBoolean(false)[0]);
+ assertEquals(1, encoder3.encodeBoolean(false)[0]);
+ assertEquals(1, encoder3.encodeBoolean(false)[0]);
+ assertEquals(1, encoder3.encodeBoolean(false)[0]);
+ assertEquals(1, encoder3.encodeBoolean(false)[0]);
+ assertEquals(1, encoder3.encodeBoolean(true)[0]);
+ assertEquals(1, encoder3.encodeBoolean(true)[0]);
+ assertEquals(1, encoder3.encodeBoolean(true)[0]);
+ assertEquals(1, encoder3.encodeBoolean(true)[0]);
+ assertEquals(1, encoder3.encodeBoolean(true)[0]);
+ }
+ }
+
+ // PRR should return different value when encoder id is changed
+ boolean hasFalseResult1 = false;
+ boolean hasTrueResult1 = false;
+ for (int i = 0; i < 50; i++) {
+ boolean firstResult = false;
+ for (int j = 0; j < 10; j++) {
+ final LongitudinalReportingConfig config4 = new LongitudinalReportingConfig(
+ "Foo" + i, // encoderId
+ 0, // probabilityF
+ 1, // probabilityP
+ 0.5); // probabilityQ
+ final LongitudinalReportingEncoder encoder4
+ = LongitudinalReportingEncoder.createEncoder(
+ config4, makeTestingUserSecret("encoder4"));
+ boolean encodedFalse = encoder4.encodeBoolean(false)[0] > 0;
+ boolean encodedTrue = encoder4.encodeBoolean(true)[0] > 0;
+ // PRR should always give the same value when all parameters are the same
+ assertEquals(encodedTrue, encodedFalse);
+ if (j == 0) {
+ firstResult = encodedTrue;
+ } else {
+ assertEquals(firstResult, encodedTrue);
+ }
+ if (encodedTrue) {
+ hasTrueResult1 = true;
+ } else {
+ hasFalseResult1 = true;
+ }
+ }
+ }
+ // Ensure it has both true and false results when encoder id is different
+ assertTrue(hasTrueResult1);
+ assertTrue(hasFalseResult1);
+
+ // PRR should give different value when secret is changed
+ boolean hasFalseResult2 = false;
+ boolean hasTrueResult2 = false;
+ for (int i = 0; i < 50; i++) {
+ boolean firstResult = false;
+ for (int j = 0; j < 10; j++) {
+ final LongitudinalReportingConfig config5 = new LongitudinalReportingConfig(
+ "Foo", // encoderId
+ 0, // probabilityF
+ 1, // probabilityP
+ 0.5); // probabilityQ
+ final LongitudinalReportingEncoder encoder5
+ = LongitudinalReportingEncoder.createEncoder(
+ config5, makeTestingUserSecret("encoder" + i));
+ boolean encodedFalse = encoder5.encodeBoolean(false)[0] > 0;
+ boolean encodedTrue = encoder5.encodeBoolean(true)[0] > 0;
+ // PRR should always give the same value when parameters are the same
+ assertEquals(encodedTrue, encodedFalse);
+ if (j == 0) {
+ firstResult = encodedTrue;
+ } else {
+ assertEquals(firstResult, encodedTrue);
+ }
+ if (encodedTrue) {
+ hasTrueResult2 = true;
+ } else {
+ hasFalseResult2 = true;
+ }
+ }
+ }
+ // Ensure it has both true and false results when encoder id is different
+ assertTrue(hasTrueResult2);
+ assertTrue(hasFalseResult2);
+
+ // Confirm if PRR randomizer is working correctly
+ final int n1 = 1000;
+ final double p1 = 0.8;
+ final double expectedTrueSum1 = n1 * p1;
+ final double valueRange1 = 5 * Math.sqrt(n1 * p1 * (1 - p1));
+ int trueSum1 = 0;
+ for (int i = 0; i < n1; i++) {
+ final LongitudinalReportingConfig config6 = new LongitudinalReportingConfig(
+ "Foo", // encoderId
+ 0, // probabilityF
+ p1, // probabilityP
+ 1); // probabilityQ
+ final LongitudinalReportingEncoder encoder6
+ = LongitudinalReportingEncoder.createEncoder(
+ config6, makeTestingUserSecret("encoder" + i));
+ boolean encodedFalse = encoder6.encodeBoolean(false)[0] > 0;
+ if (encodedFalse) {
+ trueSum1 += 1;
+ }
+ }
+ // Total number of true(s) should be around the mean (1000 * 0.8)
+ assertTrue(trueSum1 < expectedTrueSum1 + valueRange1);
+ assertTrue(trueSum1 > expectedTrueSum1 - valueRange1);
+
+ // Confirm if PRR randomizer is working correctly
+ final int n2 = 1000;
+ final double p2 = 0.2;
+ final double expectedTrueSum2 = n2 * p2;
+ final double valueRange2 = 5 * Math.sqrt(n2 * p2 * (1 - p2));
+ int trueSum2 = 0;
+ for (int i = 0; i < n2; i++) {
+ final LongitudinalReportingConfig config7 = new LongitudinalReportingConfig(
+ "Foo", // encoderId
+ 0, // probabilityF
+ p2, // probabilityP
+ 1); // probabilityQ
+ final LongitudinalReportingEncoder encoder7
+ = LongitudinalReportingEncoder.createEncoder(
+ config7, makeTestingUserSecret("encoder" + i));
+ boolean encodedFalse = encoder7.encodeBoolean(false)[0] > 0;
+ if (encodedFalse) {
+ trueSum2 += 1;
+ }
+ }
+ // Total number of true(s) should be around the mean (1000 * 0.2)
+ assertTrue(trueSum2 < expectedTrueSum2 + valueRange2);
+ assertTrue(trueSum2 > expectedTrueSum2 - valueRange2);
+ }
+
+ @Test
+ public void testLongitudinalReportingEncoder_basicIRRwithPRRTest() throws Exception {
+ // Verify PRR result will run IRR
+ boolean hasFalseResult1 = false;
+ boolean hasTrueResult1 = false;
+ for (int i = 0; i < 50; i++) {
+ final LongitudinalReportingConfig config1 = new LongitudinalReportingConfig(
+ "Foo", // encoderId
+ 0.5, // probabilityF
+ 1, // probabilityP
+ 1); // probabilityQ
+ final LongitudinalReportingEncoder encoder1
+ = LongitudinalReportingEncoder.createEncoder(
+ config1, makeTestingUserSecret("encoder1"));
+ if (encoder1.encodeBoolean(false)[0] > 0) {
+ hasTrueResult1 = true;
+ } else {
+ hasFalseResult1 = true;
+ }
+ }
+ assertTrue(hasTrueResult1);
+ assertTrue(hasFalseResult1);
+
+ // When secret is different, some device should use PRR result, some should use IRR result
+ boolean hasFalseResult2 = false;
+ boolean hasTrueResult2 = false;
+ for (int i = 0; i < 50; i++) {
+ final LongitudinalReportingConfig config2 = new LongitudinalReportingConfig(
+ "Foo", // encoderId
+ 1, // probabilityF
+ 0.5, // probabilityP
+ 1); // probabilityQ
+ final LongitudinalReportingEncoder encoder2
+ = LongitudinalReportingEncoder.createEncoder(
+ config2, makeTestingUserSecret("encoder" + i));
+ if (encoder2.encodeBoolean(false)[0] > 0) {
+ hasTrueResult2 = true;
+ } else {
+ hasFalseResult2 = true;
+ }
+ }
+ assertTrue(hasTrueResult2);
+ assertTrue(hasFalseResult2);
+ }
+
+ @Test
+ public void testLongTermRandomizedResult() throws Exception {
+ // Verify getLongTermRandomizedResult can return expected result when parameters are fixed.
+ final boolean[] expectedResult =
+ new boolean[]{true, false, true, true, true,
+ false, false, false, true, false,
+ false, false, false, true, true,
+ true, true, false, true, true,
+ true, true, false, true, true};
+ for (int i = 0; i < 5; i++) {
+ for (int j = 0; j < 5; j++) {
+ boolean result = LongitudinalReportingEncoder.getLongTermRandomizedResult(0.5,
+ true, makeTestingUserSecret("secret" + i), "encoder" + j);
+ assertEquals(expectedResult[i * 5 + j], result);
+ }
+ }
+ }
+
+ private static byte[] makeTestingUserSecret(String testingSecret) throws Exception {
+ // We generate the fake user secret by concatenating three copies of the
+ // 16 byte MD5 hash of the testingSecret string encoded in UTF 8.
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ byte[] digest = md5.digest(testingSecret.getBytes(StandardCharsets.UTF_8));
+ assertEquals(16, digest.length);
+ return ByteBuffer.allocate(48).put(digest).put(digest).put(digest).array();
+ }
+}
diff --git a/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java
new file mode 100644
index 0000000..dad98b8
--- /dev/null
+++ b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.privacy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.privacy.internal.rappor.RapporConfig;
+import android.privacy.internal.rappor.RapporEncoder;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+/**
+ * Unit test for the {@link RapporEncoder}.
+ * Most of the tests are done in external/rappor/client/javatest/ already.
+ * Tests here are just make sure the {@link RapporEncoder} wrap Rappor correctly.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RapporEncoderTest {
+
+ @Test
+ public void testRapporEncoder_config() throws Exception {
+ final RapporConfig config = new RapporConfig(
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes)
+ final RapporEncoder encoder = RapporEncoder.createEncoder(config,
+ makeTestingUserSecret("encoder1"));
+ assertEquals("Rappor", encoder.getConfig().getAlgorithm());
+ assertEquals("EncoderId: Foo, NumBits: 8, ProbabilityF: 0.102, "
+ + "ProbabilityP: 0.250, ProbabilityQ: 0.750, NumCohorts: 1, "
+ + "NumBloomHashes: 2", encoder.getConfig().toString());
+ }
+
+ @Test
+ public void testRapporEncoder_basicIRRTest() throws Exception {
+ final RapporConfig config = new RapporConfig(
+ "Foo", // encoderId
+ 12, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts (so must be cohort 0)
+ 2); // numBloomHashes
+ // Use insecure encoder here as we want to get the exact output.
+ final RapporEncoder encoder = RapporEncoder.createInsecureEncoderForTest(config);
+ assertEquals(768, toLong(encoder.encodeString("Testing")));
+ }
+
+ @Test
+ public void testRapporEncoder_IRRWithPRR() throws Exception {
+ int numBits = 8;
+ final long inputValue = 254L;
+ final long prrValue = 250L;
+ final long prrAndIrrValue = 184L;
+
+ final RapporConfig config1 = new RapporConfig(
+ "Foo", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ // Use insecure encoder here as we want to get the exact output.
+ final RapporEncoder encoder1 = RapporEncoder.createInsecureEncoderForTest(config1);
+ // Verify that PRR is working as expected.
+ assertEquals(prrValue, toLong(encoder1.encodeBits(toBytes(inputValue))));
+ assertTrue(encoder1.isInsecureEncoderForTest());
+
+ // Verify that IRR is working as expected.
+ final RapporConfig config2 = new RapporConfig(
+ "Foo", // encoderId
+ numBits, // numBits,
+ 0, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ // Use insecure encoder here as we want to get the exact output.
+ final RapporEncoder encoder2 = RapporEncoder.createInsecureEncoderForTest(config2);
+ assertEquals(prrAndIrrValue, toLong(encoder2.encodeBits(toBytes(prrValue))));
+
+ // Test that end-to-end is the result of PRR + IRR.
+ final RapporConfig config3 = new RapporConfig(
+ "Foo", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ final RapporEncoder encoder3 = RapporEncoder.createInsecureEncoderForTest(config3);
+ // Verify that PRR is working as expected.
+ assertEquals(prrAndIrrValue, toLong(encoder3.encodeBits(toBytes(inputValue))));
+ }
+
+ @Test
+ public void testRapporEncoder_ensureSecureEncoderIsSecure() throws Exception {
+ int numBits = 8;
+ final long inputValue = 254L;
+ final long prrValue = 250L;
+ final long prrAndIrrValue = 184L;
+
+ final RapporConfig config1 = new RapporConfig(
+ "Foo", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ final RapporEncoder encoder1 = RapporEncoder.createEncoder(config1,
+ makeTestingUserSecret("secret1"));
+ // Verify that PRR is working as expected, not affected by random seed.
+ assertEquals(prrValue, toLong(encoder1.encodeBits(toBytes(inputValue))));
+ assertFalse(encoder1.isInsecureEncoderForTest());
+
+ boolean hasDifferentResult2 = false;
+ for (int i = 0; i < 5; i++) {
+ final RapporConfig config2 = new RapporConfig(
+ "Foo", // encoderId
+ numBits, // numBits,
+ 0, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ final RapporEncoder encoder2 = RapporEncoder.createEncoder(config2,
+ makeTestingUserSecret("secret1"));
+ hasDifferentResult2 |= (prrAndIrrValue != toLong(
+ encoder2.encodeBits(toBytes(prrValue))));
+ }
+ // Ensure it's not getting same result as it has random seed while encoder id and secret
+ // is the same.
+ assertTrue(hasDifferentResult2);
+
+ boolean hasDifferentResults3 = false;
+ for (int i = 0; i < 5; i++) {
+ final RapporConfig config3 = new RapporConfig(
+ "Foo", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ final RapporEncoder encoder3 = RapporEncoder.createEncoder(config3,
+ makeTestingUserSecret("secret1"));
+ hasDifferentResults3 |= (prrAndIrrValue != toLong(
+ encoder3.encodeBits(toBytes(inputValue))));
+ }
+ // Ensure it's not getting same result as it has random seed while encoder id and secret
+ // is the same.
+ assertTrue(hasDifferentResults3);
+ }
+
+ private static byte[] toBytes(long value) {
+ return ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array();
+ }
+
+ private static long toLong(byte[] bytes) {
+ ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(bytes);
+ buffer.rewind();
+ return buffer.getLong();
+ }
+
+ private static byte[] makeTestingUserSecret(String testingSecret) throws Exception {
+ // We generate the fake user secret by concatenating three copies of the
+ // 16 byte MD5 hash of the testingSecret string encoded in UTF 8.
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ byte[] digest = md5.digest(testingSecret.getBytes(StandardCharsets.UTF_8));
+ assertEquals(16, digest.length);
+ return ByteBuffer.allocate(48).put(digest).put(digest).put(digest).array();
+ }
+}
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
new file mode 100644
index 0000000..60416a7
--- /dev/null
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -0,0 +1,665 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RawRes;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.NinePatchDrawable;
+
+import java.nio.ByteBuffer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ArrayIndexOutOfBoundsException;
+import java.lang.NullPointerException;
+import java.lang.RuntimeException;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Class for decoding images as {@link Bitmap}s or {@link Drawable}s.
+ * @hide
+ */
+public final class ImageDecoder {
+ /**
+ * Source of the encoded image data.
+ */
+ public static abstract class Source {
+ /* @hide */
+ Resources getResources() { return null; }
+
+ /* @hide */
+ void close() {}
+
+ /* @hide */
+ abstract ImageDecoder createImageDecoder();
+ };
+
+ private static class ByteArraySource extends Source {
+ ByteArraySource(byte[] data, int offset, int length) {
+ mData = data;
+ mOffset = offset;
+ mLength = length;
+ };
+ private final byte[] mData;
+ private final int mOffset;
+ private final int mLength;
+
+ @Override
+ public ImageDecoder createImageDecoder() {
+ return nCreate(mData, mOffset, mLength);
+ }
+ }
+
+ private static class ByteBufferSource extends Source {
+ ByteBufferSource(ByteBuffer buffer) {
+ mBuffer = buffer;
+ }
+ private final ByteBuffer mBuffer;
+
+ @Override
+ public ImageDecoder createImageDecoder() {
+ if (!mBuffer.isDirect() && mBuffer.hasArray()) {
+ int offset = mBuffer.arrayOffset() + mBuffer.position();
+ int length = mBuffer.limit() - mBuffer.position();
+ return nCreate(mBuffer.array(), offset, length);
+ }
+ return nCreate(mBuffer, mBuffer.position(), mBuffer.limit());
+ }
+ }
+
+ private static class ResourceSource extends Source {
+ ResourceSource(Resources res, int resId)
+ throws Resources.NotFoundException {
+ // Test that the resource can be found.
+ InputStream is = null;
+ try {
+ is = res.openRawResource(resId);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ mResources = res;
+ mResId = resId;
+ }
+
+ final Resources mResources;
+ final int mResId;
+ // This is just stored here in order to keep the underlying Asset
+ // alive. FIXME: Can I access the Asset (and keep it alive) without
+ // this object?
+ InputStream mInputStream;
+
+ @Override
+ public Resources getResources() { return mResources; }
+
+ @Override
+ public ImageDecoder createImageDecoder() {
+ // FIXME: Can I bypass creating the stream?
+ try {
+ mInputStream = mResources.openRawResource(mResId);
+ } catch (Resources.NotFoundException e) {
+ // This should never happen, since we already tested in the
+ // constructor.
+ }
+ if (!(mInputStream instanceof AssetManager.AssetInputStream)) {
+ // This should never happen.
+ throw new RuntimeException("Resource is not an asset?");
+ }
+ long asset = ((AssetManager.AssetInputStream) mInputStream).getNativeAsset();
+ return nCreate(asset);
+ }
+
+ @Override
+ public void close() {
+ try {
+ mInputStream.close();
+ } catch (IOException e) {
+ } finally {
+ mInputStream = null;
+ }
+ }
+ }
+
+ /**
+ * Contains information about the encoded image.
+ */
+ public static class ImageInfo {
+ public final int width;
+ public final int height;
+ // TODO?: Add more info? mimetype, ninepatch etc?
+
+ ImageInfo(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+ };
+
+ /**
+ * Used if the provided data is incomplete.
+ *
+ * There may be a partial image to display.
+ */
+ public class IncompleteException extends Exception {};
+
+ /**
+ * Used if the provided data is corrupt.
+ *
+ * There may be a partial image to display.
+ */
+ public class CorruptException extends Exception {};
+
+ /**
+ * Optional listener supplied to {@link #decodeDrawable} or
+ * {@link #decodeBitmap}.
+ */
+ public static interface OnHeaderDecodedListener {
+ /**
+ * Called when the header is decoded and the size is known.
+ *
+ * @param info Information about the encoded image.
+ * @param decoder allows changing the default settings of the decode.
+ */
+ public void onHeaderDecoded(ImageInfo info, ImageDecoder decoder);
+
+ };
+
+ /**
+ * Optional listener supplied to the ImageDecoder.
+ */
+ public static interface OnExceptionListener {
+ /**
+ * Called when there is a problem in the stream or in the data.
+ * FIXME: Or do not allow streams?
+ * FIXME: Report how much of the image has been decoded?
+ *
+ * @param e Exception containing information about the error.
+ * @return True to create and return a {@link Drawable}/
+ * {@link Bitmap} with partial data. False to return
+ * {@code null}. True is the default.
+ */
+ public boolean onException(Exception e);
+ };
+
+ // Fields
+ private long mNativePtr;
+ private final int mWidth;
+ private final int mHeight;
+
+ private int mDesiredWidth;
+ private int mDesiredHeight;
+ private int mAllocator = DEFAULT_ALLOCATOR;
+ private boolean mRequireUnpremultiplied = false;
+ private boolean mMutable = false;
+ private boolean mPreferRamOverQuality = false;
+ private boolean mAsAlphaMask = false;
+ private Rect mCropRect;
+
+ private PostProcess mPostProcess;
+ private OnExceptionListener mOnExceptionListener;
+
+
+ /**
+ * Private constructor called by JNI. {@link #recycle} must be
+ * called after decoding to delete native resources.
+ */
+ @SuppressWarnings("unused")
+ private ImageDecoder(long nativePtr, int width, int height) {
+ mNativePtr = nativePtr;
+ mWidth = width;
+ mHeight = height;
+ mDesiredWidth = width;
+ mDesiredHeight = height;
+ }
+
+ /**
+ * Create a new {@link Source} from an asset.
+ *
+ * @param res the {@link Resources} object containing the image data.
+ * @param resId resource ID of the image data.
+ * // FIXME: Can be an @DrawableRes?
+ * @return a new Source object, which can be passed to
+ * {@link #decodeDrawable} or {@link #decodeBitmap}.
+ * @throws Resources.NotFoundException if the asset does not exist.
+ */
+ public static Source createSource(@NonNull Resources res, @RawRes int resId)
+ throws Resources.NotFoundException {
+ return new ResourceSource(res, resId);
+ }
+
+ /**
+ * Create a new {@link Source} from a byte array.
+ * @param data byte array of compressed image data.
+ * @param offset offset into data for where the decoder should begin
+ * parsing.
+ * @param length number of bytes, beginning at offset, to parse.
+ * @throws NullPointerException if data is null.
+ * @throws ArrayIndexOutOfBoundsException if offset and length are
+ * not within data.
+ */
+ // TODO: Overloads that don't use offset, length
+ public static Source createSource(@NonNull byte[] data, int offset,
+ int length) throws ArrayIndexOutOfBoundsException {
+ if (data == null) {
+ throw new NullPointerException("null byte[] in createSource!");
+ }
+ if (offset < 0 || length < 0 || offset >= data.length ||
+ offset + length > data.length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "invalid offset/length!");
+ }
+ return new ByteArraySource(data, offset, length);
+ }
+
+ /**
+ * Create a new {@link Source} from a {@link java.nio.ByteBuffer}.
+ *
+ * The returned {@link Source} effectively takes ownership of the
+ * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after
+ * this call.
+ *
+ * Decoding will start from {@link java.nio.ByteBuffer#position()}.
+ */
+ public static Source createSource(ByteBuffer buffer) {
+ return new ByteBufferSource(buffer);
+ }
+
+ /**
+ * Return the width and height of a given sample size.
+ *
+ * This takes an input that functions like
+ * {@link BitmapFactory.Options#inSampleSize}. It returns a width and
+ * height that can be acheived by sampling the encoded image. Other widths
+ * and heights may be supported, but will require an additional (internal)
+ * scaling step. Such internal scaling is *not* supported with
+ * {@link #requireUnpremultiplied}.
+ *
+ * @param sampleSize Sampling rate of the encoded image.
+ * @return Point {@link Point#x} and {@link Point#y} correspond to the
+ * width and height after sampling.
+ */
+ public Point getSampledSize(int sampleSize) {
+ if (sampleSize <= 0) {
+ throw new IllegalArgumentException("sampleSize must be positive! "
+ + "provided " + sampleSize);
+ }
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("ImageDecoder is recycled!");
+ }
+
+ return nGetSampledSize(mNativePtr, sampleSize);
+ }
+
+ // Modifiers
+ /**
+ * Resize the output to have the following size.
+ *
+ * @param width must be greater than 0.
+ * @param height must be greater than 0.
+ */
+ public void resize(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Dimensions must be positive! "
+ + "provided (" + width + ", " + height + ")");
+ }
+
+ mDesiredWidth = width;
+ mDesiredHeight = height;
+ }
+
+ /**
+ * Resize based on a sample size.
+ *
+ * This has the same effect as passing the result of
+ * {@link #getSampledSize} to {@link #resize(int, int)}.
+ *
+ * @param sampleSize Sampling rate of the encoded image.
+ */
+ public void resize(int sampleSize) {
+ Point dimensions = this.getSampledSize(sampleSize);
+ this.resize(dimensions.x, dimensions.y);
+ }
+
+ // These need to stay in sync with ImageDecoder.cpp's Allocator enum.
+ /**
+ * Use the default allocation for the pixel memory.
+ *
+ * Will typically result in a {@link Bitmap.Config#HARDWARE}
+ * allocation, but may be software for small images. In addition, this will
+ * switch to software when HARDWARE is incompatible, e.g.
+ * {@link #setMutable}, {@link #setAsAlphaMask}.
+ */
+ public static final int DEFAULT_ALLOCATOR = 0;
+
+ /**
+ * Use a software allocation for the pixel memory.
+ *
+ * Useful for drawing to a software {@link Canvas} or for
+ * accessing the pixels on the final output.
+ */
+ public static final int SOFTWARE_ALLOCATOR = 1;
+
+ /**
+ * Use shared memory for the pixel memory.
+ *
+ * Useful for sharing across processes.
+ */
+ public static final int SHARED_MEMORY_ALLOCATOR = 2;
+
+ /**
+ * Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}.
+ *
+ * This will throw an {@link java.lang.IllegalStateException} when combined
+ * with incompatible options, like {@link #setMutable} or
+ * {@link #setAsAlphaMask}.
+ */
+ public static final int HARDWARE_ALLOCATOR = 3;
+
+ /** @hide **/
+ @Retention(SOURCE)
+ @IntDef({ DEFAULT_ALLOCATOR, SOFTWARE_ALLOCATOR, SHARED_MEMORY_ALLOCATOR,
+ HARDWARE_ALLOCATOR })
+ public @interface Allocator {};
+
+ /**
+ * Choose the backing for the pixel memory.
+ *
+ * This is ignored for animated drawables.
+ *
+ * TODO: Allow accessing the backing from the Bitmap.
+ *
+ * @param allocator Type of allocator to use.
+ */
+ public void setAllocator(@Allocator int allocator) {
+ if (allocator < DEFAULT_ALLOCATOR || allocator > HARDWARE_ALLOCATOR) {
+ throw new IllegalArgumentException("invalid allocator " + allocator);
+ }
+ mAllocator = allocator;
+ }
+
+ /**
+ * Create a {@link Bitmap} with unpremultiplied pixels.
+ *
+ * By default, ImageDecoder will create a {@link Bitmap} with
+ * premultiplied pixels, which is required for drawing with the
+ * {@link android.view.View} system (i.e. to a {@link Canvas}). Calling
+ * this method will result in {@link #decodeBitmap} returning a
+ * {@link Bitmap} with unpremultiplied pixels. See
+ * {@link Bitmap#isPremultiplied}. Incompatible with
+ * {@link #decodeDrawable}; attempting to decode an unpremultiplied
+ * {@link Drawable} will throw an {@link java.lang.IllegalStateException}.
+ */
+ public void requireUnpremultiplied() {
+ mRequireUnpremultiplied = true;
+ }
+
+ /**
+ * Modify the image after decoding and scaling.
+ *
+ * This allows adding effects prior to returning a {@link Drawable} or
+ * {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap},
+ * this is the only way to process the image after decoding.
+ *
+ * If set on a nine-patch image, the nine-patch data is ignored.
+ *
+ * For an animated image, the drawing commands drawn on the {@link Canvas}
+ * will be recorded immediately and then applied to each frame.
+ */
+ public void setPostProcess(PostProcess p) {
+ mPostProcess = p;
+ }
+
+ /**
+ * Set (replace) the {@link OnExceptionListener} on this object.
+ *
+ * Will be called if there is an error in the input. Without one, a
+ * partial {@link Bitmap} will be created.
+ */
+ public void setOnExceptionListener(OnExceptionListener l) {
+ mOnExceptionListener = l;
+ }
+
+ /**
+ * Crop the output to {@code subset} of the (possibly) scaled image.
+ *
+ * {@code subset} must be contained within the size set by {@link #resize}
+ * or the bounds of the image if resize was not called. Otherwise an
+ * {@link IllegalStateException} will be thrown.
+ *
+ * NOT intended as a replacement for
+ * {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
+ * but merely crops the output.
+ */
+ public void crop(Rect subset) {
+ mCropRect = subset;
+ }
+
+ /**
+ * Create a mutable {@link Bitmap}.
+ *
+ * By default, a {@link Bitmap} created will be immutable, but that can be
+ * changed with this call.
+ *
+ * Incompatible with {@link #HARDWARE_ALLOCATOR}, because
+ * {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable. Attempting to
+ * combine them will throw an {@link java.lang.IllegalStateException}.
+ *
+ * Incompatible with {@link #decodeDrawable}, which would require
+ * retrieving the Bitmap from the returned Drawable in order to modify.
+ * Attempting to decode a mutable {@link Drawable} will throw an
+ * {@link java.lang.IllegalStateException}
+ */
+ public void setMutable() {
+ mMutable = true;
+ }
+
+ /**
+ * Potentially save RAM at the expense of quality.
+ *
+ * This may result in a {@link Bitmap} with a denser {@link Bitmap.Config},
+ * depending on the image. For example, for an opaque {@link Bitmap}, this
+ * may result in a {@link Bitmap.Config} with no alpha information.
+ */
+ public void setPreferRamOverQuality() {
+ mPreferRamOverQuality = true;
+ }
+
+ /**
+ * Potentially treat the output as an alpha mask.
+ *
+ * If the image is encoded in a format with only one channel, treat that
+ * channel as alpha. Otherwise this call has no effect.
+ *
+ * Incompatible with {@link #HARDWARE_ALLOCATOR}. Trying to combine them
+ * will throw an {@link java.lang.IllegalStateException}.
+ */
+ public void setAsAlphaMask() {
+ mAsAlphaMask = true;
+ }
+
+ /**
+ * Clean up resources.
+ *
+ * ImageDecoder has a private constructor, and will always be recycled
+ * by decodeDrawable or decodeBitmap which creates it, so there is no
+ * need for a finalizer.
+ */
+ private void recycle() {
+ if (mNativePtr == 0) {
+ return;
+ }
+ nRecycle(mNativePtr);
+ mNativePtr = 0;
+ }
+
+ private void checkState() {
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("Cannot reuse ImageDecoder.Source!");
+ }
+
+ checkSubset(mDesiredWidth, mDesiredHeight, mCropRect);
+
+ if (mAllocator == HARDWARE_ALLOCATOR) {
+ if (mMutable) {
+ throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!");
+ }
+ if (mAsAlphaMask) {
+ throw new IllegalStateException("Cannot make HARDWARE Alpha mask Bitmap!");
+ }
+ }
+
+ if (mPostProcess != null && mRequireUnpremultiplied) {
+ throw new IllegalStateException("Cannot draw to unpremultiplied pixels!");
+ }
+ }
+
+ private static void checkSubset(int width, int height, Rect r) {
+ if (r == null) {
+ return;
+ }
+ if (r.left < 0 || r.top < 0 || r.right > width || r.bottom > height) {
+ throw new IllegalStateException("Subset " + r + " not contained by "
+ + "scaled image bounds: (" + width + " x " + height + ")");
+ }
+ }
+
+ /**
+ * Create a {@link Drawable}.
+ */
+ public static Drawable decodeDrawable(Source src, OnHeaderDecodedListener listener) {
+ ImageDecoder decoder = src.createImageDecoder();
+ if (decoder == null) {
+ return null;
+ }
+
+ if (listener != null) {
+ ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight);
+ listener.onHeaderDecoded(info, decoder);
+ }
+
+ decoder.checkState();
+
+ if (decoder.mRequireUnpremultiplied) {
+ // Though this could be supported (ignored) for opaque images, it
+ // seems better to always report this error.
+ throw new IllegalStateException("Cannot decode a Drawable with" +
+ " unpremultiplied pixels!");
+ }
+
+ if (decoder.mMutable) {
+ throw new IllegalStateException("Cannot decode a mutable Drawable!");
+ }
+
+ try {
+ Bitmap bm = nDecodeBitmap(decoder.mNativePtr,
+ decoder.mOnExceptionListener,
+ decoder.mPostProcess,
+ decoder.mDesiredWidth, decoder.mDesiredHeight,
+ decoder.mCropRect,
+ false, // decoder.mMutable
+ decoder.mAllocator,
+ false, // decoder.mRequireUnpremultiplied
+ decoder.mPreferRamOverQuality,
+ decoder.mAsAlphaMask
+ );
+ if (bm == null) {
+ return null;
+ }
+
+ Resources res = src.getResources();
+ if (res == null) {
+ bm.setDensity(Bitmap.DENSITY_NONE);
+ }
+
+ byte[] np = bm.getNinePatchChunk();
+ if (np != null && NinePatch.isNinePatchChunk(np)) {
+ Rect opticalInsets = new Rect();
+ bm.getOpticalInsets(opticalInsets);
+ Rect padding = new Rect();
+ nGetPadding(decoder.mNativePtr, padding);
+ return new NinePatchDrawable(res, bm, np, padding,
+ opticalInsets, null);
+ }
+
+ // TODO: Handle animation.
+ return new BitmapDrawable(res, bm);
+ } finally {
+ decoder.recycle();
+ src.close();
+ }
+ }
+
+ /**
+ * Create a {@link Bitmap}.
+ */
+ public static Bitmap decodeBitmap(Source src, OnHeaderDecodedListener listener) {
+ ImageDecoder decoder = src.createImageDecoder();
+ if (decoder == null) {
+ return null;
+ }
+
+ if (listener != null) {
+ ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight);
+ listener.onHeaderDecoded(info, decoder);
+ }
+
+ decoder.checkState();
+
+ try {
+ return nDecodeBitmap(decoder.mNativePtr,
+ decoder.mOnExceptionListener,
+ decoder.mPostProcess,
+ decoder.mDesiredWidth, decoder.mDesiredHeight,
+ decoder.mCropRect,
+ decoder.mMutable,
+ decoder.mAllocator,
+ decoder.mRequireUnpremultiplied,
+ decoder.mPreferRamOverQuality,
+ decoder.mAsAlphaMask);
+ } finally {
+ decoder.recycle();
+ src.close();
+ }
+ }
+
+ private static native ImageDecoder nCreate(long asset);
+ private static native ImageDecoder nCreate(ByteBuffer buffer,
+ int position,
+ int limit);
+ private static native ImageDecoder nCreate(byte[] data, int offset,
+ int length);
+ private static native Bitmap nDecodeBitmap(long nativePtr,
+ OnExceptionListener listener,
+ PostProcess postProcess,
+ int width, int height,
+ Rect cropRect, boolean mutable,
+ int allocator, boolean requireUnpremul,
+ boolean preferRamOverQuality, boolean asAlphaMask);
+ private static native Point nGetSampledSize(long nativePtr,
+ int sampleSize);
+ private static native void nGetPadding(long nativePtr, Rect outRect);
+ private static native void nRecycle(long nativePtr);
+}
diff --git a/graphics/java/android/graphics/PostProcess.java b/graphics/java/android/graphics/PostProcess.java
new file mode 100644
index 0000000..c5a31e8
--- /dev/null
+++ b/graphics/java/android/graphics/PostProcess.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.drawable.Drawable;
+
+
+/**
+ * Helper interface for adding custom processing to an image.
+ *
+ * The image being processed may be a {@link Drawable}, {@link Bitmap} or frame
+ * of an animated image produced by {@link ImageDecoder}. This is called before
+ * the requested object is returned.
+ *
+ * This custom processing also applies to image types that are otherwise
+ * immutable, such as {@link Bitmap.Config#HARDWARE}.
+ *
+ * On an animated image, the callback will only be called once, but the drawing
+ * commands will be applied to each frame, as if the {@code Canvas} had been
+ * returned by {@link Picture#beginRecording}.
+ *
+ * Supplied to ImageDecoder via {@link ImageDecoder#setPostProcess}.
+ * @hide
+ */
+public interface PostProcess {
+ /**
+ * Do any processing after (for example) decoding.
+ *
+ * Drawing to the {@link Canvas} will behave as if the initial processing
+ * (e.g. decoding) already exists in the Canvas. An implementation can draw
+ * effects on top of this, or it can even draw behind it using
+ * {@link PorterDuff.Mode#DST_OVER}. A common effect is to add transparency
+ * to the corners to achieve rounded corners. That can be done with the
+ * following code:
+ *
+ * <code>
+ * Path path = new Path();
+ * path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
+ * path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW);
+ * Paint paint = new Paint();
+ * paint.setAntiAlias(true);
+ * paint.setColor(Color.TRANSPARENT);
+ * paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+ * canvas.drawPath(path, paint);
+ * return PixelFormat.TRANSLUCENT;
+ * </code>
+ *
+ *
+ * @param canvas The {@link Canvas} to draw to.
+ * @param width Width of {@code canvas}. Anything drawn outside of this
+ * will be ignored.
+ * @param height Height of {@code canvas}. Anything drawn outside of this
+ * will be ignored.
+ * @return Opacity of the result after drawing.
+ * {@link PixelFormat#UNKNOWN} means that the implementation did not
+ * change whether the image has alpha. Return this unless you added
+ * transparency (e.g. with the code above, in which case you should
+ * return {@code PixelFormat.TRANSLUCENT}) or you forced the image to
+ * be opaque (e.g. by drawing everywhere with an opaque color and
+ * {@code PorterDuff.Mode.DST_OVER}, in which case you should return
+ * {@code PixelFormat.OPAQUE}).
+ * {@link PixelFormat#TRANSLUCENT} means that the implementation added
+ * transparency. This is safe to return even if the image already had
+ * transparency. This is also safe to return if the result is opaque,
+ * though it may draw more slowly.
+ * {@link PixelFormat#OPAQUE} means that the implementation forced the
+ * image to be opaque. This is safe to return even if the image was
+ * already opaque.
+ * {@link PixelFormat#TRANSPARENT} (or any other integer) is not
+ * allowed, and will result in throwing an
+ * {@link java.lang.IllegalArgumentException}.
+ */
+ @PixelFormat.Opacity
+ public int postProcess(@NonNull Canvas canvas, int width, int height);
+}
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index eca52cc..7c7417d 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -35,6 +35,7 @@
boolean generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec);
boolean attestKey(in String alias, in byte[] challenge, out KeymasterCertificateChain chain);
+ boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain);
// APIs used by CertInstaller and DevicePolicyManager
String installCaCertificate(in byte[] caCertificate);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 3fe75cf..5b95c81 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6880,6 +6880,9 @@
return UNKNOWN_ERROR;
}
+ // The number of resources overlaid that were not explicitly marked overlayable.
+ size_t forcedOverlayCount = 0u;
+
KeyedVector<uint8_t, IdmapTypeMap> map;
// overlaid packages are assumed to contain only one package group
@@ -6919,6 +6922,7 @@
continue;
}
+ uint32_t typeSpecFlags = 0u;
const String16 overlayType(resName.type, resName.typeLen);
const String16 overlayName(resName.name, resName.nameLen);
uint32_t overlayResID = overlay.identifierForName(overlayName.string(),
@@ -6926,14 +6930,23 @@
overlayType.string(),
overlayType.size(),
overlayPackage.string(),
- overlayPackage.size());
+ overlayPackage.size(),
+ &typeSpecFlags);
if (overlayResID == 0) {
+ // No such target resource was found.
if (typeMap.entryMap.isEmpty()) {
typeMap.entryOffset++;
}
continue;
}
+ // Now that we know this is being overlaid, check if it can be, and emit a warning if
+ // it can't.
+ if ((dtohl(typeConfigs->typeSpecFlags[entryIndex]) &
+ ResTable_typeSpec::SPEC_OVERLAYABLE) == 0) {
+ forcedOverlayCount++;
+ }
+
if (typeMap.overlayTypeId == -1) {
typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1;
}
@@ -7012,6 +7025,10 @@
typeData += entryCount * 2;
}
+ if (forcedOverlayCount > 0) {
+ ALOGW("idmap: overlaid %zu resources not marked overlayable", forcedOverlayCount);
+ }
+
return NO_ERROR;
}
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 20d0178..8cf4de9 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1339,9 +1339,13 @@
// Number of uint32_t entry configuration masks that follow.
uint32_t entryCount;
- enum {
+ enum : uint32_t {
// Additional flag indicating an entry is public.
- SPEC_PUBLIC = 0x40000000
+ SPEC_PUBLIC = 0x40000000u,
+
+ // Additional flag indicating an entry is overlayable at runtime.
+ // Added in Android-P.
+ SPEC_OVERLAYABLE = 0x80000000u,
};
};
diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk
index 0c17328..18ef75e 100644
--- a/libs/androidfw/tests/data/basic/basic.apk
+++ b/libs/androidfw/tests/data/basic/basic.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_de_fr.apk b/libs/androidfw/tests/data/basic/basic_de_fr.apk
index e45258c..767dff6 100644
--- a/libs/androidfw/tests/data/basic/basic_de_fr.apk
+++ b/libs/androidfw/tests/data/basic/basic_de_fr.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
index 4ae1a7c..58953f5 100644
--- a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
index a240d4c..103f656 100644
--- a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
index fd3d9b2..61369d5 100644
--- a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build
index d619800..5682ed4 100755
--- a/libs/androidfw/tests/data/basic/build
+++ b/libs/androidfw/tests/data/basic/build
@@ -19,11 +19,15 @@
PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar
-aapt package \
- -M AndroidManifest.xml \
- -S res \
- -A assets \
+aapt2 compile --dir res -o compiled.flata
+aapt2 link \
-I $PATH_TO_FRAMEWORK_RES \
- --split hdpi --split xhdpi --split xxhdpi --split fr,de \
- -F basic.apk \
- -f
+ --manifest AndroidManifest.xml \
+ -A assets \
+ --split basic_hdpi-v4.apk:hdpi \
+ --split basic_xhdpi-v4.apk:xhdpi \
+ --split basic_xxhdpi-v4.apk:xxhdpi \
+ --split basic_de_fr.apk:de,fr \
+ -o basic.apk \
+ compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml
index 638c983..6c47459 100644
--- a/libs/androidfw/tests/data/basic/res/values/values.xml
+++ b/libs/androidfw/tests/data/basic/res/values/values.xml
@@ -60,4 +60,9 @@
<item>2</item>
<item>3</item>
</integer-array>
+
+ <overlayable>
+ <item type="string" name="test2" />
+ <item type="array" name="integerArray1" />
+ </overlayable>
</resources>
diff --git a/libs/androidfw/tests/data/overlay/build b/libs/androidfw/tests/data/overlay/build
index 112f373..716b1bd 100755
--- a/libs/androidfw/tests/data/overlay/build
+++ b/libs/androidfw/tests/data/overlay/build
@@ -17,4 +17,6 @@
set -e
-aapt package -M AndroidManifest.xml -S res -F overlay.apk -f
+aapt2 compile --dir res -o compiled.flata
+aapt2 link --manifest AndroidManifest.xml -o overlay.apk compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk
index 40bf17c..33f9611 100644
--- a/libs/androidfw/tests/data/overlay/overlay.apk
+++ b/libs/androidfw/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 8c0ca5c..0a9a74e 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -21,7 +21,6 @@
#include "hwui/Typeface.h"
#include "protos/hwui.pb.h"
-#include <../src/sysinfo.h>
#include <benchmark/benchmark.h>
#include <getopt.h>
#include <pthread.h>
@@ -345,9 +344,6 @@
name_field_width += 5;
benchmark::BenchmarkReporter::Context context;
- context.num_cpus = benchmark::NumCPUs();
- context.mhz_per_cpu = benchmark::CyclesPerSecond() / 1000000.0f;
- context.cpu_scaling_enabled = benchmark::CpuScalingEnabled();
context.name_field_width = name_field_width;
gBenchmarkReporter->ReportContext(context);
}
diff --git a/libs/incident/Android.mk b/libs/incident/Android.mk
index 8aa4b10..5f3e407 100644
--- a/libs/incident/Android.mk
+++ b/libs/incident/Android.mk
@@ -31,7 +31,6 @@
LOCAL_SRC_FILES := \
../../core/java/android/os/IIncidentManager.aidl \
- ../../core/java/android/os/IIncidentReportCompletedListener.aidl \
../../core/java/android/os/IIncidentReportStatusListener.aidl \
src/IncidentReportArgs.cpp
diff --git a/packages/InputDevices/res/values-bn/strings.xml b/packages/InputDevices/res/values-bn/strings.xml
index 5f8877a..a61e6ce 100644
--- a/packages/InputDevices/res/values-bn/strings.xml
+++ b/packages/InputDevices/res/values-bn/strings.xml
@@ -42,6 +42,5 @@
<string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"স্প্যানিশ (ল্যাটিন)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"লাটভিও"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"ফার্সী"</string>
- <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
- <skip />
+ <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"আজারবাইজানি"</string>
</resources>
diff --git a/packages/InputDevices/res/values-gu/strings.xml b/packages/InputDevices/res/values-gu/strings.xml
index d11df01..915f1b6 100644
--- a/packages/InputDevices/res/values-gu/strings.xml
+++ b/packages/InputDevices/res/values-gu/strings.xml
@@ -42,6 +42,5 @@
<string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"સ્પેનિશ (લેટિન)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"લાતવિયન"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"પર્શિયન"</string>
- <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
- <skip />
+ <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"અઝરબૈજાની"</string>
</resources>
diff --git a/packages/InputDevices/res/values-kn/strings.xml b/packages/InputDevices/res/values-kn/strings.xml
index 2e6f892..8f2b51a 100644
--- a/packages/InputDevices/res/values-kn/strings.xml
+++ b/packages/InputDevices/res/values-kn/strings.xml
@@ -42,6 +42,5 @@
<string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ಸ್ಪ್ಯಾನಿಶ್ (ಲ್ಯಾಟಿನ್)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"ಲ್ಯಾಟ್ವಿಯನ್"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"ಪರ್ಶಿಯನ್"</string>
- <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
- <skip />
+ <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"ಅಜೆರ್ಬೈಜಾನಿ"</string>
</resources>
diff --git a/packages/InputDevices/res/values-ml/strings.xml b/packages/InputDevices/res/values-ml/strings.xml
index dfd9754..d346d9f 100644
--- a/packages/InputDevices/res/values-ml/strings.xml
+++ b/packages/InputDevices/res/values-ml/strings.xml
@@ -42,6 +42,5 @@
<string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"സ്പാനിഷ് (ലാറ്റിൻ)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"ലാറ്റ്വിയന്"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"പേര്ഷ്യന്"</string>
- <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
- <skip />
+ <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"അസര്ബൈജാനി"</string>
</resources>
diff --git a/packages/InputDevices/res/values-pa/strings.xml b/packages/InputDevices/res/values-pa/strings.xml
index 1cf6a2e..f707730 100644
--- a/packages/InputDevices/res/values-pa/strings.xml
+++ b/packages/InputDevices/res/values-pa/strings.xml
@@ -42,6 +42,5 @@
<string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ਸਪੇਨੀ (ਲਾਤੀਨੀ)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"ਲਾਤਵੀਅਨ"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"ਫ਼ਾਰਸੀ"</string>
- <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
- <skip />
+ <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"ਅਜ਼ੇਰਬੈਜਾਨੀ"</string>
</resources>
diff --git a/packages/InputDevices/res/values-ta/strings.xml b/packages/InputDevices/res/values-ta/strings.xml
index ebee5c1..a4d07ac 100644
--- a/packages/InputDevices/res/values-ta/strings.xml
+++ b/packages/InputDevices/res/values-ta/strings.xml
@@ -42,6 +42,5 @@
<string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ஸ்பானிஷ் (லத்தீன்)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"லத்வியன்"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"பெர்சியன்"</string>
- <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
- <skip />
+ <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"அஜர்பைஜானி"</string>
</resources>
diff --git a/packages/InputDevices/res/values-te/strings.xml b/packages/InputDevices/res/values-te/strings.xml
index 141bb95..f7cce96 100644
--- a/packages/InputDevices/res/values-te/strings.xml
+++ b/packages/InputDevices/res/values-te/strings.xml
@@ -42,6 +42,5 @@
<string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"స్పానిష్ (లాటిన్)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"లాత్వియన్"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"పర్షియన్"</string>
- <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
- <skip />
+ <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"అజర్బైజాన్"</string>
</resources>
diff --git a/packages/InputDevices/res/values-ur/strings.xml b/packages/InputDevices/res/values-ur/strings.xml
index 71ce1cc..ab95bd5 100644
--- a/packages/InputDevices/res/values-ur/strings.xml
+++ b/packages/InputDevices/res/values-ur/strings.xml
@@ -42,6 +42,5 @@
<string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ہسپانوی (لاطینی)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"لاتویائی"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"فارسی"</string>
- <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) -->
- <skip />
+ <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"آزربائیجانی"</string>
</resources>
diff --git a/packages/InputDevices/res/values-uz/strings.xml b/packages/InputDevices/res/values-uz/strings.xml
index e820469..d6f7b2b 100644
--- a/packages/InputDevices/res/values-uz/strings.xml
+++ b/packages/InputDevices/res/values-uz/strings.xml
@@ -42,5 +42,5 @@
<string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Ispan (lotin)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"Latish"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"Fors"</string>
- <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"ozarbayjon"</string>
+ <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"Ozarbayjon"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 57d1657..22e9d35 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -353,7 +353,7 @@
<string name="daltonizer_mode_deuteranomaly" msgid="5475532989673586329">"Daltonismoa (gorri-berdeak)"</string>
<string name="daltonizer_mode_protanomaly" msgid="8424148009038666065">"Protanopia (gorri-berdeak)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="481725854987912389">"Tritanopia (urdin-horia)"</string>
- <string name="accessibility_display_daltonizer_preference_title" msgid="5800761362678707872">"Kolore-zuzenketa"</string>
+ <string name="accessibility_display_daltonizer_preference_title" msgid="5800761362678707872">"Kolorearen zuzenketa"</string>
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Eginbidea esperimentala da eta eragina izan dezake funtzionamenduan."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"<xliff:g id="TIME">^1</xliff:g> inguru gelditzen dira"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index f5cf5f6..47d8408 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -401,7 +401,7 @@
<string name="active_input_method_subtypes" msgid="3596398805424733238">"Métodos de entrada activos"</string>
<string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"Usar idiomas do sistema"</string>
<string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Non se puido abrir a configuración de <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
- <string name="ime_security_warning" msgid="4135828934735934248">"É posible que este método de entrada poida recompilar todo o texto que escribas, incluídos os datos persoais como os contrasinais e os números de tarxetas de crédito. Provén da aplicación <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Queres usar este método de entrada?"</string>
+ <string name="ime_security_warning" msgid="4135828934735934248">"Este método de introdución de texto pode recompilar todo o que escribas, incluídos os datos persoais como os contrasinais e os números de tarxetas de crédito. Provén da aplicación <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Queres usar este método de introdución de texto?"</string>
<string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Nota: Tras un reinicio, non se pode iniciar esta aplicación ata que desbloquees o teléfono"</string>
<string name="ims_reg_title" msgid="7609782759207241443">"Estado de rexistro de IMS"</string>
<string name="ims_reg_status_registered" msgid="933003316932739188">"Rexistrado"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index a8262c8..974b2a4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -271,7 +271,7 @@
* @param now The current time, used to tell whether daylight savings is active.
* @return A CharSequence suitable for display as the offset label of {@code tz}.
*/
- private static CharSequence getGmtOffsetText(TimeZoneFormat tzFormatter, Locale locale,
+ public static CharSequence getGmtOffsetText(TimeZoneFormat tzFormatter, Locale locale,
TimeZone tz, Date now) {
final SpannableStringBuilder builder = new SpannableStringBuilder();
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index 5df3989..791f5e9 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -135,6 +135,12 @@
<item quantity="other">ডিভাইসটি <xliff:g id="NUMBER_1">%d</xliff:g> ঘন্টা ধরে আনলক করা হয় নি। পাসওয়ার্ড নিশ্চিত করুন।</item>
</plurals>
<string name="fingerprint_not_recognized" msgid="348813995267914625">"স্বীকৃত নয়"</string>
- <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
- <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+ <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+ <item quantity="one">সিমের পিন লিখুন। আপনি আর <xliff:g id="NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন।</item>
+ <item quantity="other">সিমের পিন লিখুন। আপনি আর <xliff:g id="NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন।</item>
+ </plurals>
+ <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+ <item quantity="one">সিম অক্ষম করা হয়েছে। চালিয়ে যেতে PUK কোড লিখুন। আপনি আর <xliff:g id="_NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন, তারপরে এই সিমটি আর একেবারেই ব্যবহার করা যাবে না। বিশদে জানতে পরিষেবা প্রদানকারীর সাথে যোগাযোগ করুন।</item>
+ <item quantity="other">সিম অক্ষম করা হয়েছে। চালিয়ে যেতে PUK কোড লিখুন। আপনি আর <xliff:g id="_NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন, তারপরে এই সিমটি আর একেবারেই ব্যবহার করা যাবে না। বিশদে জানতে পরিষেবা প্রদানকারীর সাথে যোগাযোগ করুন।</item>
+ </plurals>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index a6ee9a6..c62bab8 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -135,6 +135,12 @@
<item quantity="other">ઉપકરણને <xliff:g id="NUMBER_1">%d</xliff:g> કલાક માટે અનલૉક કરવામાં આવ્યું નથી. પાસવર્ડની પુષ્ટિ કરો.</item>
</plurals>
<string name="fingerprint_not_recognized" msgid="348813995267914625">"ઓળખાયેલ નથી"</string>
- <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
- <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+ <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+ <item quantity="one">સિમ પિન દાખલ કરો, તમારી પાસે <xliff:g id="NUMBER_1">%d</xliff:g> પ્રયાસ બાકી છે.</item>
+ <item quantity="other">સિમ પિન દાખલ કરો, તમારી પાસે <xliff:g id="NUMBER_1">%d</xliff:g> પ્રયાસો બાકી છે.</item>
+ </plurals>
+ <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+ <item quantity="one">સિમ હવે બંધ કરેલ છે. ચાલુ રાખવા માટે PUK કોડ દાખલ કરો. સિમ કાયમીરૂપે બિનઉપયોગી બની જાય એ પહેલાં તમારી પાસે <xliff:g id="_NUMBER_1">%d</xliff:g> પ્રયાસ બાકી છે. વિગતો માટે કૅરિઅરનો સંપર્ક કરો.</item>
+ <item quantity="other">સિમ હવે બંધ કરેલ છે. ચાલુ રાખવા માટે PUK કોડ દાખલ કરો. સિમ કાયમીરૂપે બિનઉપયોગી બની જાય એ પહેલાં તમારી પાસે <xliff:g id="_NUMBER_1">%d</xliff:g> પ્રયાસો બાકી છે. વિગતો માટે કૅરિઅરનો સંપર્ક કરો.</item>
+ </plurals>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index 2ee30e9..2c29112 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -135,6 +135,12 @@
<item quantity="other">ಸಾಧನವನ್ನು <xliff:g id="NUMBER_1">%d</xliff:g> ಗಂಟೆಗಳವರೆಗೆ ಅನ್ಲಾಕ್ ಮಾಡಿರಲಿಲ್ಲ. ಪಾಸ್ವರ್ಡ್ ಖಚಿತಪಡಿಸಿ.</item>
</plurals>
<string name="fingerprint_not_recognized" msgid="348813995267914625">"ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
- <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
- <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+ <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+ <item quantity="one">ಸಿಮ್ ಪಿನ್ ನಮೂದಿಸಿ, ನಿಮ್ಮಲ್ಲಿ <xliff:g id="NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ.</item>
+ <item quantity="other">ಸಿಮ್ ಪಿನ್ ನಮೂದಿಸಿ, ನಿಮ್ಮಲ್ಲಿ <xliff:g id="NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ.</item>
+ </plurals>
+ <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+ <item quantity="one">ಸಿಮ್ ಅನ್ನು ಈಗ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ಮುಂದುವರಿಸಲು PUK ಕೋಡ್ ನಮೂದಿಸಿ. ಸಿಮ್ ಶಾಶ್ವತವಾಗಿ ನಿಷ್ಪ್ರಯೋಜಕವಾಗುವ ಮುನ್ನ ನಿಮ್ಮಲ್ಲಿ <xliff:g id="_NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ. ವಿವರಗಳಿಗಾಗಿ ವಾಹಕವನ್ನು ಸಂಪರ್ಕಿಸಿ.</item>
+ <item quantity="other">ಸಿಮ್ ಅನ್ನು ಈಗ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ಮುಂದುವರಿಸಲು PUK ಕೋಡ್ ನಮೂದಿಸಿ. ಸಿಮ್ ಶಾಶ್ವತವಾಗಿ ನಿಷ್ಪ್ರಯೋಜಕವಾಗುವ ಮುನ್ನ ನಿಮ್ಮಲ್ಲಿ <xliff:g id="_NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ. ವಿವರಗಳಿಗಾಗಿ ವಾಹಕವನ್ನು ಸಂಪರ್ಕಿಸಿ.</item>
+ </plurals>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 054ce7c..a4f5b7d 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -141,6 +141,6 @@
</plurals>
<plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
<item quantity="other">SIM-карта азыр жарактан чыкты. Улантуу үчүн PUK-кодду киргизиңиз. SIM-картанын биротоло жарактан чыгарына <xliff:g id="_NUMBER_1">%d</xliff:g> аракет калды. Чоо-жайын билүү үчүн байланыш операторуна кайрылыңыз.</item>
- <item quantity="one">SIM-карта азыр жарактан чыкты. Улантуу үчүн PUK-кодду киргизиңиз. SIM-картанын биротоло жарактан чыгарына <xliff:g id="_NUMBER_0">%d</xliff:g> аракет калды. Чоо-жайын билүү үчүн байланыш операторуна кайрылыңыз.</item>
+ <item quantity="one">SIM-карта азыр жарактан чыкты. Улантуу үчүн PUK-кодду киргизиңиз. SIM-картанын биротоло жарактан чыгаарына <xliff:g id="_NUMBER_0">%d</xliff:g> аракет калды. Чоо-жайын билүү үчүн байланыш операторуна кайрылыңыз.</item>
</plurals>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index 7f0e957..d62537d 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -135,6 +135,12 @@
<item quantity="one">ഉപകരണം <xliff:g id="NUMBER_0">%d</xliff:g> മണിക്കൂറായി അൺലോക്ക് ചെയ്തിട്ടില്ല. പാസ്വേഡ് സ്ഥിരീകരിക്കുക.</item>
</plurals>
<string name="fingerprint_not_recognized" msgid="348813995267914625">"തിരിച്ചറിഞ്ഞില്ല"</string>
- <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
- <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+ <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+ <item quantity="other">സിം പിൻ നൽകുക, <xliff:g id="NUMBER_1">%d</xliff:g> ശ്രമങ്ങൾ കൂടി ശേഷിക്കുന്നു.</item>
+ <item quantity="one">സിം പിൻ നൽകുക, ഉപകരണം അൺലോക്ക് ചെയ്യാൻ കാരിയറുമായി ബന്ധപ്പെടേണ്ടിവരുന്നതിന് മുമ്പ് <xliff:g id="NUMBER_0">%d</xliff:g> ശ്രമം കൂടി ശേഷിക്കുന്നു.</item>
+ </plurals>
+ <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+ <item quantity="other">സിം ഇപ്പോൾ പ്രവർത്തനരഹിതമാക്കി. തുടരുന്നതിന് PUK കോഡ് നൽകുക. സിം ശാശ്വതമായി ഉപയോഗശൂന്യമാകുന്നതിന് മുമ്പായി <xliff:g id="_NUMBER_1">%d</xliff:g> ശ്രമങ്ങൾ കൂടി ശേഷിക്കുന്നു. വിശദാംശങ്ങൾക്ക് കാരിയറുമായി ബന്ധപ്പെടുക.</item>
+ <item quantity="one">സിം ഇപ്പോൾ പ്രവർത്തനരഹിതമാക്കി. തുടരുന്നതിന് PUK കോഡ് നൽകുക. സിം ശാശ്വതമായി ഉപയോഗശൂന്യമാകുന്നതിന് മുമ്പായി <xliff:g id="_NUMBER_0">%d</xliff:g> ശ്രമം കൂടി ശേഷിക്കുന്നു. വിശദാംശങ്ങൾക്ക് കാരിയറുമായി ബന്ധപ്പെടുക.</item>
+ </plurals>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index dc0e53ba..d5d27ca 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -135,6 +135,12 @@
<item quantity="other">ਡੀਵਾਈਸ <xliff:g id="NUMBER_1">%d</xliff:g> ਘੰਟਿਆਂ ਤੋਂ ਅਣਲਾਕ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ। ਪਾਸਵਰਡ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ</item>
</plurals>
<string name="fingerprint_not_recognized" msgid="348813995267914625">"ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
- <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
- <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+ <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+ <item quantity="one">ਸਿਮ ਪਿੰਨ ਦਾਖਲ ਕਰੋ, ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ ਬਾਕੀ ਹੈ।</item>
+ <item quantity="other">ਸਿਮ ਪਿੰਨ ਦਾਖਲ ਕਰੋ, ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ਾਂ ਬਾਕੀ ਹਨ।</item>
+ </plurals>
+ <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+ <item quantity="one">ਸਿਮ ਹੁਣ ਬੰਦ ਹੋ ਗਿਆ ਹੈ। ਜਾਰੀ ਰੱਖਣ ਲਈ PUK ਕੋਡ ਦਾਖਲ ਕਰੋ। ਸਿਮ ਦੇ ਪੱਕੇ ਤੌਰ \'ਤੇ ਬੇਕਾਰ ਹੋ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="_NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ ਬਾਕੀ ਹੈ। ਵੇਰਵਿਆਂ ਲਈ ਕੈਰੀਅਰ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।</item>
+ <item quantity="other">ਸਿਮ ਹੁਣ ਬੰਦ ਹੋ ਗਿਆ ਹੈ। ਜਾਰੀ ਰੱਖਣ ਲਈ PUK ਕੋਡ ਦਾਖਲ ਕਰੋ। ਸਿਮ ਦੇ ਪੱਕੇ ਤੌਰ \'ਤੇ ਬੇਕਾਰ ਹੋ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="_NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ਾਂ ਬਾਕੀ ਹਨ। ਵੇਰਵਿਆਂ ਲਈ ਕੈਰੀਅਰ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।</item>
+ </plurals>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 727ea5b..2ce57a0 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -135,6 +135,12 @@
<item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> மணிநேரமாகச் சாதனம் திறக்கப்படவில்லை. கடவுச்சொல்லை உறுதிப்படுத்தவும்.</item>
</plurals>
<string name="fingerprint_not_recognized" msgid="348813995267914625">"அடையாளங்காண முடியவில்லை"</string>
- <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
- <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+ <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+ <item quantity="other">சிம் பின்னை உள்ளிடவும், மேலும் <xliff:g id="NUMBER_1">%d</xliff:g> முறை முயற்சிக்கலாம்.</item>
+ <item quantity="one">சிம் பின்னை உள்ளிடவும், நீங்கள் <xliff:g id="NUMBER_0">%d</xliff:g> முறை மட்டுமே முயற்சிக்க முடியுமென்பதால், அதற்கு முன்பு மொபைல் நிறுவனத்தைத் தொடர்பு கொண்டு சாதனத்தைத் திறக்க முயலவும்.</item>
+ </plurals>
+ <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+ <item quantity="other">சிம் தற்போது முடக்கப்பட்டுள்ளது. தொடர்வதற்கு, PUK குறியீட்டை உள்ளிடவும். நீங்கள் <xliff:g id="_NUMBER_1">%d</xliff:g> முறை மட்டுமே முயற்சிக்க முடியும். அதன்பிறகு சிம் நிரந்தரமாக முடக்கப்படும். விவரங்களுக்கு, மொபைல் நிறுவனத்தைத் தொடர்புகொள்ளவும்.</item>
+ <item quantity="one">சிம் தற்போது முடக்கப்பட்டுள்ளது. தொடர்வதற்கு, PUK குறியீட்டை உள்ளிடவும். நீங்கள் <xliff:g id="_NUMBER_0">%d</xliff:g> முறை மட்டுமே முயற்சிக்க முடியும். அதன்பிறகு சிம் நிரந்தரமாக முடக்கப்படும். விவரங்களுக்கு, மொபைல் நிறுவனத்தைத் தொடர்புகொள்ளவும்.</item>
+ </plurals>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index ddc5928..bdee4a3 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -135,6 +135,12 @@
<item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> గంట పాటు పరికరాన్ని అన్లాక్ చేయలేదు. పాస్వర్డ్ని నమోదు చేయండి.</item>
</plurals>
<string name="fingerprint_not_recognized" msgid="348813995267914625">"గుర్తించలేదు"</string>
- <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
- <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+ <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+ <item quantity="other">SIM పిన్ని నమోదు చేయండి, మీకు <xliff:g id="NUMBER_1">%d</xliff:g> ప్రయత్నలు మిగిలి ఉన్నాయి.</item>
+ <item quantity="one">SIM పిన్ని నమోదు చేయండి, మీరు మీ పరికరాన్ని అన్లాక్ చేయడానికి తప్పనిసరిగా మీ క్యారియర్ను సంప్రదించడానికి ముందు మీకు <xliff:g id="NUMBER_0">%d</xliff:g> ప్రయత్నం మిగిలి ఉంది.</item>
+ </plurals>
+ <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+ <item quantity="other">SIM ఇప్పుడు నిలిపివేయబడింది. PUK కోడ్ను నమోదు చేయండి. SIM శాశ్వతంగా నిరుపయోగం కాకుండా ఉండటానికి మీకు <xliff:g id="_NUMBER_1">%d</xliff:g> ప్రయత్నాలు మిగిలి ఉన్నాయి. వివరాల కోసం కారియర్ను సంప్రదించండి.</item>
+ <item quantity="one">SIM ఇప్పుడు నిలిపివేయబడింది. PUK కోడ్ను నమోదు చేయండి. SIM శాశ్వతంగా నిరుపయోగం కాకుండా ఉండటానికి మీకు <xliff:g id="_NUMBER_0">%d</xliff:g> ప్రయత్నం మిగిలి ఉంది వివరాల కోసం కారియర్ను సంప్రదించండి.</item>
+ </plurals>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index 5440a87..cd99c92 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -135,6 +135,12 @@
<item quantity="one">آلہ <xliff:g id="NUMBER_0">%d</xliff:g> گھنٹہ سے غیر مقفل نہیں کیا گیا۔ پاسورڈ کی توثیق کریں۔</item>
</plurals>
<string name="fingerprint_not_recognized" msgid="348813995267914625">"تسلیم شدہ نہیں ہے"</string>
- <!-- no translation found for kg_password_default_pin_message (6203676909479972943) -->
- <!-- no translation found for kg_password_default_puk_message (8744416410184198352) -->
+ <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943">
+ <item quantity="other">SIM کا PIN درج کریں، آپ کے پاس <xliff:g id="NUMBER_1">%d</xliff:g> کوششیں بچی ہیں۔</item>
+ <item quantity="one">SIM کا PIN درج کریں، آپ کے پاس <xliff:g id="NUMBER_0">%d</xliff:g> کوشش بچی ہے، اس کے بعد آپ کو اپنا آلہ غیر مقفل کرنے کیلئے اپنے کیریئر سے رابطہ کرنا ہوگا۔</item>
+ </plurals>
+ <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
+ <item quantity="other">SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس <xliff:g id="_NUMBER_1">%d</xliff:g> کوششیں بچی ہیں۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔</item>
+ <item quantity="one">SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس <xliff:g id="_NUMBER_0">%d</xliff:g> کوشش بچی ہے۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔</item>
+ </plurals>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index ee11cff..204bb8b 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -140,7 +140,7 @@
<item quantity="one">SIM PIN kodini kiriting, qurilmani qulfdan chiqarish uchun sizda <xliff:g id="NUMBER_0">%d</xliff:g> ta urinish bor.</item>
</plurals>
<plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352">
- <item quantity="other">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_1">%d</xliff:g> marta urinib ko‘rganingizdan keyin SIM kartadan umuman foydalanib bo‘lmaydi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item>
- <item quantity="one">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_0">%d</xliff:g> marta urinib ko‘rganingizdan keyin SIM kartadan umuman foydalanib bo‘lmaydi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item>
+ <item quantity="other">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_1">%d</xliff:g> marta xato qilsangiz, SIM kartangiz butunlay qulflanadi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item>
+ <item quantity="one">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_0">%d</xliff:g> marta xato qilsangiz, SIM kartangiz butunlay qulflanadi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item>
</plurals>
</resources>
diff --git a/packages/SystemUI/res/drawable/ic_cast.xml b/packages/SystemUI/res/drawable/ic_cast.xml
new file mode 100644
index 0000000..b86dfea
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_cast.xml
@@ -0,0 +1,31 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M1 18v2c0 .55 .45 1 1 1h2c0-1.66-1.34-3-3-3zm0-2.94c-.01 .51 .32 .93 .82 1.02
+2.08 .36 3.74 2 4.1 4.08 .09 .48 .5 .84 .99 .84 .61 0 1.09-.54 1-1.14a6.996
+6.996 0 0 0-5.8-5.78c-.59-.09-1.09 .38 -1.11 .98 zm0-4.03c-.01 .52 .34 .96 .85
+1.01 4.26 .43 7.68 3.82 8.1 8.08 .05 .5 .48 .88 .99 .88 .59 0 1.06-.51
+1-1.1-.52-5.21-4.66-9.34-9.87-9.85-.57-.05-1.05 .4 -1.07 .98 zM21 3H3c-1.1 0-2
+.9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker.xml b/packages/SystemUI/res/drawable/ic_speaker.xml
new file mode 100644
index 0000000..1ea293c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal" >
+ <path
+ android:pathData="M17,2L7,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,1.99 2,1.99L17,22c1.1,0 2,-0.9 2,-2L19,4c0,-1.1 -0.9,-2 -2,-2zM12,4c1.1,0 2,0.9 2,2s-0.9,2 -2,2c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2zM12,20c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,12c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"
+ android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker_group.xml b/packages/SystemUI/res/drawable/ic_speaker_group.xml
new file mode 100644
index 0000000..d6867d7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_group.xml
@@ -0,0 +1,32 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal" >
+ <path
+ android:pathData="M18.2,1L9.8,1C8.81,1 8,1.81 8,2.8v14.4c0,0.99 0.81,1.79 1.8,1.79l8.4,0.01c0.99,0 1.8,-0.81 1.8,-1.8L20,2.8c0,-0.99 -0.81,-1.8 -1.8,-1.8zM14,3c1.1,0 2,0.89 2,2s-0.9,2 -2,2 -2,-0.89 -2,-2 0.9,-2 2,-2zM14,16.5c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"
+ android:fillColor="#FFFFFFFF"/>
+ <path
+ android:pathData="M14,12.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"
+ android:fillColor="#FFFFFFFF"/>
+ <path
+ android:pathData="M6,5H4v16c0,1.1 0.89,2 2,2h10v-2H6V5z"
+ android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_tv.xml b/packages/SystemUI/res/drawable/ic_tv.xml
new file mode 100644
index 0000000..cc2ae91
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_tv.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal" >
+ <path
+ android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12z"
+ android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/output_chooser.xml b/packages/SystemUI/res/layout/output_chooser.xml
index 22c3bcf..3d0ab35 100644
--- a/packages/SystemUI/res/layout/output_chooser.xml
+++ b/packages/SystemUI/res/layout/output_chooser.xml
@@ -19,6 +19,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:id="@+id/output_chooser"
+ android:minWidth="320dp"
+ android:minHeight="320dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp" >
@@ -39,12 +41,6 @@
android:gravity="center"
android:orientation="vertical">
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="56dp"
- android:layout_height="56dp"
- android:tint="?android:attr/textColorSecondary" />
-
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index b9a228f..15a7d4c 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Dubbele multitoonfrekwensie"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Toeganklikheid"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Oproepe"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Lui"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibreer"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Demp"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 961ac45..ee9acae 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"ብሉቱዝ"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"ድርብ ባለ በርካታ ቅላጼ ድግምግሞሽ"</string>
<string name="stream_accessibility" msgid="301136219144385106">"ተደራሽነት"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"ጥሪዎች"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"ጥሪ"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ንዘር"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"ድምጸ-ከል አድርግ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 8437705..1d8cb4e 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -500,6 +500,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"بلوتوث"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"تردد ثنائي متعدد النغمات"</string>
<string name="stream_accessibility" msgid="301136219144385106">"إمكانية الوصول"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"المكالمات"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"استصدار رنين"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"اهتزاز"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"كتم الصوت"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index ae3854f..fd42eb5 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Višestruka frekvencija dualnog tona"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Pristupačnost"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Pozivi"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Aktiviraj zvono"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibriraj"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Isključi zvuk"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 3c9f83a..e82dbff 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -498,6 +498,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Двухтанальны шматчастотны"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Спецыяльныя магчымасці"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Выклікі"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Званок"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вібрацыя"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Гук выключаны"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 1cd4c91..ccdc44b 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Тонално набиране"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Достъпност"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Обаждания"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Позвъняване"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вибриране"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Без звук"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index c823525..96028f7 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -492,12 +492,11 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"ব্লুটুথ"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"ডুয়েল মাল্টি টোন ফ্রিকোয়েন্সি"</string>
<string name="stream_accessibility" msgid="301136219144385106">"অ্যাক্সেসযোগ্যতা"</string>
- <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
<skip />
- <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
- <skip />
- <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
- <skip />
+ <string name="volume_ringer_status_normal" msgid="4273142424125855384">"রিং"</string>
+ <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ভাইব্রেট"</string>
+ <string name="volume_ringer_status_silent" msgid="6896394161022916369">"মিউট"</string>
<string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s। সশব্দ করতে আলতো চাপুন।"</string>
<string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s। কম্পন এ সেট করতে আলতো চাপুন। অ্যাক্সেসযোগ্যতার পরিষেবাগুলিকে নিঃশব্দ করা হতে পারে।"</string>
<string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s। নিঃশব্দ করতে আলতো চাপুন। অ্যাক্সেসযোগ্যতার পরিষেবাগুলিকে নিঃশব্দ করা হতে পারে।"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index e87bb45..dacf9d7 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Marcatge per tons"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Accessibilitat"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Trucades"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Fes sonar"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibra"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Silencia"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index e0e14a1..4166697 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -498,6 +498,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Tónová volba"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Přístupnost"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Volání"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Vyzvánění"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrace"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ztlumení"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index fb886fc..52446f8 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Tonesignalfrekvens (DTMF)"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Hjælpefunktioner"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Opkald"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ring"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibration"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Slå lyden fra"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index feb7064..83a8e33 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -496,6 +496,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Doppelton-Mehrfrequenz"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Bedienungshilfen"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Anrufe"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Klingeln lassen"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrieren"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Stummschalten"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 1fe9010..9a61828 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Πολυσυχνότητα διπλού τόνου"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Προσβασιμότητα"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Κλήσεις"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Κουδούνισμα"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Δόνηση"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Σίγαση"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 4af3f59..d808119 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Multifrecuencia de tono doble"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Accesibilidad"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Llamadas"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Hacer sonar"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrar"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Silenciar"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 27e4920..3a5af25 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Kaks mitme tooniga sagedust"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Juurdepääsetavus"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Kõned"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Helisemine"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibreerimine"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Vaigistatud"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 5d4181e..87348de 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth konexioa"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Tonu anitzeko maiztasun duala"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Erabilerraztasuna"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Deiak"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Jo tonua"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Dardara"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ez jo tonua"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 9360c1d..bb77daa 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"بلوتوث"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"فرکانس دوتایی چند نوایی"</string>
<string name="stream_accessibility" msgid="301136219144385106">"دسترسپذیری"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"تماسها"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"زنگ زدن"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"لرزش"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"بیصدا"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 864f8d0..3bebe4b 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Äänitaajuusvalinta"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Esteettömyys"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Puhelut"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Soittoääni"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Värinä"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Äänetön"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 5c4394b..a2dde85 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Fréquence double multi ton"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Accessibilité"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Appels"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Sonnerie"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibration"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Sonnerie désactivée"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index a3d580b..9fbcdec 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -280,7 +280,7 @@
<string name="quick_settings_rotation_locked_label" msgid="6359205706154282377">"Rotación bloqueada"</string>
<string name="quick_settings_rotation_locked_portrait_label" msgid="5102691921442135053">"Vertical"</string>
<string name="quick_settings_rotation_locked_landscape_label" msgid="8553157770061178719">"Horizontal"</string>
- <string name="quick_settings_ime_label" msgid="7073463064369468429">"Método de entrada"</string>
+ <string name="quick_settings_ime_label" msgid="7073463064369468429">"Método de introdución de texto"</string>
<string name="quick_settings_location_label" msgid="5011327048748762257">"Localización"</string>
<string name="quick_settings_location_off_label" msgid="7464544086507331459">"Localización desactivada"</string>
<string name="quick_settings_media_device_label" msgid="1302906836372603762">"Dispositivo multimedia"</string>
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Multifrecuencia de dobre ton"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Accesibilidade"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Chamadas"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Facer soar"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrar"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Silenciar"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 83f7896..418f31e1 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -492,12 +492,11 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"બ્લૂટૂથ"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"દ્વિ બહુ ટોન આવર્તન"</string>
<string name="stream_accessibility" msgid="301136219144385106">"ઍક્સેસિબિલિટી"</string>
- <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
<skip />
- <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
- <skip />
- <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
- <skip />
+ <string name="volume_ringer_status_normal" msgid="4273142424125855384">"રિંગ કરો"</string>
+ <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"વાઇબ્રેટ"</string>
+ <string name="volume_ringer_status_silent" msgid="6896394161022916369">"મ્યૂટ કરો"</string>
<string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. અનમ્યૂટ કરવા માટે ટૅપ કરો."</string>
<string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. વાઇબ્રેટ પર સેટ કરવા માટે ટૅપ કરો. ઍક્સેસિબિલિટી સેવાઓ મ્યૂટ કરવામાં આવી શકે છે."</string>
<string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. મ્યૂટ કરવા માટે ટૅપ કરો. ઍક્સેસિબિલિટી સેવાઓ મ્યૂટ કરવામાં આવી શકે છે."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index e7fc696..1acd6e7 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -492,6 +492,8 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"ब्लूटूथ"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"दोहरी बहु टोन आवृत्ति"</string>
<string name="stream_accessibility" msgid="301136219144385106">"सुलभता"</string>
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
+ <skip />
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"आवाज़ चालू है"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"कंपन (वाइब्रेशन)"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"आवाज़ बंद है"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 88f5097..7ba9111 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"DTMF"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Pristupačnost"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Pozivi"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zvonjenje"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibriranje"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Zvuk je isključen"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index e81df8f..8aa2c71 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Kéthangú többfrekvenciás jelzésátvitel (DTMF)"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Kisegítő lehetőségek"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Hívások"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Csörgés"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Rezgés"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Néma"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 2994b9b..2841074 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Կրկնակի բազմերանգ հաճախականություն"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Մատչելիություն"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Զանգեր"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Սովորական"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Թրթռազանգ"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Անձայն"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 1efc249..66530bd 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Tvítóna fjöltíðni"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Aðgengi"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Símtöl"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Hringing"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Titringur"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Hljóð af"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 2927116..cbbffa9 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -496,6 +496,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"טון זוגי מרובה תדרים (DTMF)"</string>
<string name="stream_accessibility" msgid="301136219144385106">"נגישות"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"שיחות"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"צלצול"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"רטט"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"השתקה"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 38622c9..f840225 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"デュアルトーン マルチ周波数"</string>
<string name="stream_accessibility" msgid="301136219144385106">"ユーザー補助機能"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"通話"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"着信音"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"バイブレーション"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"ミュート"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 3f36c72..a9e6ffe 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Қос үнді көп жиілік"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Арнайы мүмкіндіктер"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Қоңыраулар"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Шылдырлау"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Діріл"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Дыбысын өшіру"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 1d5d7d4..5819f2a 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -492,9 +492,10 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"ប៊្លូធូស"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"ហ្វ្រេកង់ពហុសំឡេងទ្វេ"</string>
<string name="stream_accessibility" msgid="301136219144385106">"ភាពងាយស្រួល"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"ហៅទូរសព្ទ"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"រោទ៍"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ញ័រ"</string>
- <string name="volume_ringer_status_silent" msgid="6896394161022916369">"បិទ"</string>
+ <string name="volume_ringer_status_silent" msgid="6896394161022916369">"បិទសំឡេង"</string>
<string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s។ ប៉ះដើម្បីបើកសំឡេង។"</string>
<string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s។ ប៉ះដើម្បីកំណត់ឲ្យញ័រ។ សេវាកម្មលទ្ធភាពប្រើប្រាស់អាចនឹងត្រូវបានបិទសំឡេង។"</string>
<string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s។ ប៉ះដើម្បីបិទសំឡេង។ សេវាកម្មលទ្ធភាពប្រើប្រាស់អាចនឹងត្រូវបានបិទសំឡេង។"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index b2e9d8f..5e7e1e2 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -492,12 +492,11 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"ಬ್ಲೂಟೂತ್"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"ಡ್ಯುಯಲ್ ಬಹು ಟೋನ್ ಆವರ್ತನೆ"</string>
<string name="stream_accessibility" msgid="301136219144385106">"ಪ್ರವೇಶಿಸುವಿಕೆ"</string>
- <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
<skip />
- <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
- <skip />
- <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
- <skip />
+ <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ರಿಂಗ್"</string>
+ <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ವೈಬ್ರೇಟ್"</string>
+ <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ಮ್ಯೂಟ್"</string>
<string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. ಅನ್ಮ್ಯೂಟ್ ಮಾಡುವುದಕ್ಕಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. ಕಂಪನಕ್ಕೆ ಹೊಂದಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಪ್ರವೇಶಿಸುವಿಕೆ ಸೇವೆಗಳನ್ನು ಮ್ಯೂಟ್ ಮಾಡಬಹುದು."</string>
<string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. ಮ್ಯೂಟ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಪ್ರವೇಶಿಸುವಿಕೆ ಸೇವೆಗಳನ್ನು ಮ್ಯೂಟ್ ಮಾಡಬಹುದು."</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 83018be..d86c32f 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"블루투스"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"듀얼 멀티 톤 주파수"</string>
<string name="stream_accessibility" msgid="301136219144385106">"접근성"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"통화"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"벨소리"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"진동"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"음소거"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 4aaae3c..e10e2b0 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Көп тондуу жыштык"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Атайын мүмкүнчүлүктөр"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Чалуулар"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Шыңгыратуу"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Дирилдөө"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Үнсүз"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index bc1e5b9..2587a20 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"ບຣູທູດ"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Dual multi tone frequency"</string>
<string name="stream_accessibility" msgid="301136219144385106">"ການຊ່ວຍເຂົ້າເຖິງ"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"ການໂທ"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"ເຕືອນດ້ວຍສຽງ"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ສັ່ນເຕືອນ"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"ປິດ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 53ddcb3..1c97452 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -496,6 +496,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Dvigubas kelių tonų dažnis"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Pritaikymas neįgaliesiems"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Skambučiai"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Skambinti"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibruoti"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Nutildyti"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index b1c3db5..c14eb3f 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Divtoņu daudzfrekvenču signalizācija"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Pieejamība"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Zvani"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zvanīt"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrēt"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Izslēgt skaņu"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 3986e16..e30085c 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Двојна повеќетонска фреквенција"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Пристапност"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Повици"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ѕвони"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вибрации"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Исклучи звук"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 3dfde13..385331e 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -492,12 +492,11 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"ബ്ലൂടൂത്ത്"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"ഡ്യുവൽ മൾട്ടി റ്റോൺ ഫ്രീക്വൻസി"</string>
<string name="stream_accessibility" msgid="301136219144385106">"ഉപയോഗസഹായി"</string>
- <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
<skip />
- <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
- <skip />
- <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
- <skip />
+ <string name="volume_ringer_status_normal" msgid="4273142424125855384">"റിംഗ് ചെയ്യുക"</string>
+ <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"വൈബ്രേറ്റ് ചെയ്യുക"</string>
+ <string name="volume_ringer_status_silent" msgid="6896394161022916369">"മ്യൂട്ട് ചെയ്യുക"</string>
<string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. അൺമ്യൂട്ടുചെയ്യുന്നതിന് ടാപ്പുചെയ്യുക."</string>
<string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. വൈബ്രേറ്റിലേക്ക് സജ്ജമാക്കുന്നതിന് ടാപ്പുചെയ്യുക. ഉപയോഗസഹായി സേവനങ്ങൾ മ്യൂട്ടുചെയ്യപ്പെട്ടേക്കാം."</string>
<string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. മ്യൂട്ടുചെയ്യുന്നതിന് ടാപ്പുചെയ്യുക. ഉപയോഗസഹായി സേവനങ്ങൾ മ്യൂട്ടുചെയ്യപ്പെട്ടേക്കാം."</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 9f09dad..480956c 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -490,6 +490,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Олон дууны давтамж"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Хүртээмж"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Дуудлага"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Хонх дуугаргах"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Чичиргэх"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Хаах"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 5b0984d..8a0d661 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -492,6 +492,8 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"ब्लूटूथ"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"दुहेरी एकाधिक टोन वारंंवारता"</string>
<string name="stream_accessibility" msgid="301136219144385106">"प्रवेशयोग्यता"</string>
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
+ <skip />
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"रिंग करा"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"कंपन"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"म्युट करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 2907761..e074875 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Multifrekuensi dwinada"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Kebolehaksesan"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Panggilan"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Dering"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Getar"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Redam"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 51b3b0d..661df03 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"DTMF (dual-tone multi-frequency)"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Tilgjengelighet"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Anrop"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ring"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrer"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ignorer"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 0a63ce2..dce9cc4 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -492,6 +492,8 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"ब्लुटुथ"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"दोहोरो बहु टोनको फ्रिक्वेन्सी"</string>
<string name="stream_accessibility" msgid="301136219144385106">"पहुँच"</string>
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
+ <skip />
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"घन्टी"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"कम्पन"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"म्युट गर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index c6475dc..43503d6 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -492,12 +492,11 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"ਬਲੂਟੁੱਥ"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"ਦੂਹਰੀ ਮਲਟੀ ਟੋਨ ਆਵਰਤੀ"</string>
<string name="stream_accessibility" msgid="301136219144385106">"ਪਹੁੰਚਯੋਗਤਾ"</string>
- <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
<skip />
- <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
- <skip />
- <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
- <skip />
+ <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ਘੰਟੀ"</string>
+ <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ਥਰਥਰਾਹਟ"</string>
+ <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ਮਿਊਟ"</string>
<string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s। ਅਣਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s। ਥਰਥਰਾਹਟ ਸੈੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ। ਪਹੁੰਚਯੋਗਤਾ ਸੇਵਾਵਾਂ ਮਿਊਟ ਹੋ ਸਕਦੀਆਂ ਹਨ।"</string>
<string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s। ਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ। ਪਹੁੰਚਯੋਗਤਾ ਸੇਵਾਵਾਂ ਮਿਊਟ ਹੋ ਸਕਦੀਆਂ ਹਨ।"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index bc2a954..e567e6ba 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -496,6 +496,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"DTMF"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Ułatwienia dostępu"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Połączenia"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Dzwonek"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Wibracje"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Wyciszenie"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 7f2c643..681d3e4 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Multifrequência de duas tonalidades"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Acessibilidade"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Chamadas"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Toque"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrar"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Desativar som"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 2108455..2a0744e 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -496,6 +496,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Frecvență tonuri multiple duale"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Accesibilitate"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Apeluri"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Sonerie"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrații"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Blocați"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 151c2b4..694695c 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -498,6 +498,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Dvojtónová multifrekvencia"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Dostupnosť"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Hovory"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Prezvoniť"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrovať"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Vypnúť zvuk"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index ee628e1..10d0104 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -498,6 +498,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Dvojna večtonska frekvenca"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Funkcije za ljudi s posebnimi potrebami"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Klici"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zvonjenje"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibriranje"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Utišano"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 7641e6c..665177f 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Frekuenca e dyfishtë me shumë tone"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Qasshmëria"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Telefonatat"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Bjeri ziles"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Dridhje"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Pa zë"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index b5fab0d..cef0f69 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Вишеструка фреквенција дуалног тона"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Приступачност"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Позиви"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Активирај звоно"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вибрирај"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Искључи звук"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index cc6f542..c8f8db4 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Tonval"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Tillgänglighet"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Samtal"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ringsignal"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibration"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Dölj"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index b83f10f..8fa370d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Masafa ya ishara ya kampuni ya simu"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Zana za walio na matatizo ya kuona au kusikia"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Simu"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Piga"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Tetema"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Zima sauti"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 9d532e2..6568381 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -492,12 +492,11 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"புளூடூத்"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"டூயல் டோன் மல்டி ஃப்ரீக்வென்சி"</string>
<string name="stream_accessibility" msgid="301136219144385106">"அணுகல்தன்மை"</string>
- <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
<skip />
- <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
- <skip />
- <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
- <skip />
+ <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ஒலி"</string>
+ <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"அதிர்வு"</string>
+ <string name="volume_ringer_status_silent" msgid="6896394161022916369">"அமைதி"</string>
<string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. ஒலி இயக்க, தட்டவும்."</string>
<string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. அதிர்விற்கு அமைக்க, தட்டவும். அணுகல்தன்மை சேவைகள் ஒலியடக்கப்படக்கூடும்."</string>
<string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. ஒலியடக்க, தட்டவும். அணுகல்தன்மை சேவைகள் ஒலியடக்கப்படக்கூடும்."</string>
@@ -762,7 +761,7 @@
<string name="instant_apps" msgid="6647570248119804907">"இன்ஸ்டண்ட் பயன்பாடுகள்"</string>
<string name="instant_apps_message" msgid="8116608994995104836">"இன்ஸ்டண்ட் பயன்பாடுகளுக்கு நிறுவல் தேவையில்லை."</string>
<string name="app_info" msgid="6856026610594615344">"ஆப்ஸ் தகவல்"</string>
- <string name="go_to_web" msgid="2650669128861626071">"உலாவிக்குக்குச் செல்"</string>
+ <string name="go_to_web" msgid="2650669128861626071">"உலாவிக்குச் செல்"</string>
<string name="mobile_data" msgid="7094582042819250762">"மொபைல் டேட்டா"</string>
<string name="wifi_is_off" msgid="1838559392210456893">"வைஃபை முடக்கத்தில் உள்ளது"</string>
<string name="bt_is_off" msgid="2640685272289706392">"புளூடூத் முடக்கத்தில் உள்ளது"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index f1c66f5..fa7cb09 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -492,12 +492,11 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"బ్లూటూత్"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"డ్యూయల్ మల్టీ టోన్ ఫ్రీక్వెన్సీ"</string>
<string name="stream_accessibility" msgid="301136219144385106">"యాక్సెస్ సామర్థ్యం"</string>
- <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
<skip />
- <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
- <skip />
- <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
- <skip />
+ <string name="volume_ringer_status_normal" msgid="4273142424125855384">"రింగ్"</string>
+ <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"వైబ్రేట్"</string>
+ <string name="volume_ringer_status_silent" msgid="6896394161022916369">"మ్యూట్"</string>
<string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. అన్మ్యూట్ చేయడానికి నొక్కండి."</string>
<string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. వైబ్రేషన్కు సెట్ చేయడానికి నొక్కండి. యాక్సెస్ సామర్థ్య సేవలు మ్యూట్ చేయబడవచ్చు."</string>
<string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. మ్యూట్ చేయడానికి నొక్కండి. యాక్సెస్ సామర్థ్య సేవలు మ్యూట్ చేయబడవచ్చు."</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index ea61018..d43efdd 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"บลูทูธ"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"การส่งสัญญาณเสียงแบบ 2 เสียงพร้อมกัน"</string>
<string name="stream_accessibility" msgid="301136219144385106">"การเข้าถึง"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"การโทร"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"ทำให้ส่งเสียง"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"สั่น"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"ปิดเสียง"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index cdf28d6..4aa9ca4 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Dual multi tone frequency"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Pagiging Naa-access"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Mga Tawag"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ipa-ring"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"I-vibrate"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"I-mute"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 5dc81a1..d028fdd 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Çift ton çoklu frekans"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Erişilebilirlik"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Çağrılar"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zili çaldır"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Titreşim"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Sesi kapat"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 6498088..73fd5886 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -492,12 +492,11 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"بلوٹوتھ"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"دوہری ملٹی ٹون فریکوئنسی"</string>
<string name="stream_accessibility" msgid="301136219144385106">"ایکسیسبیلٹی"</string>
- <!-- no translation found for volume_ringer_status_normal (4273142424125855384) -->
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
<skip />
- <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) -->
- <skip />
- <!-- no translation found for volume_ringer_status_silent (6896394161022916369) -->
- <skip />
+ <string name="volume_ringer_status_normal" msgid="4273142424125855384">"رِنگ کریں"</string>
+ <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"وائبریٹ"</string>
+ <string name="volume_ringer_status_silent" msgid="6896394161022916369">"خاموش کریں"</string>
<string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s۔ آواز چالو کرنے کیلئے تھپتھپائیں۔"</string>
<string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s۔ ارتعاش پر سیٹ کرنے کیلئے تھپتھپائیں۔ ایکسیسبیلٹی سروسز شاید خاموش ہوں۔"</string>
<string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s۔ خاموش کرنے کیلئے تھپتھپائیں۔ ایکسیسبیلٹی سروسز شاید خاموش ہوں۔"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 273079d..55eb4fb 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Ikkitali ko‘pchastotali ovoz"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Maxsus imkoniyatlar"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Chaqiruvlar"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Jiringlatish"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Tebranish"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ovozsiz"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index df06298..551decf 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"Tần số đa chuông kép"</string>
<string name="stream_accessibility" msgid="301136219144385106">"Trợ năng"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"Cuộc gọi"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"Đổ chuông"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Rung"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"Tắt tiếng"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index f7999a6..2b64d86 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -492,6 +492,8 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"蓝牙"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"双音多频"</string>
<string name="stream_accessibility" msgid="301136219144385106">"无障碍"</string>
+ <!-- no translation found for ring_toggle_title (3281244519428819576) -->
+ <skip />
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"响铃"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"振动"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"静音"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 188ad7c..382425d 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -494,6 +494,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"藍牙"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"雙音多頻訊號"</string>
<string name="stream_accessibility" msgid="301136219144385106">"無障礙功能"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"通話"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"鈴聲"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"震動"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"靜音"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 75f8015..ef9ea43 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -492,6 +492,7 @@
<string name="stream_bluetooth_sco" msgid="2055645746402746292">"藍牙"</string>
<string name="stream_dtmf" msgid="2447177903892477915">"雙音多頻"</string>
<string name="stream_accessibility" msgid="301136219144385106">"協助工具"</string>
+ <string name="ring_toggle_title" msgid="3281244519428819576">"通話"</string>
<string name="volume_ringer_status_normal" msgid="4273142424125855384">"鈴聲"</string>
<string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"震動"</string>
<string name="volume_ringer_status_silent" msgid="6896394161022916369">"靜音"</string>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 78e621e..fd205dd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1269,6 +1269,14 @@
<string name="volume_dialog_accessibility_shown_message">%s volume controls shown. Swipe up to dismiss.</string>
<string name="volume_dialog_accessibility_dismissed_message">Volume controls hidden</string>
+ <string name="output_title">Media output</string>
+ <string name="output_calls_title">Phone call output</string>
+ <string name="output_none_found">No devices found</string>
+ <string name="output_none_found_service_off">No devices found. Try turning on <xliff:g id="service" example="Bluetooth">%1$s</xliff:g></string>
+ <string name="output_service_bt">Bluetooth</string>
+ <string name="output_service_wifi">Wi-Fi</string>
+ <string name="output_service_bt_wifi">Bluetooth and Wi-Fi</string>
+
<!-- Name of special SystemUI debug settings -->
<string name="system_ui_tuner">System UI Tuner</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d58b69d..2058f15 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -71,6 +71,7 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.IccCardConstants.State;
import com.android.internal.telephony.PhoneConstants;
@@ -1111,7 +1112,8 @@
}
}
- private KeyguardUpdateMonitor(Context context) {
+ @VisibleForTesting
+ protected KeyguardUpdateMonitor(Context context) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
index edd1748..6aa465c 100644
--- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
+++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
@@ -24,6 +24,7 @@
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Point;
+import android.graphics.Region;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -36,7 +37,8 @@
import android.view.WindowInsets;
import android.view.WindowManager;
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Emulates a display cutout by drawing its shape in an overlay as supplied by
@@ -85,6 +87,7 @@
PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
| WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
+ lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
lp.setTitle("EmulatedDisplayCutout");
lp.gravity = Gravity.TOP;
return lp;
@@ -102,9 +105,8 @@
};
private static class CutoutView extends View {
- private Paint mPaint = new Paint();
- private Path mPath = new Path();
- private ArrayList<Point> mBoundingPolygon = new ArrayList<>();
+ private final Paint mPaint = new Paint();
+ private final Path mBounds = new Path();
CutoutView(Context context) {
super(context);
@@ -112,28 +114,22 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- insets.getDisplayCutout().getBoundingPolygon(mBoundingPolygon);
+ if (insets.getDisplayCutout() != null) {
+ insets.getDisplayCutout().getBounds().getBoundaryPath(mBounds);
+ } else {
+ mBounds.reset();
+ }
invalidate();
- return insets.consumeCutout();
+ return insets.consumeDisplayCutout();
}
@Override
protected void onDraw(Canvas canvas) {
- if (!mBoundingPolygon.isEmpty()) {
+ if (!mBounds.isEmpty()) {
mPaint.setColor(Color.DKGRAY);
mPaint.setStyle(Paint.Style.FILL);
- mPath.reset();
- for (int i = 0; i < mBoundingPolygon.size(); i++) {
- Point point = mBoundingPolygon.get(i);
- if (i == 0) {
- mPath.moveTo(point.x, point.y);
- } else {
- mPath.lineTo(point.x, point.y);
- }
- }
- mPath.close();
- canvas.drawPath(mPath, mPaint);
+ canvas.drawPath(mBounds, mPaint);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index e80d6d3..3177c03 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -16,6 +16,7 @@
package com.android.systemui;
+import android.app.AlarmManager;
import android.content.Context;
import android.util.ArrayMap;
import android.util.Log;
@@ -92,10 +93,10 @@
public ScrimController createScrimController(LightBarController lightBarController,
ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
- LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener,
- DozeParameters dozeParameters) {
+ LockscreenWallpaper lockscreenWallpaper, Consumer<Integer> scrimVisibleListener,
+ DozeParameters dozeParameters, AlarmManager alarmManager) {
return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
- scrimVisibleListener, dozeParameters);
+ scrimVisibleListener, dozeParameters, alarmManager);
}
public NotificationIconAreaController createNotificationIconAreaController(Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
index cc2244a..8515bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
@@ -40,12 +40,16 @@
private static final long DEFAULT_PROX_SCREEN_OFF_DELAY_MS = 10 * DateUtils.SECOND_IN_MILLIS;
private static final long DEFAULT_PROX_COOLDOWN_TRIGGER_MS = 2 * DateUtils.SECOND_IN_MILLIS;
private static final long DEFAULT_PROX_COOLDOWN_PERIOD_MS = 5 * DateUtils.SECOND_IN_MILLIS;
+ private static final long DEFAULT_WALLPAPER_VISIBILITY_MS = 60 * DateUtils.SECOND_IN_MILLIS;
+ private static final long DEFAULT_WALLPAPER_FADE_OUT_MS = 400;
static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array";
static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array";
static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay";
static final String KEY_PROX_COOLDOWN_TRIGGER_MS = "prox_cooldown_trigger";
static final String KEY_PROX_COOLDOWN_PERIOD_MS = "prox_cooldown_period";
+ static final String KEY_WALLPAPER_VISIBILITY_MS = "wallpaper_visibility_timeout";
+ static final String KEY_WALLPAPER_FADE_OUT_MS = "wallpaper_fade_out_duration";
/**
* Integer array to map ambient brightness type to real screen brightness.
@@ -89,6 +93,24 @@
*/
public long proxCooldownPeriodMs;
+ /**
+ * For how long(ms) the wallpaper should still be visible
+ * after entering AoD.
+ *
+ * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+ * @see #KEY_WALLPAPER_VISIBILITY_MS
+ */
+ public long wallpaperVisibilityDuration;
+
+ /**
+ * Duration(ms) of the fade out animation after
+ * {@link #KEY_WALLPAPER_VISIBILITY_MS} elapses.
+ *
+ * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+ * @see #KEY_WALLPAPER_FADE_OUT_MS
+ */
+ public long wallpaperFadeOutDuration;
+
private final KeyValueListParser mParser;
private final Context mContext;
private SettingsObserver mSettingsObserver;
@@ -138,6 +160,10 @@
DEFAULT_PROX_COOLDOWN_TRIGGER_MS);
proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
DEFAULT_PROX_COOLDOWN_PERIOD_MS);
+ wallpaperFadeOutDuration = mParser.getLong(KEY_WALLPAPER_FADE_OUT_MS,
+ DEFAULT_WALLPAPER_FADE_OUT_MS);
+ wallpaperVisibilityDuration = mParser.getLong(KEY_WALLPAPER_VISIBILITY_MS,
+ DEFAULT_WALLPAPER_VISIBILITY_MS);
screenBrightnessArray = mParser.getIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
resources.getIntArray(
R.array.config_doze_brightness_sensor_to_brightness));
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index a409fcb..0f0402d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -66,6 +66,7 @@
createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params),
new DozeScreenState(wrappedService, handler),
createDozeScreenBrightness(context, wrappedService, sensorManager, host, handler),
+ new DozeWallpaperState()
});
return machine;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 6650cc6..34d3928 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -24,9 +24,10 @@
import com.android.systemui.Dependency;
import com.android.systemui.plugins.DozeServicePlugin;
-import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
new file mode 100644
index 0000000..ee41001
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.doze;
+
+import android.app.IWallpaperManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+/**
+ * Propagates doze state to wallpaper engine.
+ */
+public class DozeWallpaperState implements DozeMachine.Part {
+
+ private static final String TAG = "DozeWallpaperState";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ @VisibleForTesting
+ final IWallpaperManager mWallpaperManagerService;
+ private boolean mIsAmbientMode;
+
+ public DozeWallpaperState() {
+ this(IWallpaperManager.Stub.asInterface(
+ ServiceManager.getService(Context.WALLPAPER_SERVICE)));
+ }
+
+ @VisibleForTesting
+ DozeWallpaperState(IWallpaperManager wallpaperManagerService) {
+ mWallpaperManagerService = wallpaperManagerService;
+ }
+
+ @Override
+ public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
+ final boolean isAmbientMode;
+ switch (newState) {
+ case DOZE_AOD:
+ case DOZE_AOD_PAUSING:
+ case DOZE_AOD_PAUSED:
+ case DOZE_REQUEST_PULSE:
+ case DOZE_PULSING:
+ case DOZE_PULSE_DONE:
+ isAmbientMode = true;
+ break;
+ default:
+ isAmbientMode = false;
+ }
+
+ if (isAmbientMode != mIsAmbientMode) {
+ mIsAmbientMode = isAmbientMode;
+ try {
+ Log.i(TAG, "AoD wallpaper state changed to: " + mIsAmbientMode);
+ mWallpaperManagerService.setInAmbientMode(mIsAmbientMode);
+ } catch (RemoteException e) {
+ // Cannot notify wallpaper manager service, but it's fine, let's just skip it.
+ Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode);
+ }
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.println("DozeWallpaperState:");
+ pw.println(" isAmbientMode: " + mIsAmbientMode);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 50cbd69..6d85fb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -26,6 +26,7 @@
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.systemui.R;
+import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import java.io.PrintWriter;
@@ -37,10 +38,12 @@
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private static IntInOutMatcher sPickupSubtypePerformsProxMatcher;
+ private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
public DozeParameters(Context context) {
mContext = context;
mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+ mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(context);
}
public void dump(PrintWriter pw) {
@@ -83,6 +86,11 @@
return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
}
+ public float getScreenBrightnessDoze() {
+ return mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
+ }
+
public int getPulseInDuration() {
return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
}
@@ -115,6 +123,26 @@
return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold);
}
+ /**
+ * For how long a wallpaper can be visible in AoD before it fades aways.
+ * @return duration in millis.
+ */
+ public long getWallpaperAodDuration() {
+ return mAlwaysOnPolicy.wallpaperVisibilityDuration;
+ }
+
+ /**
+ * How long it takes for the wallpaper fade away (Animation duration.)
+ * @return duration in millis.
+ */
+ public long getWallpaperFadeOutDuration() {
+ return mAlwaysOnPolicy.wallpaperFadeOutDuration;
+ }
+
+ /**
+ * Checks if always on is available and enabled for the current user.
+ * @return {@code true} if enabled and available.
+ */
public boolean getAlwaysOn() {
return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
}
@@ -123,7 +151,7 @@
* Some screens need to be completely black before changing the display power mode,
* unexpected behavior might happen if this parameter isn't respected.
*
- * @return true if screen needs to be completely black before a power transition.
+ * @return {@code true} if screen needs to be completely black before a power transition.
*/
public boolean getDisplayNeedsBlanking() {
return mContext.getResources().getBoolean(
@@ -134,7 +162,7 @@
* Whether we can implement our own screen off animation or if we need
* to rely on DisplayPowerManager to dim the display.
*
- * @return true if SystemUI can control the screen off animation.
+ * @return {@code true} if SystemUI can control the screen off animation.
*/
public boolean getCanControlScreenOffAnimation() {
return !mContext.getResources().getBoolean(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 168758f..14329b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.app.AlarmManager;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Color;
@@ -51,6 +52,7 @@
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.AlarmTimeout;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
@@ -73,6 +75,19 @@
= new PathInterpolator(0f, 0, 0.7f, 1f);
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
= new PathInterpolator(0.3f, 0f, 0.8f, 1f);
+
+ /**
+ * When both scrims have 0 alpha.
+ */
+ public static final int VISIBILITY_FULLY_TRANSPARENT = 0;
+ /**
+ * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
+ */
+ public static final int VISIBILITY_SEMI_TRANSPARENT = 1;
+ /**
+ * When at least 1 scrim is fully opaque (alpha set to 1.)
+ */
+ public static final int VISIBILITY_FULLY_OPAQUE = 2;
/**
* Default alpha value for most scrims.
*/
@@ -111,6 +126,7 @@
private final UnlockMethodCache mUnlockMethodCache;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DozeParameters mDozeParameters;
+ private final AlarmTimeout mTimeTicker;
private final SysuiColorExtractor mColorExtractor;
private GradientColors mLockColors;
@@ -138,23 +154,25 @@
private float mCurrentBehindAlpha = NOT_INITIALIZED;
private int mCurrentInFrontTint;
private int mCurrentBehindTint;
+ private boolean mWallpaperVisibilityTimedOut;
private int mPinnedHeadsUpCount;
private float mTopHeadsUpDragAmount;
private View mDraggedHeadsUpView;
private boolean mKeyguardFadingOutInProgress;
private ValueAnimator mKeyguardFadeoutAnimation;
- private boolean mScrimsVisible;
- private final Consumer<Boolean> mScrimVisibleListener;
+ private int mScrimsVisibility;
+ private final Consumer<Integer> mScrimVisibleListener;
private boolean mBlankScreen;
private boolean mScreenBlankingCallbackCalled;
private Callback mCallback;
+ private boolean mWallpaperSupportsAmbientMode;
private final WakeLock mWakeLock;
private boolean mWakeLockHeld;
public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
- ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener,
- DozeParameters dozeParameters) {
+ ScrimView scrimInFront, View headsUpScrim, Consumer<Integer> scrimVisibleListener,
+ DozeParameters dozeParameters, AlarmManager alarmManager) {
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
mHeadsUpScrim = headsUpScrim;
@@ -164,6 +182,8 @@
mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
mLightBarController = lightBarController;
mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
+ "hide_aod_wallpaper", new Handler());
mWakeLock = createWakeLock();
// Scrim alpha is initially set to the value on the resource but might be changed
// to make sure that text on top of it is legible.
@@ -235,14 +255,24 @@
mKeyguardFadeoutAnimation.cancel();
}
- // Do not let the device sleep until we're done with all animations
- if (!mWakeLockHeld) {
- if (mWakeLock != null) {
- mWakeLockHeld = true;
- mWakeLock.acquire();
- } else {
- Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+ // The device might sleep if it's entering AOD, we need to make sure that
+ // the animation plays properly until the last frame.
+ // It's important to avoid holding the wakelock unless necessary because
+ // WakeLock#aqcuire will trigger an IPC and will cause jank.
+ if (mState == ScrimState.AOD) {
+ holdWakeLock();
+ }
+
+ // AOD wallpapers should fade away after a while
+ if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn()
+ && (mState == ScrimState.AOD || mState == ScrimState.PULSING)) {
+ if (!mWallpaperVisibilityTimedOut) {
+ mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
+ AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
}
+ } else {
+ mTimeTicker.cancel();
+ mWallpaperVisibilityTimedOut = false;
}
if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
@@ -279,6 +309,30 @@
mTracking = false;
}
+ @VisibleForTesting
+ protected void onHideWallpaperTimeout() {
+ if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
+ return;
+ }
+
+ holdWakeLock();
+ mWallpaperVisibilityTimedOut = true;
+ mAnimateChange = true;
+ mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration();
+ scheduleUpdate();
+ }
+
+ private void holdWakeLock() {
+ if (!mWakeLockHeld) {
+ if (mWakeLock != null) {
+ mWakeLockHeld = true;
+ mWakeLock.acquire();
+ } else {
+ Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+ }
+ }
+ }
+
/**
* Current state of the shade expansion when pulling it from the top.
* This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
@@ -391,6 +445,14 @@
mLightBarController.setScrimColor(mScrimInFront.getColors());
}
+ // We want to override the back scrim opacity for AOD and PULSING
+ // when it's time to fade the wallpaper away.
+ boolean overrideBackScrimAlpha = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
+ && mWallpaperVisibilityTimedOut;
+ if (overrideBackScrimAlpha) {
+ mCurrentBehindAlpha = 1;
+ }
+
setScrimInFrontAlpha(mCurrentInFrontAlpha);
setScrimBehindAlpha(mCurrentBehindAlpha);
@@ -398,12 +460,18 @@
}
private void dispatchScrimsVisible() {
- boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
+ final int currentScrimVisibility;
+ if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
+ currentScrimVisibility = VISIBILITY_FULLY_OPAQUE;
+ } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
+ currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT;
+ } else {
+ currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT;
+ }
- if (mScrimsVisible != scrimsVisible) {
- mScrimsVisible = scrimsVisible;
-
- mScrimVisibleListener.accept(scrimsVisible);
+ if (mScrimsVisibility != currentScrimVisibility) {
+ mScrimsVisibility = currentScrimVisibility;
+ mScrimVisibleListener.accept(currentScrimVisibility);
}
}
@@ -811,6 +879,14 @@
pw.print(" mTracking="); pw.println(mTracking);
}
+ public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+ mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
+ ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode);
+ }
+ }
+
public interface Callback {
default void onStart() {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index c33cc50..fa2c1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -19,6 +19,7 @@
import android.graphics.Color;
import android.os.Trace;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -89,8 +90,10 @@
updateScrimColor(mScrimInFront, 1, Color.BLACK);
}
final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
- mBlankScreen = previousState == ScrimState.PULSING;
- mCurrentBehindAlpha = 1;
+ final boolean wasPulsing = previousState == ScrimState.PULSING;
+ mBlankScreen = wasPulsing && !mCanControlScreenOff;
+ mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
+ && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
mCurrentInFrontTint = Color.BLACK;
mCurrentBehindTint = Color.BLACK;
@@ -106,9 +109,10 @@
PULSING {
@Override
public void prepare(ScrimState previousState) {
- mCurrentBehindAlpha = 1;
mCurrentInFrontAlpha = 0;
mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
+ && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
mCurrentBehindTint = Color.BLACK;
mBlankScreen = mDisplayRequiresBlanking;
if (mDisplayRequiresBlanking) {
@@ -158,6 +162,8 @@
DozeParameters mDozeParameters;
boolean mDisplayRequiresBlanking;
boolean mCanControlScreenOff;
+ boolean mWallpaperSupportsAmbientMode;
+ KeyguardUpdateMonitor mKeyguardUpdateMonitor;
public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
mScrimInFront = scrimInFront;
@@ -165,6 +171,7 @@
mDozeParameters = dozeParameters;
mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
mCanControlScreenOff = dozeParameters.getCanControlScreenOffAnimation();
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(scrimInFront.getContext());
}
public void prepare(ScrimState previousState) {
@@ -218,4 +225,8 @@
public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
}
+
+ public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+ mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index fecd6bd..c5349d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -46,6 +46,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
+import android.app.AlarmManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -53,6 +54,7 @@
import android.app.StatusBarManager;
import android.app.TaskStackBuilder;
import android.app.WallpaperColors;
+import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -249,7 +251,6 @@
import java.util.List;
import java.util.Map;
import java.util.Stack;
-import java.util.function.Function;
public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
@@ -526,7 +527,22 @@
protected NotificationLockscreenUserManager mLockscreenUserManager;
protected NotificationRemoteInputManager mRemoteInputManager;
+ private BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
+ if (wallpaperManager == null) {
+ Log.w(TAG, "WallpaperManager not available");
+ return;
+ }
+ WallpaperInfo info = wallpaperManager.getWallpaperInfo();
+ final boolean supportsAmbientMode = info != null &&
+ info.getSupportsAmbientMode();
+ mStatusBarWindowManager.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+ mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+ }
+ };
private Runnable mLaunchTransitionEndRunnable;
protected boolean mLaunchTransitionFadingAway;
@@ -704,6 +720,11 @@
createAndAddWindows();
+ // Make sure we always have the most current wallpaper info.
+ IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
+ mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter);
+ mWallpaperChangedReceiver.onReceive(mContext, null);
+
mCommandQueue.disable(switches[0], switches[6], false /* animate */);
setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
fullscreenStackBounds, dockedStackBounds);
@@ -922,9 +943,9 @@
scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper,
scrimsVisible -> {
if (mStatusBarWindowManager != null) {
- mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
+ mStatusBarWindowManager.setScrimsVisibility(scrimsVisible);
}
- }, new DozeParameters(mContext));
+ }, new DozeParameters(mContext), mContext.getSystemService(AlarmManager.class));
if (mScrimSrcModeEnabled) {
Runnable runnable = () -> {
boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index b0b5b8e..c30f633 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -54,6 +54,7 @@
private final Context mContext;
private final WindowManager mWindowManager;
private final IActivityManager mActivityManager;
+ private final DozeParameters mDozeParameters;
private View mStatusBarView;
private WindowManager.LayoutParams mLp;
private WindowManager.LayoutParams mLpChanged;
@@ -70,8 +71,8 @@
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mActivityManager = ActivityManager.getService();
mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
- mScreenBrightnessDoze = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
+ mDozeParameters = new DozeParameters(mContext);
+ mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
}
private boolean shouldEnableKeyguardScreenRotation() {
@@ -136,7 +137,11 @@
mLpChanged.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
}
- if (state.keyguardShowing && !state.backdropShowing && !state.dozing) {
+ final boolean showWallpaperOnAod = mDozeParameters.getAlwaysOn() &&
+ state.wallpaperSupportsAmbientMode &&
+ state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_OPAQUE;
+ if (state.keyguardShowing && !state.backdropShowing &&
+ (!state.dozing || showWallpaperOnAod)) {
mLpChanged.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
} else {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -188,7 +193,8 @@
private boolean isExpanded(State state) {
return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
|| state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
- || state.headsUpShowing || state.scrimsVisible);
+ || state.headsUpShowing
+ || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
}
private void applyFitsSystemWindows(State state) {
@@ -336,8 +342,8 @@
apply(mCurrentState);
}
- public void setScrimsVisible(boolean scrimsVisible) {
- mCurrentState.scrimsVisible = scrimsVisible;
+ public void setScrimsVisibility(int scrimsVisibility) {
+ mCurrentState.scrimsVisibility = scrimsVisibility;
apply(mCurrentState);
}
@@ -346,6 +352,11 @@
apply(mCurrentState);
}
+ public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+ mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
+ apply(mCurrentState);
+ }
+
/**
* @param state The {@link StatusBarState} of the status bar.
*/
@@ -433,6 +444,7 @@
boolean forceDozeBrightness;
boolean forceUserActivity;
boolean backdropShowing;
+ boolean wallpaperSupportsAmbientMode;
/**
* The {@link StatusBar} state from the status bar.
@@ -442,7 +454,7 @@
boolean remoteInputActive;
boolean forcePluginOpen;
boolean dozing;
- boolean scrimsVisible;
+ int scrimsVisibility;
private boolean isKeyguardShowingAndNotOccluded() {
return keyguardShowing && !keyguardOccluded;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index fa82e33..f8843a9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -16,6 +16,10 @@
package com.android.systemui.volume;
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED;
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING;
+import static android.support.v7.media.MediaRouter.UNSELECT_REASON_DISCONNECTED;
+
import static com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription;
import android.bluetooth.BluetoothClass;
@@ -27,7 +31,15 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaRouteSelector;
+import android.support.v7.media.MediaRouter;
import android.util.Log;
import android.util.Pair;
@@ -38,8 +50,13 @@
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.BluetoothController;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
public class OutputChooserDialog extends SystemUIDialog
implements DialogInterface.OnDismissListener, OutputChooserLayout.Callback {
@@ -47,15 +64,33 @@
private static final String TAG = Util.logTag(OutputChooserDialog.class);
private static final int MAX_DEVICES = 10;
+ private static final long UPDATE_DELAY_MS = 300L;
+ static final int MSG_UPDATE_ITEMS = 1;
+
private final Context mContext;
private final BluetoothController mController;
+ private final WifiManager mWifiManager;
private OutputChooserLayout mView;
+ private final MediaRouter mRouter;
+ private final MediaRouterCallback mRouterCallback;
+ private long mLastUpdateTime;
+ private final MediaRouteSelector mRouteSelector;
+ private Drawable mDefaultIcon;
+ private Drawable mTvIcon;
+ private Drawable mSpeakerIcon;
+ private Drawable mSpeakerGroupIcon;
public OutputChooserDialog(Context context) {
super(context);
mContext = context;
mController = Dependency.get(BluetoothController.class);
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ mRouter = MediaRouter.getInstance(context);
+ mRouterCallback = new MediaRouterCallback();
+ mRouteSelector = new MediaRouteSelector.Builder()
+ .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+ .build();
final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(mReceiver, filter);
@@ -67,10 +102,21 @@
setContentView(R.layout.output_chooser);
setCanceledOnTouchOutside(true);
setOnDismissListener(this::onDismiss);
+ setTitle(R.string.output_title);
+
mView = findViewById(R.id.output_chooser);
mView.setCallback(this);
- updateItems();
- mController.addCallback(mCallback);
+
+ mDefaultIcon = mContext.getDrawable(R.drawable.ic_cast);
+ mTvIcon = mContext.getDrawable(R.drawable.ic_tv);
+ mSpeakerIcon = mContext.getDrawable(R.drawable.ic_speaker);
+ mSpeakerGroupIcon = mContext.getDrawable(R.drawable.ic_speaker_group);
+
+ final boolean wifiOff = !mWifiManager.isWifiEnabled();
+ final boolean btOff = !mController.isBluetoothEnabled();
+ if (wifiOff || btOff) {
+ mView.setEmptyState(getDisabledServicesMessage(wifiOff, btOff));
+ }
}
protected void cleanUp() {}
@@ -82,43 +128,97 @@
}
@Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mRouter.addCallback(mRouteSelector, mRouterCallback,
+ MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+ mController.addCallback(mCallback);
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mRouter.removeCallback(mRouterCallback);
+ mController.removeCallback(mCallback);
+ super.onDetachedFromWindow();
+ }
+
+ @Override
public void onDismiss(DialogInterface unused) {
mContext.unregisterReceiver(mReceiver);
- mController.removeCallback(mCallback);
cleanUp();
}
@Override
public void onDetailItemClick(OutputChooserLayout.Item item) {
if (item == null || item.tag == null) return;
- final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
- if (device != null && device.getMaxConnectionState()
- == BluetoothProfile.STATE_DISCONNECTED) {
- mController.connect(device);
+ if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ if (device != null && device.getMaxConnectionState()
+ == BluetoothProfile.STATE_DISCONNECTED) {
+ mController.connect(device);
+ }
+ } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+ final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag;
+ if (route.isEnabled()) {
+ route.select();
+ }
}
}
@Override
public void onDetailItemDisconnect(OutputChooserLayout.Item item) {
if (item == null || item.tag == null) return;
- final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
- if (device != null) {
- mController.disconnect(device);
+ if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ if (device != null) {
+ mController.disconnect(device);
+ }
+ } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+ mRouter.unselect(UNSELECT_REASON_DISCONNECTED);
}
}
private void updateItems() {
- if (mView == null) return;
- if (mController.isBluetoothEnabled()) {
- mView.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
- R.string.quick_settings_bluetooth_detail_empty_text);
- mView.setItemsVisible(true);
- } else {
- mView.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
- R.string.bt_is_off);
- mView.setItemsVisible(false);
+ if (SystemClock.uptimeMillis() - mLastUpdateTime < UPDATE_DELAY_MS) {
+ mHandler.removeMessages(MSG_UPDATE_ITEMS);
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_UPDATE_ITEMS),
+ mLastUpdateTime + UPDATE_DELAY_MS);
+ return;
}
+ mLastUpdateTime = SystemClock.uptimeMillis();
+ if (mView == null) return;
ArrayList<OutputChooserLayout.Item> items = new ArrayList<>();
+
+ // Add bluetooth devices
+ addBluetoothDevices(items);
+
+ // Add remote displays
+ addRemoteDisplayRoutes(items);
+
+ Collections.sort(items, ItemComparator.sInstance);
+
+ if (items.size() == 0) {
+ String emptyMessage = mContext.getString(R.string.output_none_found);
+ final boolean wifiOff = !mWifiManager.isWifiEnabled();
+ final boolean btOff = !mController.isBluetoothEnabled();
+ if (wifiOff || btOff) {
+ emptyMessage = getDisabledServicesMessage(wifiOff, btOff);
+ }
+ mView.setEmptyState(emptyMessage);
+ }
+
+ mView.setItems(items.toArray(new OutputChooserLayout.Item[items.size()]));
+ }
+
+ private String getDisabledServicesMessage(boolean wifiOff, boolean btOff) {
+ return mContext.getString(R.string.output_none_found_service_off,
+ wifiOff && btOff ? mContext.getString(R.string.output_service_bt_wifi)
+ : wifiOff ? mContext.getString(R.string.output_service_wifi)
+ : mContext.getString(R.string.output_service_bt));
+ }
+
+ private void addBluetoothDevices(List<OutputChooserLayout.Item> items) {
final Collection<CachedBluetoothDevice> devices = mController.getDevices();
if (devices != null) {
int connectedDevices = 0;
@@ -134,6 +234,7 @@
item.iconResId = R.drawable.ic_qs_bluetooth_on;
item.line1 = device.getName();
item.tag = device;
+ item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_BT;
int state = device.getMaxConnectionState();
if (state == BluetoothProfile.STATE_CONNECTED) {
item.iconResId = R.drawable.ic_qs_bluetooth_connected;
@@ -163,7 +264,87 @@
}
}
}
- mView.setItems(items.toArray(new OutputChooserLayout.Item[items.size()]));
+ }
+
+ private void addRemoteDisplayRoutes(List<OutputChooserLayout.Item> items) {
+ List<MediaRouter.RouteInfo> routes = mRouter.getRoutes();
+ for(MediaRouter.RouteInfo route : routes) {
+ if (route.isDefaultOrBluetooth() || !route.isEnabled()
+ || !route.matchesSelector(mRouteSelector)) {
+ continue;
+ }
+ final OutputChooserLayout.Item item = new OutputChooserLayout.Item();
+ item.icon = getIconDrawable(route);
+ item.line1 = route.getName();
+ item.tag = route;
+ item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER;
+ if (route.getConnectionState() == CONNECTION_STATE_CONNECTING) {
+ mContext.getString(R.string.quick_settings_connecting);
+ } else {
+ item.line2 = route.getDescription();
+ }
+
+ if (route.getConnectionState() == CONNECTION_STATE_CONNECTED) {
+ item.canDisconnect = true;
+ }
+ items.add(item);
+ }
+ }
+
+ private Drawable getIconDrawable(MediaRouter.RouteInfo route) {
+ Uri iconUri = route.getIconUri();
+ if (iconUri != null) {
+ try {
+ InputStream is = getContext().getContentResolver().openInputStream(iconUri);
+ Drawable drawable = Drawable.createFromStream(is, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to load " + iconUri, e);
+ // Falls back.
+ }
+ }
+ return getDefaultIconDrawable(route);
+ }
+
+ private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) {
+ // If the type of the receiver device is specified, use it.
+ switch (route.getDeviceType()) {
+ case MediaRouter.RouteInfo.DEVICE_TYPE_TV:
+ return mTvIcon;
+ case MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER:
+ return mSpeakerIcon;
+ }
+
+ // Otherwise, make the best guess based on other route information.
+ if (route instanceof MediaRouter.RouteGroup) {
+ // Only speakers can be grouped for now.
+ return mSpeakerGroupIcon;
+ }
+ return mDefaultIcon;
+ }
+
+ private final class MediaRouterCallback extends MediaRouter.Callback {
+ @Override
+ public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+ updateItems();
+ }
+
+ @Override
+ public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+ updateItems();
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+ updateItems();
+ }
+
+ @Override
+ public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
+ dismiss();
+ }
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -188,4 +369,33 @@
updateItems();
}
};
+
+ static final class ItemComparator implements Comparator<OutputChooserLayout.Item> {
+ public static final ItemComparator sInstance = new ItemComparator();
+
+ @Override
+ public int compare(OutputChooserLayout.Item lhs, OutputChooserLayout.Item rhs) {
+ // Connected item(s) first
+ if (lhs.canDisconnect != rhs.canDisconnect) {
+ return Boolean.compare(rhs.canDisconnect, lhs.canDisconnect);
+ }
+ // Bluetooth items before media routes
+ if (lhs.deviceType != rhs.deviceType) {
+ return Integer.compare(lhs.deviceType, rhs.deviceType);
+ }
+ // then by name
+ return lhs.line1.toString().compareToIgnoreCase(rhs.line1.toString());
+ }
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_UPDATE_ITEMS:
+ updateItems();
+ break;
+ }
+ }
+ };
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
index e8be4fd..22ced60 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
@@ -55,7 +55,6 @@
private AutoSizingList mItemList;
private View mEmpty;
private TextView mEmptyText;
- private ImageView mEmptyIcon;
private Item[] mItems;
@@ -76,7 +75,6 @@
mEmpty = findViewById(android.R.id.empty);
mEmpty.setVisibility(GONE);
mEmptyText = mEmpty.findViewById(android.R.id.title);
- mEmptyIcon = mEmpty.findViewById(android.R.id.icon);
}
@Override
@@ -93,9 +91,8 @@
}
}
- public void setEmptyState(int icon, int text) {
+ public void setEmptyState(String text) {
mEmpty.post(() -> {
- mEmptyIcon.setImageResource(icon);
mEmptyText.setText(text);
});
}
@@ -241,6 +238,8 @@
}
public static class Item {
+ public static int DEVICE_TYPE_BT = 1;
+ public static int DEVICE_TYPE_MEDIA_ROUTER = 2;
public int iconResId;
public Drawable icon;
public Drawable overlay;
@@ -249,6 +248,7 @@
public Object tag;
public boolean canDisconnect;
public int icon2 = -1;
+ public int deviceType = 0;
}
public interface Callback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
new file mode 100644
index 0000000..8e7f83d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.doze;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.IWallpaperManager;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.support.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class DozeWallpaperStateTest extends SysuiTestCase {
+
+ @Test
+ public void testDreamNotification() throws RemoteException {
+ IWallpaperManager wallpaperManagerService = mock(IWallpaperManager.class);
+ DozeWallpaperState dozeWallpaperState = new DozeWallpaperState(wallpaperManagerService);
+ dozeWallpaperState.transitionTo(DozeMachine.State.UNINITIALIZED,
+ DozeMachine.State.DOZE_AOD);
+ verify(wallpaperManagerService).setInAmbientMode(eq(true));
+ dozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.FINISH);
+ verify(wallpaperManagerService).setInAmbientMode(eq(false));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index a40ef64..6d2691c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,13 +16,22 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_OPAQUE;
+import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_SEMI_TRANSPARENT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.animation.Animator;
+import android.app.AlarmManager;
import android.graphics.Color;
import android.os.Handler;
import android.os.Looper;
@@ -31,6 +40,7 @@
import android.testing.TestableLooper;
import android.view.View;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.util.wakelock.WakeLock;
@@ -52,12 +62,13 @@
private ScrimView mScrimBehind;
private ScrimView mScrimInFront;
private View mHeadsUpScrim;
- private Consumer<Boolean> mScrimVisibilityCallback;
- private Boolean mScrimVisibile;
+ private Consumer<Integer> mScrimVisibilityCallback;
+ private int mScrimVisibility;
private LightBarController mLightBarController;
private DozeParameters mDozeParamenters;
private WakeLock mWakeLock;
private boolean mAlwaysOnEnabled;
+ private AlarmManager mAlarmManager;
@Before
public void setup() {
@@ -66,13 +77,15 @@
mScrimInFront = new ScrimView(getContext());
mHeadsUpScrim = mock(View.class);
mWakeLock = mock(WakeLock.class);
+ mAlarmManager = mock(AlarmManager.class);
mAlwaysOnEnabled = true;
- mScrimVisibilityCallback = (Boolean visible) -> mScrimVisibile = visible;
+ mScrimVisibilityCallback = (Integer visible) -> mScrimVisibility = visible;
mDozeParamenters = mock(DozeParameters.class);
when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true);
mScrimController = new SynchronousScrimController(mLightBarController, mScrimBehind,
- mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters);
+ mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters,
+ mAlarmManager);
}
@Test
@@ -87,29 +100,70 @@
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be visible without tint
- assertScrimVisibility(false /* front */, true /* behind */);
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
assertScrimTint(mScrimBehind, false /* tinted */);
}
@Test
- public void transitionToAod() {
+ public void transitionToAod_withRegularWallpaper() {
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be visible with tint
- assertScrimVisibility(false /* front */, true /* behind */);
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimTint(mScrimBehind, true /* tinted */);
+ assertScrimTint(mScrimInFront, true /* tinted */);
+ }
+
+ @Test
+ public void transitionToAod_withAodWallpaper() {
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be transparent
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+ // Move on to PULSING and check if the back scrim is still transparent
+ mScrimController.transitionTo(ScrimState.PULSING);
+ mScrimController.finishAnimationsImmediately();
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+ }
+
+ @Test
+ public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() {
+ ScrimState.AOD.mKeyguardUpdateMonitor = new KeyguardUpdateMonitor(getContext()) {
+ @Override
+ public boolean hasLockscreenWallpaper() {
+ return true;
+ }
+ };
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible with tint
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
assertScrimTint(mScrimBehind, true /* tinted */);
assertScrimTint(mScrimInFront, true /* tinted */);
}
@Test
public void transitionToPulsing() {
+ // Pre-condition
+ // Need to go to AoD first because PULSING doesn't change
+ // the back scrim opacity - otherwise it would hide AoD wallpapers.
+ mScrimController.setWallpaperSupportsAmbientMode(false);
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+
mScrimController.transitionTo(ScrimState.PULSING);
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be visible with tint
// Pulse callback should have been invoked
- assertScrimVisibility(false /* front */, true /* behind */);
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
assertScrimTint(mScrimBehind, true /* tinted */);
}
@@ -119,7 +173,7 @@
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be visible without tint
- assertScrimVisibility(true /* front */, true /* behind */);
+ assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
assertScrimTint(mScrimBehind, false /* tinted */);
}
@@ -129,13 +183,13 @@
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be transparent
- assertScrimVisibility(false /* front */, false /* behind */);
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
assertScrimTint(mScrimBehind, false /* tinted */);
assertScrimTint(mScrimInFront, false /* tinted */);
// Back scrim should be visible after start dragging
mScrimController.setPanelExpansion(0.5f);
- assertScrimVisibility(false /* front */, true /* behind */);
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
}
@Test
@@ -151,7 +205,7 @@
// Front scrim should be transparent
// Back scrim should be transparent
// Neither scrims should be tinted anymore after the animation.
- assertScrimVisibility(false /* front */, false /* behind */);
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
assertScrimTint(mScrimInFront, false /* tinted */);
assertScrimTint(mScrimBehind, false /* tinted */);
}
@@ -169,8 +223,8 @@
Assert.assertTrue("Scrim should be visible during transition. Alpha: "
+ mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
assertScrimTint(mScrimInFront, true /* tinted */);
- Assert.assertTrue("Scrim should be visible during transition.",
- mScrimVisibile);
+ Assert.assertSame("Scrim should be visible during transition.",
+ mScrimVisibility, VISIBILITY_FULLY_OPAQUE);
}
});
mScrimController.finishAnimationsImmediately();
@@ -222,12 +276,19 @@
}
@Test
- public void testHoldsWakeLock() {
+ public void testHoldsWakeLock_whenAOD() {
mScrimController.transitionTo(ScrimState.AOD);
- verify(mWakeLock, times(1)).acquire();
+ verify(mWakeLock).acquire();
verify(mWakeLock, never()).release();
mScrimController.finishAnimationsImmediately();
- verify(mWakeLock, times(1)).release();
+ verify(mWakeLock).release();
+ }
+
+ @Test
+ public void testDoesNotHoldWakeLock_whenUnlocking() {
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.finishAnimationsImmediately();
+ verifyZeroInteractions(mWakeLock);
}
@Test
@@ -236,7 +297,30 @@
mScrimController.finishAnimationsImmediately();
ScrimController.Callback callback = mock(ScrimController.Callback.class);
mScrimController.transitionTo(ScrimState.UNLOCKED, callback);
- verify(callback, times(1)).onFinished();
+ verify(callback).onFinished();
+ }
+
+ @Test
+ public void testHoldsAodWallpaperAnimationLock() {
+ // Pre-conditions
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ reset(mWakeLock);
+
+ mScrimController.onHideWallpaperTimeout();
+ verify(mWakeLock).acquire();
+ verify(mWakeLock, never()).release();
+ mScrimController.finishAnimationsImmediately();
+ verify(mWakeLock).release();
+ }
+
+ @Test
+ public void testWillHideAoDWallpaper() {
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+ mScrimController.transitionTo(ScrimState.AOD);
+ verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
}
private void assertScrimTint(ScrimView scrimView, boolean tinted) {
@@ -247,12 +331,23 @@
tinted, viewIsTinted);
}
- private void assertScrimVisibility(boolean inFront, boolean behind) {
+ private void assertScrimVisibility(int inFront, int behind) {
+ boolean inFrontVisible = inFront != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
+ boolean behindVisible = behind != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
- + mScrimInFront.getViewAlpha(), inFront, mScrimInFront.getViewAlpha() > 0);
+ + mScrimInFront.getViewAlpha(), inFrontVisible, mScrimInFront.getViewAlpha() > 0);
Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
- + mScrimBehind.getViewAlpha(), behind, mScrimBehind.getViewAlpha() > 0);
- Assert.assertEquals("Invalid visibility.", inFront || behind, mScrimVisibile);
+ + mScrimBehind.getViewAlpha(), behindVisible, mScrimBehind.getViewAlpha() > 0);
+
+ final int visibility;
+ if (inFront == VISIBILITY_FULLY_OPAQUE || behind == VISIBILITY_FULLY_OPAQUE) {
+ visibility = VISIBILITY_FULLY_OPAQUE;
+ } else if (inFront > VISIBILITY_FULLY_TRANSPARENT || behind > VISIBILITY_FULLY_TRANSPARENT) {
+ visibility = VISIBILITY_SEMI_TRANSPARENT;
+ } else {
+ visibility = VISIBILITY_FULLY_TRANSPARENT;
+ }
+ Assert.assertEquals("Invalid visibility.", visibility, mScrimVisibility);
}
/**
@@ -264,9 +359,10 @@
public SynchronousScrimController(LightBarController lightBarController,
ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
- Consumer<Boolean> scrimVisibleListener, DozeParameters dozeParameters) {
+ Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
+ AlarmManager alarmManager) {
super(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
- scrimVisibleListener, dozeParameters);
+ scrimVisibleListener, dozeParameters, alarmManager);
mHandler = new FakeHandler(Looper.myLooper());
}
diff --git a/packages/VpnDialogs/res/values-ne/strings.xml b/packages/VpnDialogs/res/values-ne/strings.xml
index c19ae52..5019a06 100644
--- a/packages/VpnDialogs/res/values-ne/strings.xml
+++ b/packages/VpnDialogs/res/values-ne/strings.xml
@@ -25,8 +25,8 @@
<string name="data_received" msgid="4062776929376067820">"प्राप्त भयो:"</string>
<string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> बाइटहरू / <xliff:g id="NUMBER_1">%2$s</xliff:g> प्याकेटहरू"</string>
<string name="always_on_disconnected_title" msgid="1906740176262776166">"सधैँ-सक्रिय रहने VPN सेवामा जडान गर्न सकिँदैन"</string>
- <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> लाई सधैँ जडान भइरहनेगरि सेट अप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। तपाईंको फोन <xliff:g id="VPN_APP_1">%1$s</xliff:g> मा पुन: जडान नहुँदासम्म यसले कुनै सार्वजनिक नेटवर्क प्रयोग गर्नेछ।"</string>
- <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> लाई सधैँ पनि जडान भइरहनेगरि सेट अप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। VPN पुन: जडान नहुँदासम्म तपाईंसँग कुनै पनि इन्टरनेट रहनेछैन।"</string>
+ <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> लाई सधैँ जडान भइरहनेगरि सेटअप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। तपाईंको फोन <xliff:g id="VPN_APP_1">%1$s</xliff:g> मा पुन: जडान नहुँदासम्म यसले कुनै सार्वजनिक नेटवर्क प्रयोग गर्नेछ।"</string>
+ <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> लाई सधैँ पनि जडान भइरहनेगरि सेटअप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। VPN पुन: जडान नहुँदासम्म तपाईंसँग कुनै पनि इन्टरनेट रहनेछैन।"</string>
<string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
<string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN सम्बन्धी सेटिङहरू परिवर्तन गर्नुहोस्"</string>
<string name="configure" msgid="4905518375574791375">"कन्फिगर गर्नुहोस्"</string>
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
similarity index 98%
rename from services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
rename to services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 01679dd..3d7d6b7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -71,7 +71,7 @@
* This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
* It is responsible for behavior common to both types of clients.
*/
-abstract class AccessibilityClientConnection extends IAccessibilityServiceConnection.Stub
+abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub
implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter,
FingerprintGestureDispatcher.FingerprintGestureClient {
private static final boolean DEBUG = false;
@@ -238,7 +238,7 @@
int flags);
}
- public AccessibilityClientConnection(Context context, ComponentName componentName,
+ public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
Object lock, SecurityPolicy securityPolicy, SystemSupport systemSupport,
WindowManagerInternal windowManagerInternal,
@@ -339,6 +339,11 @@
}
}
+ int getRelevantEventTypes() {
+ return (mUsesAccessibilityCache ? AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK : 0)
+ | mEventTypes;
+ }
+
@Override
public void setServiceInfo(AccessibilityServiceInfo info) {
final long identity = Binder.clearCallingIdentity();
@@ -954,13 +959,15 @@
public void notifyAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
+ final int eventType = event.getEventType();
+
final boolean serviceWantsEvent = wantsEventLocked(event);
- if (!serviceWantsEvent && !mUsesAccessibilityCache &&
- ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & event.getEventType()) == 0)) {
+ final boolean requiredForCacheConsistency = mUsesAccessibilityCache
+ && ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & eventType) != 0);
+ if (!serviceWantsEvent && !requiredForCacheConsistency) {
return;
}
- final int eventType = event.getEventType();
// Make a copy since during dispatch it is possible the event to
// be modified to remove its source if the receiving service does
// not have permission to access the window content.
@@ -1226,6 +1233,10 @@
return windowId;
}
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
private final class InvocationHandler extends Handler {
public static final int MSG_ON_GESTURE = 1;
public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 270a762..d83f6ae 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -20,6 +20,8 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -84,7 +86,6 @@
import android.view.View;
import android.view.WindowInfo;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityCache;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
@@ -114,6 +115,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -131,7 +133,7 @@
* on the device. Events are dispatched to {@link AccessibilityService}s.
*/
public class AccessibilityManagerService extends IAccessibilityManager.Stub
- implements AccessibilityClientConnection.SystemSupport {
+ implements AbstractAccessibilityServiceConnection.SystemSupport {
private static final boolean DEBUG = false;
@@ -455,7 +457,7 @@
}
@Override
- public long addClient(IAccessibilityManagerClient client, int userId) {
+ public long addClient(IAccessibilityManagerClient callback, int userId) {
synchronized (mLock) {
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
@@ -467,15 +469,17 @@
// the system UI or the system we add it to the global state that
// is shared across users.
UserState userState = getUserStateLocked(resolvedUserId);
+ Client client = new Client(callback, Binder.getCallingUid(), userState);
if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
- mGlobalClients.register(client);
+ mGlobalClients.register(callback, client);
if (DEBUG) {
Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
}
return IntPair.of(
- userState.getClientState(), userState.mLastSentRelevantEventTypes);
+ userState.getClientState(),
+ client.mLastSentRelevantEventTypes);
} else {
- userState.mUserClients.register(client);
+ userState.mUserClients.register(callback, client);
// If this client is not for the current user we do not
// return a state since it is not for the foreground user.
// We will send the state to the client on a user switch.
@@ -485,7 +489,7 @@
}
return IntPair.of(
(resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0,
- userState.mLastSentRelevantEventTypes);
+ client.mLastSentRelevantEventTypes);
}
}
}
@@ -951,7 +955,7 @@
* Has no effect if no item has accessibility focus, if the item with accessibility
* focus does not expose the specified action, or if the action fails.
*
- * @param actionId The id of the action to perform.
+ * @param action The action to perform.
*
* @return {@code true} if the action was performed. {@code false} if it was not.
*/
@@ -1329,33 +1333,67 @@
}
private void updateRelevantEventsLocked(UserState userState) {
- int relevantEventTypes = AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK;
- for (AccessibilityServiceConnection service : userState.mBoundServices) {
- relevantEventTypes |= service.mEventTypes;
- }
- relevantEventTypes |= mUiAutomationManager.getRequestedEventMaskLocked();
- int finalRelevantEventTypes = relevantEventTypes;
+ mMainHandler.post(() -> {
+ broadcastToClients(userState, ignoreRemoteException(client -> {
+ int relevantEventTypes = computeRelevantEventTypes(userState, client);
- if (userState.mLastSentRelevantEventTypes != finalRelevantEventTypes) {
- userState.mLastSentRelevantEventTypes = finalRelevantEventTypes;
- mMainHandler.obtainMessage(MainHandler.MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS,
- userState.mUserId, finalRelevantEventTypes);
- mMainHandler.post(() -> {
- broadcastToClients(userState, (client) -> {
- try {
- client.setRelevantEventTypes(finalRelevantEventTypes);
- } catch (RemoteException re) {
- /* ignore */
- }
- });
- });
+ if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+ client.mLastSentRelevantEventTypes = relevantEventTypes;
+ client.mCallback.setRelevantEventTypes(relevantEventTypes);
+ }
+ }));
+ });
+ }
+
+ private int computeRelevantEventTypes(UserState userState, Client client) {
+ int relevantEventTypes = 0;
+
+ int numBoundServices = userState.mBoundServices.size();
+ for (int i = 0; i < numBoundServices; i++) {
+ AccessibilityServiceConnection service =
+ userState.mBoundServices.get(i);
+ relevantEventTypes |= isClientInPackageWhitelist(service.getServiceInfo(), client)
+ ? service.getRelevantEventTypes()
+ : 0;
}
+ relevantEventTypes |= isClientInPackageWhitelist(
+ mUiAutomationManager.getServiceInfo(), client)
+ ? mUiAutomationManager.getRelevantEventTypes()
+ : 0;
+ return relevantEventTypes;
+ }
+
+ private static boolean isClientInPackageWhitelist(
+ @Nullable AccessibilityServiceInfo serviceInfo, Client client) {
+ if (serviceInfo == null) return false;
+
+ String[] clientPackages = client.mPackageNames;
+ boolean result = ArrayUtils.isEmpty(serviceInfo.packageNames);
+ if (!result && clientPackages != null) {
+ for (String packageName : clientPackages) {
+ if (ArrayUtils.contains(serviceInfo.packageNames, packageName)) {
+ result = true;
+ break;
+ }
+ }
+ }
+ if (!result) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Dropping events: "
+ + Arrays.toString(clientPackages) + " -> "
+ + serviceInfo.getComponentName().flattenToShortString()
+ + " due to not being in package whitelist "
+ + Arrays.toString(serviceInfo.packageNames));
+ }
+ }
+
+ return result;
}
private void broadcastToClients(
- UserState userState, Consumer<IAccessibilityManagerClient> clientAction) {
- mGlobalClients.broadcast(clientAction);
- userState.mUserClients.broadcast(clientAction);
+ UserState userState, Consumer<Client> clientAction) {
+ mGlobalClients.broadcastForEachCookie(clientAction);
+ userState.mUserClients.broadcastForEachCookie(clientAction);
}
private void unbindAllServicesLocked(UserState userState) {
@@ -2156,6 +2194,7 @@
* permission to write secure settings, since someone with that permission can enable
* accessibility services themselves.
*/
+ @Override
public void performAccessibilityShortcut() {
if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
&& (mContext.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -2445,13 +2484,8 @@
synchronized (mLock) {
userState = getUserStateLocked(userId);
}
- broadcastToClients(userState, (client) -> {
- try {
- client.setRelevantEventTypes(relevantEventTypes);
- } catch (RemoteException re) {
- /* ignore */
- }
- });
+ broadcastToClients(userState, ignoreRemoteException(
+ client -> client.mCallback.setRelevantEventTypes(relevantEventTypes)));
} break;
case MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER: {
@@ -2500,30 +2534,14 @@
private void sendStateToClients(int clientState,
RemoteCallbackList<IAccessibilityManagerClient> clients) {
- clients.broadcast((client) -> {
- try {
- client.setState(clientState);
- } catch (RemoteException re) {
- /* ignore */
- }
- });
+ clients.broadcast(ignoreRemoteException(
+ client -> client.setState(clientState)));
}
private void notifyClientsOfServicesStateChange(
RemoteCallbackList<IAccessibilityManagerClient> clients) {
- try {
- final int userClientCount = clients.beginBroadcast();
- for (int i = 0; i < userClientCount; i++) {
- IAccessibilityManagerClient client = clients.getBroadcastItem(i);
- try {
- client.notifyServicesStateChanged();
- } catch (RemoteException re) {
- /* ignore */
- }
- }
- } finally {
- clients.finishBroadcast();
- }
+ clients.broadcast(ignoreRemoteException(
+ client -> client.notifyServicesStateChanged()));
}
}
@@ -3335,20 +3353,20 @@
}
public boolean canGetAccessibilityNodeInfoLocked(
- AccessibilityClientConnection service, int windowId) {
+ AbstractAccessibilityServiceConnection service, int windowId) {
return canRetrieveWindowContentLocked(service) && isRetrievalAllowingWindow(windowId);
}
- public boolean canRetrieveWindowsLocked(AccessibilityClientConnection service) {
+ public boolean canRetrieveWindowsLocked(AbstractAccessibilityServiceConnection service) {
return canRetrieveWindowContentLocked(service) && service.mRetrieveInteractiveWindows;
}
- public boolean canRetrieveWindowContentLocked(AccessibilityClientConnection service) {
+ public boolean canRetrieveWindowContentLocked(AbstractAccessibilityServiceConnection service) {
return (service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
}
- public boolean canControlMagnification(AccessibilityClientConnection service) {
+ public boolean canControlMagnification(AbstractAccessibilityServiceConnection service) {
return (service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
}
@@ -3445,7 +3463,7 @@
final int windowCount = mWindows.size();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = mWindows.get(i);
- if (window.inPictureInPicture()) {
+ if (window.isInPictureInPictureMode()) {
return window;
}
}
@@ -3476,13 +3494,26 @@
}
}
+ /** Represents an {@link AccessibilityManager} */
+ class Client {
+ final IAccessibilityManagerClient mCallback;
+ final String[] mPackageNames;
+ int mLastSentRelevantEventTypes;
+
+ private Client(IAccessibilityManagerClient callback, int clientUid, UserState userState) {
+ mCallback = callback;
+ mPackageNames = mPackageManager.getPackagesForUid(clientUid);
+ mLastSentRelevantEventTypes = computeRelevantEventTypes(userState, this);
+ }
+ }
+
public class UserState {
public final int mUserId;
// Non-transient state.
public final RemoteCallbackList<IAccessibilityManagerClient> mUserClients =
- new RemoteCallbackList<>();
+ new RemoteCallbackList<>();
public final SparseArray<RemoteAccessibilityConnection> mInteractionConnections =
new SparseArray<>();
@@ -3494,8 +3525,6 @@
public final CopyOnWriteArrayList<AccessibilityServiceConnection> mBoundServices =
new CopyOnWriteArrayList<>();
- public int mLastSentRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
-
public final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
new HashMap<>();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 9cafa1e..5f6efb6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -19,9 +19,7 @@
import static android.provider.Settings.Secure.SHOW_MODE_AUTO;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.IAccessibilityServiceClient;
-import android.accessibilityservice.IAccessibilityServiceConnection;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -39,7 +37,6 @@
import com.android.server.wm.WindowManagerInternal;
import java.lang.ref.WeakReference;
-import java.util.List;
import java.util.Set;
/**
@@ -50,7 +47,7 @@
* passed to the service it represents as soon it is bound. It also serves as the
* connection for the service.
*/
-class AccessibilityServiceConnection extends AccessibilityClientConnection {
+class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
private static final String LOG_TAG = "AccessibilityServiceConnection";
/*
Holding a weak reference so there isn't a loop of references. UserState keeps lists of bound
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 56a9534..ed3b3e7 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -18,6 +18,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.annotation.Nullable;
import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.Context;
@@ -46,7 +47,7 @@
private AccessibilityServiceInfo mUiAutomationServiceInfo;
- private AccessibilityClientConnection.SystemSupport mSystemSupport;
+ private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport;
private int mUiAutomationFlags;
@@ -78,7 +79,7 @@
Context context, AccessibilityServiceInfo accessibilityServiceInfo,
int id, Handler mainHandler, Object lock,
AccessibilityManagerService.SecurityPolicy securityPolicy,
- AccessibilityClientConnection.SystemSupport systemSupport,
+ AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
WindowManagerInternal windowManagerInternal,
GlobalActionPerformer globalActionPerfomer, int flags) {
accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
@@ -157,6 +158,17 @@
return mUiAutomationService.mEventTypes;
}
+ int getRelevantEventTypes() {
+ if (mUiAutomationService == null) return 0;
+ return mUiAutomationService.getRelevantEventTypes();
+ }
+
+ @Nullable
+ AccessibilityServiceInfo getServiceInfo() {
+ if (mUiAutomationService == null) return null;
+ return mUiAutomationService.getServiceInfo();
+ }
+
void dumpUiAutomationService(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (mUiAutomationService != null) {
mUiAutomationService.dump(fd, pw, args);
@@ -176,7 +188,7 @@
mSystemSupport.onClientChange(false);
}
- private class UiAutomationService extends AccessibilityClientConnection {
+ private class UiAutomationService extends AbstractAccessibilityServiceConnection {
private final Handler mMainHandler;
UiAutomationService(Context context, AccessibilityServiceInfo accessibilityServiceInfo,
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index f185443..fbdb183 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -483,6 +483,7 @@
description.currentDestinationString = currentDestinationString;
description.dataManagementIntent = dataManagementIntent;
description.dataManagementLabel = dataManagementLabel;
+ Slog.d(TAG, "Transport " + name + " updated its attributes");
}
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index e9eb3b3..d3ab125 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -34,6 +34,7 @@
import android.net.IpSecTransformResponse;
import android.net.IpSecUdpEncapResponse;
import android.net.NetworkUtils;
+import android.net.TrafficStats;
import android.net.util.NetdService;
import android.os.Binder;
import android.os.IBinder;
@@ -120,6 +121,7 @@
}
private final IpSecServiceConfiguration mSrvConfig;
+ final UidFdTagger mUidFdTagger;
/**
* Interface for user-reference and kernel-resource cleanup.
@@ -762,8 +764,23 @@
/** @hide */
@VisibleForTesting
public IpSecService(Context context, IpSecServiceConfiguration config) {
+ this(context, config, (fd, uid) -> {
+ try{
+ TrafficStats.setThreadStatsUid(uid);
+ TrafficStats.tagFileDescriptor(fd);
+ } finally {
+ TrafficStats.clearThreadStatsUid();
+ }
+ });
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public IpSecService(
+ Context context, IpSecServiceConfiguration config, UidFdTagger uidFdTagger) {
mContext = context;
mSrvConfig = config;
+ mUidFdTagger = uidFdTagger;
}
public void systemReady() {
@@ -925,6 +942,26 @@
}
/**
+ * Functional interface to do traffic tagging of given sockets to UIDs.
+ *
+ * <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap
+ * sockets are billed to the UID that the UDP encap socket was created on behalf of.
+ *
+ * <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static
+ * methods that cannot be easily mocked/tested.
+ */
+ @VisibleForTesting
+ public interface UidFdTagger {
+ /**
+ * Sets socket tag to assign all traffic to the provided UID.
+ *
+ * <p>Since the socket is created on behalf of an unprivileged application, all traffic
+ * should be accounted to the UID of the unprivileged application.
+ */
+ public void tag(FileDescriptor fd, int uid) throws IOException;
+ }
+
+ /**
* Open a socket via the system server and bind it to the specified port (random if port=0).
* This will return a PFD to the user that represent a bound UDP socket. The system server will
* cache the socket and a record of its owner so that it can and must be freed when no longer
@@ -939,7 +976,8 @@
}
checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
- UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+ int callingUid = Binder.getCallingUid();
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
int resourceId = mNextResourceId.getAndIncrement();
FileDescriptor sockFd = null;
try {
@@ -948,13 +986,8 @@
}
sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ mUidFdTagger.tag(sockFd, callingUid);
- if (port != 0) {
- Log.v(TAG, "Binding to port " + port);
- Os.bind(sockFd, INADDR_ANY, port);
- } else {
- port = bindToRandomPort(sockFd);
- }
// This code is common to both the unspecified and specified port cases
Os.setsockoptInt(
sockFd,
@@ -962,6 +995,14 @@
OsConstants.UDP_ENCAP,
OsConstants.UDP_ENCAP_ESPINUDP);
+ mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(sockFd, callingUid);
+ if (port != 0) {
+ Log.v(TAG, "Binding to port " + port);
+ Os.bind(sockFd, INADDR_ANY, port);
+ } else {
+ port = bindToRandomPort(sockFd);
+ }
+
userRecord.mEncapSocketRecords.put(
resourceId,
new RefcountedResource<EncapSocketRecord>(
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 0ffc779..31aea63 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1406,7 +1406,7 @@
mLocalUnlockedUsers.put(userId, true);
}
if (userId < 1) return;
- syncSharedAccounts(userId);
+ mHandler.post(() -> syncSharedAccounts(userId));
}
private void syncSharedAccounts(int userId) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 3cd2f6a..088ddea 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2640,7 +2640,7 @@
try {
bumpServiceExecutingLocked(s, false, "unbind");
if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0
- && s.app.setProcState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+ && s.app.setProcState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
// If this service's process is not already in the cached list,
// then update it in the LRU list here because this may be causing
// it to go down there and we want it to start out near the top.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index eaa7108..7dfde56 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -391,6 +391,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.BinderInternal;
+import com.android.internal.os.ByteTransferPipe;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.TransferPipe;
@@ -14145,8 +14146,7 @@
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
ProcessRecord proc = mLruProcesses.get(i);
if (proc.notCachedSinceIdle) {
- if (proc.setProcState != ActivityManager.PROCESS_STATE_TOP_SLEEPING
- && proc.setProcState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ if (proc.setProcState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
&& proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
if (doKilling && proc.initialIdlePss != 0
&& proc.lastPss > ((proc.initialIdlePss*3)/2)) {
@@ -17241,20 +17241,24 @@
}
}
+ private static void sortMemItems(List<MemItem> items) {
+ Collections.sort(items, new Comparator<MemItem>() {
+ @Override
+ public int compare(MemItem lhs, MemItem rhs) {
+ if (lhs.pss < rhs.pss) {
+ return 1;
+ } else if (lhs.pss > rhs.pss) {
+ return -1;
+ }
+ return 0;
+ }
+ });
+ }
+
static final void dumpMemItems(PrintWriter pw, String prefix, String tag,
ArrayList<MemItem> items, boolean sort, boolean isCompact, boolean dumpSwapPss) {
if (sort && !isCompact) {
- Collections.sort(items, new Comparator<MemItem>() {
- @Override
- public int compare(MemItem lhs, MemItem rhs) {
- if (lhs.pss < rhs.pss) {
- return 1;
- } else if (lhs.pss > rhs.pss) {
- return -1;
- }
- return 0;
- }
- });
+ sortMemItems(items);
}
for (int i=0; i<items.size(); i++) {
@@ -17282,6 +17286,33 @@
}
}
+ static final void dumpMemItems(ProtoOutputStream proto, long fieldId, String tag,
+ ArrayList<MemItem> items, boolean sort, boolean dumpSwapPss) {
+ if (sort) {
+ sortMemItems(items);
+ }
+
+ for (int i=0; i<items.size(); i++) {
+ MemItem mi = items.get(i);
+ final long token = proto.start(fieldId);
+
+ proto.write(MemInfoProto.MemItem.TAG, tag);
+ proto.write(MemInfoProto.MemItem.LABEL, mi.shortLabel);
+ proto.write(MemInfoProto.MemItem.IS_PROC, mi.isProc);
+ proto.write(MemInfoProto.MemItem.ID, mi.id);
+ proto.write(MemInfoProto.MemItem.HAS_ACTIVITIES, mi.hasActivities);
+ proto.write(MemInfoProto.MemItem.PSS_KB, mi.pss);
+ if (dumpSwapPss) {
+ proto.write(MemInfoProto.MemItem.SWAP_PSS_KB, mi.swapPss);
+ }
+ if (mi.subitems != null) {
+ dumpMemItems(proto, MemInfoProto.MemItem.SUB_ITEMS, mi.shortLabel, mi.subitems,
+ true, dumpSwapPss);
+ }
+ proto.end(token);
+ }
+ }
+
// These are in KB.
static final long[] DUMP_MEM_BUCKETS = new long[] {
5*1024, 7*1024, 10*1024, 15*1024, 20*1024, 30*1024, 40*1024, 80*1024,
@@ -17493,7 +17524,7 @@
ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, opts.packages, args);
if (opts.dumpProto) {
- dumpApplicationMemoryUsage(fd, pw, opts, innerArgs, brief, procs);
+ dumpApplicationMemoryUsage(fd, opts, innerArgs, brief, procs);
} else {
dumpApplicationMemoryUsage(fd, pw, prefix, opts, innerArgs, brief, procs, categoryPw);
}
@@ -17983,7 +18014,7 @@
}
}
- private final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw,
+ private final void dumpApplicationMemoryUsage(FileDescriptor fd,
MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief,
ArrayList<ProcessRecord> procs) {
final long uptimeMs = SystemClock.uptimeMillis();
@@ -18025,8 +18056,8 @@
final int pid = r.pid;
final long nToken = proto.start(MemInfoProto.NATIVE_PROCESSES);
- proto.write(MemInfoProto.NativeProcess.PID, pid);
- proto.write(MemInfoProto.NativeProcess.PROCESS_NAME, r.baseName);
+ proto.write(MemInfoProto.ProcessMemory.PID, pid);
+ proto.write(MemInfoProto.ProcessMemory.PROCESS_NAME, r.baseName);
if (mi == null) {
mi = new Debug.MemoryInfo();
@@ -18052,8 +18083,332 @@
return;
}
- // TODO: finish
- pw.println("Java processes aren't implemented yet. Have a coffee instead! :]");
+ if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) {
+ opts.dumpDetails = true;
+ }
+
+ ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ proto.write(MemInfoProto.UPTIME_DURATION_MS, uptimeMs);
+ proto.write(MemInfoProto.ELAPSED_REALTIME_MS, realtimeMs);
+
+ ArrayList<MemItem> procMems = new ArrayList<MemItem>();
+ final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
+ long nativePss = 0;
+ long nativeSwapPss = 0;
+ long dalvikPss = 0;
+ long dalvikSwapPss = 0;
+ long[] dalvikSubitemPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+ EmptyArray.LONG;
+ long[] dalvikSubitemSwapPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+ EmptyArray.LONG;
+ long otherPss = 0;
+ long otherSwapPss = 0;
+ long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
+ long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
+
+ long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length];
+ long oomSwapPss[] = new long[DUMP_MEM_OOM_LABEL.length];
+ ArrayList<MemItem>[] oomProcs = (ArrayList<MemItem>[])
+ new ArrayList[DUMP_MEM_OOM_LABEL.length];
+
+ long totalPss = 0;
+ long totalSwapPss = 0;
+ long cachedPss = 0;
+ long cachedSwapPss = 0;
+ boolean hasSwapPss = false;
+
+ Debug.MemoryInfo mi = null;
+ for (int i = procs.size() - 1 ; i >= 0 ; i--) {
+ final ProcessRecord r = procs.get(i);
+ final IApplicationThread thread;
+ final int pid;
+ final int oomAdj;
+ final boolean hasActivities;
+ synchronized (this) {
+ thread = r.thread;
+ pid = r.pid;
+ oomAdj = r.getSetAdjWithServices();
+ hasActivities = r.activities.size() > 0;
+ }
+ if (thread == null) {
+ continue;
+ }
+ if (mi == null) {
+ mi = new Debug.MemoryInfo();
+ }
+ if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
+ Debug.getMemoryInfo(pid, mi);
+ hasSwapPss = mi.hasSwappedOutPss;
+ } else {
+ mi.dalvikPss = (int) Debug.getPss(pid, tmpLong, null);
+ mi.dalvikPrivateDirty = (int) tmpLong[0];
+ }
+ if (opts.dumpDetails) {
+ if (opts.localOnly) {
+ final long aToken = proto.start(MemInfoProto.APP_PROCESSES);
+ final long mToken = proto.start(MemInfoProto.AppData.PROCESS_MEMORY);
+ proto.write(MemInfoProto.ProcessMemory.PID, pid);
+ proto.write(MemInfoProto.ProcessMemory.PROCESS_NAME, r.processName);
+ ActivityThread.dumpMemInfoTable(proto, mi, opts.dumpDalvik,
+ opts.dumpSummaryOnly, 0, 0, 0, 0, 0, 0);
+ proto.end(mToken);
+ proto.end(aToken);
+ } else {
+ try {
+ ByteTransferPipe tp = new ByteTransferPipe();
+ try {
+ thread.dumpMemInfoProto(tp.getWriteFd(),
+ mi, opts.dumpFullDetails, opts.dumpDalvik, opts.dumpSummaryOnly,
+ opts.dumpUnreachable, innerArgs);
+ proto.write(MemInfoProto.APP_PROCESSES, tp.get());
+ } finally {
+ tp.kill();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Got IOException!", e);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Got RemoteException!", e);
+ }
+ }
+ }
+
+ final long myTotalPss = mi.getTotalPss();
+ final long myTotalUss = mi.getTotalUss();
+ final long myTotalSwapPss = mi.getTotalSwappedOutPss();
+
+ synchronized (this) {
+ if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
+ // Record this for posterity if the process has been stable.
+ r.baseProcessTracker.addPss(myTotalPss, myTotalUss, true, r.pkgList);
+ }
+ }
+
+ if (!opts.isCheckinRequest && mi != null) {
+ totalPss += myTotalPss;
+ totalSwapPss += myTotalSwapPss;
+ MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
+ (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss,
+ myTotalSwapPss, pid, hasActivities);
+ procMems.add(pssItem);
+ procMemsMap.put(pid, pssItem);
+
+ nativePss += mi.nativePss;
+ nativeSwapPss += mi.nativeSwappedOutPss;
+ dalvikPss += mi.dalvikPss;
+ dalvikSwapPss += mi.dalvikSwappedOutPss;
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ dalvikSubitemSwapPss[j] +=
+ mi.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ }
+ otherPss += mi.otherPss;
+ otherSwapPss += mi.otherSwappedOutPss;
+ for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
+ long mem = mi.getOtherPss(j);
+ miscPss[j] += mem;
+ otherPss -= mem;
+ mem = mi.getOtherSwappedOutPss(j);
+ miscSwapPss[j] += mem;
+ otherSwapPss -= mem;
+ }
+
+ if (oomAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+ cachedPss += myTotalPss;
+ cachedSwapPss += myTotalSwapPss;
+ }
+
+ for (int oomIndex=0; oomIndex<oomPss.length; oomIndex++) {
+ if (oomIndex == (oomPss.length - 1)
+ || (oomAdj >= DUMP_MEM_OOM_ADJ[oomIndex]
+ && oomAdj < DUMP_MEM_OOM_ADJ[oomIndex + 1])) {
+ oomPss[oomIndex] += myTotalPss;
+ oomSwapPss[oomIndex] += myTotalSwapPss;
+ if (oomProcs[oomIndex] == null) {
+ oomProcs[oomIndex] = new ArrayList<MemItem>();
+ }
+ oomProcs[oomIndex].add(pssItem);
+ break;
+ }
+ }
+ }
+ }
+
+ long nativeProcTotalPss = 0;
+
+ if (procs.size() > 1 && !opts.packages) {
+ // If we are showing aggregations, also look for native processes to
+ // include so that our aggregations are more accurate.
+ updateCpuStatsNow();
+ mi = null;
+ synchronized (mProcessCpuTracker) {
+ final int N = mProcessCpuTracker.countStats();
+ for (int i=0; i<N; i++) {
+ ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
+ if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) {
+ if (mi == null) {
+ mi = new Debug.MemoryInfo();
+ }
+ if (!brief && !opts.oomOnly) {
+ Debug.getMemoryInfo(st.pid, mi);
+ } else {
+ mi.nativePss = (int)Debug.getPss(st.pid, tmpLong, null);
+ mi.nativePrivateDirty = (int)tmpLong[0];
+ }
+
+ final long myTotalPss = mi.getTotalPss();
+ final long myTotalSwapPss = mi.getTotalSwappedOutPss();
+ totalPss += myTotalPss;
+ nativeProcTotalPss += myTotalPss;
+
+ MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
+ st.name, myTotalPss, mi.getSummaryTotalSwapPss(), st.pid, false);
+ procMems.add(pssItem);
+
+ nativePss += mi.nativePss;
+ nativeSwapPss += mi.nativeSwappedOutPss;
+ dalvikPss += mi.dalvikPss;
+ dalvikSwapPss += mi.dalvikSwappedOutPss;
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ dalvikSubitemSwapPss[j] +=
+ mi.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ }
+ otherPss += mi.otherPss;
+ otherSwapPss += mi.otherSwappedOutPss;
+ for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
+ long mem = mi.getOtherPss(j);
+ miscPss[j] += mem;
+ otherPss -= mem;
+ mem = mi.getOtherSwappedOutPss(j);
+ miscSwapPss[j] += mem;
+ otherSwapPss -= mem;
+ }
+ oomPss[0] += myTotalPss;
+ oomSwapPss[0] += myTotalSwapPss;
+ if (oomProcs[0] == null) {
+ oomProcs[0] = new ArrayList<MemItem>();
+ }
+ oomProcs[0].add(pssItem);
+ }
+ }
+ }
+
+ ArrayList<MemItem> catMems = new ArrayList<MemItem>();
+
+ catMems.add(new MemItem("Native", "Native", nativePss, nativeSwapPss, -1));
+ final int dalvikId = -2;
+ catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, dalvikSwapPss, dalvikId));
+ catMems.add(new MemItem("Unknown", "Unknown", otherPss, otherSwapPss, -3));
+ for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
+ String label = Debug.MemoryInfo.getOtherLabel(j);
+ catMems.add(new MemItem(label, label, miscPss[j], miscSwapPss[j], j));
+ }
+ if (dalvikSubitemPss.length > 0) {
+ // Add dalvik subitems.
+ for (MemItem memItem : catMems) {
+ int memItemStart = 0, memItemEnd = 0;
+ if (memItem.id == dalvikId) {
+ memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_START;
+ memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_END;
+ } else if (memItem.id == Debug.MemoryInfo.OTHER_DALVIK_OTHER) {
+ memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_OTHER_START;
+ memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_OTHER_END;
+ } else if (memItem.id == Debug.MemoryInfo.OTHER_DEX) {
+ memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DEX_START;
+ memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DEX_END;
+ } else if (memItem.id == Debug.MemoryInfo.OTHER_ART) {
+ memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_ART_START;
+ memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_ART_END;
+ } else {
+ continue; // No subitems, continue.
+ }
+ memItem.subitems = new ArrayList<MemItem>();
+ for (int j=memItemStart; j<=memItemEnd; j++) {
+ final String name = Debug.MemoryInfo.getOtherLabel(
+ Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ memItem.subitems.add(new MemItem(name, name, dalvikSubitemPss[j],
+ dalvikSubitemSwapPss[j], j));
+ }
+ }
+ }
+
+ ArrayList<MemItem> oomMems = new ArrayList<MemItem>();
+ for (int j=0; j<oomPss.length; j++) {
+ if (oomPss[j] != 0) {
+ String label = opts.isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j]
+ : DUMP_MEM_OOM_LABEL[j];
+ MemItem item = new MemItem(label, label, oomPss[j], oomSwapPss[j],
+ DUMP_MEM_OOM_ADJ[j]);
+ item.subitems = oomProcs[j];
+ oomMems.add(item);
+ }
+ }
+
+ opts.dumpSwapPss = opts.dumpSwapPss && hasSwapPss && totalSwapPss != 0;
+ if (!opts.oomOnly) {
+ dumpMemItems(proto, MemInfoProto.TOTAL_PSS_BY_PROCESS, "proc",
+ procMems, true, opts.dumpSwapPss);
+ }
+ dumpMemItems(proto, MemInfoProto.TOTAL_PSS_BY_OOM_ADJUSTMENT, "oom",
+ oomMems, false, opts.dumpSwapPss);
+ if (!brief && !opts.oomOnly) {
+ dumpMemItems(proto, MemInfoProto.TOTAL_PSS_BY_CATEGORY, "cat",
+ catMems, true, opts.dumpSwapPss);
+ }
+ MemInfoReader memInfo = new MemInfoReader();
+ memInfo.readMemInfo();
+ if (nativeProcTotalPss > 0) {
+ synchronized (this) {
+ final long cachedKb = memInfo.getCachedSizeKb();
+ final long freeKb = memInfo.getFreeSizeKb();
+ final long zramKb = memInfo.getZramTotalSizeKb();
+ final long kernelKb = memInfo.getKernelUsedSizeKb();
+ EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024,
+ kernelKb*1024, nativeProcTotalPss*1024);
+ mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb,
+ nativeProcTotalPss);
+ }
+ }
+ if (!brief) {
+ proto.write(MemInfoProto.TOTAL_RAM_KB, memInfo.getTotalSizeKb());
+ proto.write(MemInfoProto.STATUS, mLastMemoryLevel);
+ proto.write(MemInfoProto.CACHED_PSS_KB, cachedPss);
+ proto.write(MemInfoProto.CACHED_KERNEL_KB, memInfo.getCachedSizeKb());
+ proto.write(MemInfoProto.FREE_KB, memInfo.getFreeSizeKb());
+ }
+ long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
+ - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
+ - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb();
+ proto.write(MemInfoProto.USED_PSS_KB, totalPss - cachedPss);
+ proto.write(MemInfoProto.USED_KERNEL_KB, memInfo.getKernelUsedSizeKb());
+ proto.write(MemInfoProto.LOST_RAM_KB, lostRAM);
+ if (!brief) {
+ if (memInfo.getZramTotalSizeKb() != 0) {
+ proto.write(MemInfoProto.TOTAL_ZRAM_KB, memInfo.getZramTotalSizeKb());
+ proto.write(MemInfoProto.ZRAM_PHYSICAL_USED_IN_SWAP_KB,
+ memInfo.getSwapTotalSizeKb() - memInfo.getSwapFreeSizeKb());
+ proto.write(MemInfoProto.TOTAL_ZRAM_SWAP_KB, memInfo.getSwapTotalSizeKb());
+ }
+ final long[] ksm = getKsmInfo();
+ proto.write(MemInfoProto.KSM_SHARING_KB, ksm[KSM_SHARING]);
+ proto.write(MemInfoProto.KSM_SHARED_KB, ksm[KSM_SHARED]);
+ proto.write(MemInfoProto.KSM_UNSHARED_KB, ksm[KSM_UNSHARED]);
+ proto.write(MemInfoProto.KSM_VOLATILE_KB, ksm[KSM_VOLATILE]);
+
+ proto.write(MemInfoProto.TUNING_MB, ActivityManager.staticGetMemoryClass());
+ proto.write(MemInfoProto.TUNING_LARGE_MB, ActivityManager.staticGetLargeMemoryClass());
+ proto.write(MemInfoProto.OOM_KB,
+ mProcessList.getMemLevel(ProcessList.CACHED_APP_MAX_ADJ) / 1024);
+ proto.write(MemInfoProto.RESTORE_LIMIT_KB,
+ mProcessList.getCachedRestoreThresholdKb());
+
+ proto.write(MemInfoProto.IS_LOW_RAM_DEVICE, ActivityManager.isLowRamDeviceStatic());
+ proto.write(MemInfoProto.IS_HIGH_END_GFX, ActivityManager.isHighEndGfx());
+ }
+ }
+
+ proto.flush();
}
private void appendBasicMemEntry(StringBuilder sb, int oomAdj, int procState, long pss,
@@ -21336,7 +21691,7 @@
int procState;
boolean foregroundActivities = false;
mTmpBroadcastQueue.clear();
- if (app == TOP_APP) {
+ if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {
// The last app on the list is the foreground app.
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
@@ -21372,6 +21727,13 @@
procState = ActivityManager.PROCESS_STATE_SERVICE;
if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Making exec-service: " + app);
//Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
+ } else if (app == TOP_APP) {
+ adj = ProcessList.FOREGROUND_APP_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ app.adjType = "top-sleeping";
+ foregroundActivities = true;
+ procState = PROCESS_STATE_CUR_TOP;
+ if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Making top: " + app);
} else {
// As far as we know the process is empty. We may change our mind later.
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
@@ -22820,7 +23182,7 @@
if (app.curProcState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
isInteraction = true;
app.fgInteractionTime = 0;
- } else if (app.curProcState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+ } else if (app.curProcState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
if (app.fgInteractionTime == 0) {
app.fgInteractionTime = nowElapsed;
isInteraction = false;
@@ -23254,7 +23616,8 @@
break;
}
}
- } else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+ } else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+ && !app.killedByAm) {
if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
&& app.thread != null) {
try {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index eb1636d..ab5d64c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -364,9 +364,6 @@
case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
procState = "FGS ";
break;
- case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
- procState = "TPSL";
- break;
case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
procState = "IMPF";
break;
@@ -379,15 +376,18 @@
case ActivityManager.PROCESS_STATE_BACKUP:
procState = "BKUP";
break;
- case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT:
- procState = "HVY ";
- break;
case ActivityManager.PROCESS_STATE_SERVICE:
procState = "SVC ";
break;
case ActivityManager.PROCESS_STATE_RECEIVER:
procState = "RCVR";
break;
+ case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
+ procState = "TPSL";
+ break;
+ case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT:
+ procState = "HVY ";
+ break;
case ActivityManager.PROCESS_STATE_HOME:
procState = "HOME";
break;
@@ -485,14 +485,14 @@
PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BACKUP
- PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PROC_MEM_SERVICE, // ActivityManager.PROCESS_STATE_SERVICE
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_RECEIVER
+ PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+ PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_HOME
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -507,14 +507,14 @@
PSS_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
- PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER
+ PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+ PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -529,14 +529,14 @@
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
- PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE
PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER
+ PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+ PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -545,20 +545,20 @@
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
- private static final long[] sTestFirstAwakePssTimes = new long[] {
+ private static final long[] sTestFirstPssTimes = new long[] {
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
- PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
- PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+ PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+ PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
- PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER
+ PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+ PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HOME
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -567,20 +567,20 @@
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
- private static final long[] sTestSameAwakePssTimes = new long[] {
+ private static final long[] sTestSamePssTimes = new long[] {
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
- PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER
+ PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+ PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HOME
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -601,8 +601,8 @@
boolean sleeping, long now) {
final long[] table = test
? (first
- ? sTestFirstAwakePssTimes
- : sTestSameAwakePssTimes)
+ ? sTestFirstPssTimes
+ : sTestSamePssTimes)
: (first
? sFirstAwakePssTimes
: sSameAwakePssTimes);
diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java
index 41d9feb..9640e04 100644
--- a/services/core/java/com/android/server/location/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java
@@ -179,7 +179,7 @@
}
/**
- * Handles a nanoapp load event.
+ * Notifies the client of a nanoapp load event if the connection is open.
*
* @param nanoAppId the ID of the nanoapp that was loaded.
*/
@@ -195,7 +195,7 @@
}
/**
- * Handles a nanoapp unload event.
+ * Notifies the client of a nanoapp unload event if the connection is open.
*
* @param nanoAppId the ID of the nanoapp that was unloaded.
*/
@@ -211,7 +211,7 @@
}
/**
- * Handles a hub reset for this client.
+ * Notifies the client of a hub reset event if the connection is open.
*/
/* package */ void onHubReset() {
if (mConnectionOpen.get()) {
@@ -223,4 +223,21 @@
}
}
}
+
+ /**
+ * Notifies the client of a nanoapp abort event if the connection is open.
+ *
+ * @param nanoAppId the ID of the nanoapp that aborted
+ * @param abortCode the nanoapp specific abort code
+ */
+ /* package */ void onNanoAppAborted(long nanoAppId, int abortCode) {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onNanoAppAborted(nanoAppId, abortCode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onNanoAppAborted on client"
+ + " (host endpoint ID = " + mHostEndPointId + ")", e);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/ContextHubClientManager.java b/services/core/java/com/android/server/location/ContextHubClientManager.java
index d58a746..60b5b1f 100644
--- a/services/core/java/com/android/server/location/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/ContextHubClientManager.java
@@ -149,35 +149,38 @@
}
/**
- * Handles a nanoapp load event.
- *
- * @param contextHubId the ID of the hub where the nanoapp was loaded.
- * @param nanoAppId the ID of the nanoapp that was loaded.
+ * @param contextHubId the ID of the hub where the nanoapp was loaded
+ * @param nanoAppId the ID of the nanoapp that was loaded
*/
/* package */ void onNanoAppLoaded(int contextHubId, long nanoAppId) {
forEachClientOfHub(contextHubId, client -> client.onNanoAppLoaded(nanoAppId));
}
/**
- * Handles a nanoapp unload event.
- *
- * @param contextHubId the ID of the hub where the nanoapp was unloaded.
- * @param nanoAppId the ID of the nanoapp that was unloaded.
+ * @param contextHubId the ID of the hub where the nanoapp was unloaded
+ * @param nanoAppId the ID of the nanoapp that was unloaded
*/
/* package */ void onNanoAppUnloaded(int contextHubId, long nanoAppId) {
forEachClientOfHub(contextHubId, client -> client.onNanoAppUnloaded(nanoAppId));
}
/**
- * Handles a hub reset.
- *
- * @param contextHubId the ID of the hub that has reset.
+ * @param contextHubId the ID of the hub that has reset
*/
/* package */ void onHubReset(int contextHubId) {
forEachClientOfHub(contextHubId, client -> client.onHubReset());
}
/**
+ * @param contextHubId the ID of the hub that contained the nanoapp that aborted
+ * @param nanoAppId the ID of the nanoapp that aborted
+ * @param abortCode the nanoapp specific abort code
+ */
+ /* package */ void onNanoAppAborted(int contextHubId, long nanoAppId, int abortCode) {
+ forEachClientOfHub(contextHubId, client -> client.onNanoAppAborted(nanoAppId, abortCode));
+ }
+
+ /**
* Creates a new ContextHubClientBroker object for a client and registers it with the client
* manager.
*
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index b5fc9d0..26edf62 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -79,7 +79,8 @@
private final Context mContext;
- private final ContextHubInfo[] mContextHubInfo;
+ private final Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
+ private final List<ContextHubInfo> mContextHubInfoList;
private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
new RemoteCallbackList<>();
@@ -141,8 +142,9 @@
if (mContextHubProxy == null) {
mTransactionManager = null;
mClientManager = null;
- mDefaultClientMap = Collections.EMPTY_MAP;
- mContextHubInfo = new ContextHubInfo[0];
+ mDefaultClientMap = Collections.emptyMap();
+ mContextHubIdToInfoMap = Collections.emptyMap();
+ mContextHubInfoList = Collections.emptyList();
return;
}
@@ -157,20 +159,16 @@
Log.e(TAG, "RemoteException while getting Context Hub info", e);
hubList = Collections.emptyList();
}
- mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList);
+ mContextHubIdToInfoMap = Collections.unmodifiableMap(
+ ContextHubServiceUtil.createContextHubInfoMap(hubList));
+ mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
- for (ContextHubInfo contextHubInfo : mContextHubInfo) {
- int contextHubId = contextHubInfo.getId();
-
+ for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
IContextHubClient client = mClientManager.registerClient(
createDefaultClientCallback(contextHubId), contextHubId);
defaultClientMap.put(contextHubId, client);
- }
- mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
- for (ContextHubInfo contextHubInfo : mContextHubInfo) {
- int contextHubId = contextHubInfo.getId();
try {
mContextHubProxy.registerCallback(
contextHubId, new ContextHubServiceCallback(contextHubId));
@@ -178,18 +176,12 @@
Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
+ contextHubId + ")", e);
}
- }
- // Do a query to initialize the service cache list of nanoapps
- // TODO(b/69270990): Remove this when old API is deprecated
- for (ContextHubInfo contextHubInfo : mContextHubInfo) {
- queryNanoAppsInternal(contextHubInfo.getId());
+ // Do a query to initialize the service cache list of nanoapps
+ // TODO(b/69270990): Remove this when old API is deprecated
+ queryNanoAppsInternal(contextHubId);
}
-
- for (int i = 0; i < mContextHubInfo.length; i++) {
- Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId()
- + ", name: " + mContextHubInfo[i].getName());
- }
+ mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
}
/**
@@ -267,23 +259,29 @@
@Override
public int[] getContextHubHandles() throws RemoteException {
checkPermissions();
- int[] returnArray = new int[mContextHubInfo.length];
-
- for (int i = 0; i < returnArray.length; ++i) {
- returnArray[i] = i;
- }
- return returnArray;
+ return ContextHubServiceUtil.createPrimitiveIntArray(mContextHubIdToInfoMap.keySet());
}
@Override
- public ContextHubInfo getContextHubInfo(int contextHubId) throws RemoteException {
+ public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
checkPermissions();
- if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
- Log.e(TAG, "Invalid context hub handle " + contextHubId);
- return null; // null means fail
+ if (!mContextHubIdToInfoMap.containsKey(contextHubHandle)) {
+ Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in getContextHubInfo");
+ return null;
}
- return mContextHubInfo[contextHubId];
+ return mContextHubIdToInfoMap.get(contextHubHandle);
+ }
+
+ /**
+ * Returns a List of ContextHubInfo object describing the available hubs.
+ *
+ * @return the List of ContextHubInfo objects
+ */
+ @Override
+ public List<ContextHubInfo> getContextHubs() throws RemoteException {
+ checkPermissions();
+ return mContextHubInfoList;
}
/**
@@ -347,28 +345,27 @@
}
@Override
- public int loadNanoApp(int contextHubId, NanoApp app) throws RemoteException {
+ public int loadNanoApp(int contextHubHandle, NanoApp nanoApp) throws RemoteException {
checkPermissions();
if (mContextHubProxy == null) {
return -1;
}
-
- if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
- Log.e(TAG, "Invalid contextHubhandle " + contextHubId);
+ if (!isValidContextHubId(contextHubHandle)) {
+ Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in loadNanoApp");
return -1;
}
- if (app == null) {
- Log.e(TAG, "Invalid null app");
+ if (nanoApp == null) {
+ Log.e(TAG, "NanoApp cannot be null in loadNanoApp");
return -1;
}
// Create an internal IContextHubTransactionCallback for the old API clients
- NanoAppBinary nanoAppBinary = new NanoAppBinary(app.getAppBinary());
+ NanoAppBinary nanoAppBinary = new NanoAppBinary(nanoApp.getAppBinary());
IContextHubTransactionCallback onCompleteCallback =
- createLoadTransactionCallback(contextHubId, nanoAppBinary);
+ createLoadTransactionCallback(contextHubHandle, nanoAppBinary);
ContextHubServiceTransaction transaction = mTransactionManager.createLoadTransaction(
- contextHubId, nanoAppBinary, onCompleteCallback);
+ contextHubHandle, nanoAppBinary, onCompleteCallback);
mTransactionManager.addTransaction(transaction);
return 0;
@@ -384,7 +381,7 @@
NanoAppInstanceInfo info =
mNanoAppStateManager.getNanoAppInstanceInfo(nanoAppHandle);
if (info == null) {
- Log.e(TAG, "Cannot find nanoapp with handle " + nanoAppHandle);
+ Log.e(TAG, "Invalid nanoapp handle " + nanoAppHandle + " in unloadNanoApp");
return -1;
}
@@ -407,7 +404,8 @@
}
@Override
- public int[] findNanoAppOnHub(int hubHandle, NanoAppFilter filter) throws RemoteException {
+ public int[] findNanoAppOnHub(
+ int contextHubHandle, NanoAppFilter filter) throws RemoteException {
checkPermissions();
ArrayList<Integer> foundInstances = new ArrayList<>();
@@ -450,29 +448,29 @@
}
@Override
- public int sendMessage(
- int hubHandle, int nanoAppHandle, ContextHubMessage msg) throws RemoteException {
+ public int sendMessage(int contextHubHandle, int nanoAppHandle, ContextHubMessage msg)
+ throws RemoteException {
checkPermissions();
if (mContextHubProxy == null) {
return -1;
}
if (msg == null) {
- Log.e(TAG, "ContextHubMessage cannot be null");
+ Log.e(TAG, "ContextHubMessage cannot be null in sendMessage");
return -1;
}
if (msg.getData() == null) {
- Log.e(TAG, "ContextHubMessage message body cannot be null");
+ Log.e(TAG, "ContextHubMessage message body cannot be null in sendMessage");
return -1;
}
- if (!mDefaultClientMap.containsKey(hubHandle)) {
- Log.e(TAG, "Hub with ID " + hubHandle + " does not exist");
+ if (!isValidContextHubId(contextHubHandle)) {
+ Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in sendMessage");
return -1;
}
boolean success = false;
if (nanoAppHandle == OS_APP_INSTANCE) {
if (msg.getMsgType() == MSG_QUERY_NANO_APPS) {
- success = (queryNanoAppsInternal(hubHandle) == Result.OK);
+ success = (queryNanoAppsInternal(contextHubHandle) == Result.OK);
} else {
Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType());
}
@@ -482,7 +480,7 @@
NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
info.getAppId(), msg.getMsgType(), msg.getData());
- IContextHubClient client = mDefaultClientMap.get(hubHandle);
+ IContextHubClient client = mDefaultClientMap.get(contextHubHandle);
success = (client.sendMessageToNanoApp(message) ==
ContextHubTransaction.TRANSACTION_SUCCESS);
} else {
@@ -573,7 +571,7 @@
* @param abortCode the nanoapp-specific abort code
*/
private void handleAppAbortCallback(int contextHubId, long nanoAppId, int abortCode) {
- // TODO(b/31049861): Implement this
+ mClientManager.onNanoAppAborted(contextHubId, nanoAppId, abortCode);
}
/**
@@ -595,13 +593,7 @@
* @return {@code true} if the ID represents that of an available hub, {@code false} otherwise
*/
private boolean isValidContextHubId(int contextHubId) {
- for (ContextHubInfo hubInfo : mContextHubInfo) {
- if (hubInfo.getId() == contextHubId) {
- return true;
- }
- }
-
- return false;
+ return mContextHubIdToInfoMap.containsKey(contextHubId);
}
/**
@@ -762,8 +754,8 @@
pw.println("");
// dump ContextHubInfo
pw.println("=================== CONTEXT HUBS ====================");
- for (int i = 0; i < mContextHubInfo.length; i++) {
- pw.println("Handle " + i + " : " + mContextHubInfo[i].toString());
+ for (ContextHubInfo hubInfo : mContextHubIdToInfoMap.values()) {
+ pw.println(hubInfo);
}
pw.println("");
pw.println("=================== NANOAPPS ====================");
@@ -779,7 +771,8 @@
ContextHubServiceUtil.checkPermissions(mContext);
}
- private int onMessageReceiptOldApi(int msgType, int hubHandle, int appInstance, byte[] data) {
+ private int onMessageReceiptOldApi(
+ int msgType, int contextHubHandle, int appInstance, byte[] data) {
if (data == null) {
return -1;
}
@@ -787,7 +780,8 @@
int msgVersion = 0;
int callbacksCount = mCallbacksList.beginBroadcast();
Log.d(TAG, "Sending message " + msgType + " version " + msgVersion + " from hubHandle " +
- hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
+ contextHubHandle + ", appInstance " + appInstance + ", callBackCount "
+ + callbacksCount);
if (callbacksCount < 1) {
Log.v(TAG, "No message callbacks registered.");
@@ -798,7 +792,7 @@
for (int i = 0; i < callbacksCount; ++i) {
IContextHubCallback callback = mCallbacksList.getBroadcastItem(i);
try {
- callback.onMessageReceipt(hubHandle, appInstance, msg);
+ callback.onMessageReceipt(contextHubHandle, appInstance, msg);
} catch (RemoteException e) {
Log.i(TAG, "Exception (" + e + ") calling remote callback (" + callback + ").");
continue;
diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
index 6faeb72..7a57dd3 100644
--- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -30,6 +30,9 @@
import android.hardware.location.NanoAppState;
import android.util.Log;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
@@ -43,19 +46,20 @@
+ HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
/**
- * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects.
+ * Creates a ConcurrentHashMap of the Context Hub ID to the ContextHubInfo object given an
+ * ArrayList of HIDL ContextHub objects.
*
* @param hubList the ContextHub ArrayList
- * @return the ContextHubInfo array
+ * @return the HashMap object
*/
/* package */
- static ContextHubInfo[] createContextHubInfoArray(List<ContextHub> hubList) {
- ContextHubInfo[] contextHubInfoList = new ContextHubInfo[hubList.size()];
- for (int i = 0; i < hubList.size(); i++) {
- contextHubInfoList[i] = new ContextHubInfo(hubList.get(i));
+ static HashMap<Integer, ContextHubInfo> createContextHubInfoMap(List<ContextHub> hubList) {
+ HashMap<Integer, ContextHubInfo> contextHubIdToInfoMap = new HashMap<>();
+ for (ContextHub contextHub : hubList) {
+ contextHubIdToInfoMap.put(contextHub.hubId, new ContextHubInfo(contextHub));
}
- return contextHubInfoList;
+ return contextHubIdToInfoMap;
}
/**
@@ -90,6 +94,22 @@
}
/**
+ * Creates a primitive integer array given a Collection<Integer>.
+ * @param collection the collection to iterate
+ * @return the primitive integer array
+ */
+ static int[] createPrimitiveIntArray(Collection<Integer> collection) {
+ int[] primitiveArray = new int[collection.size()];
+
+ int i = 0;
+ for (int contextHubId : collection) {
+ primitiveArray[i++] = contextHubId;
+ }
+
+ return primitiveArray;
+ }
+
+ /**
* Generates the Context Hub HAL's NanoAppBinary object from the client-facing
* android.hardware.location.NanoAppBinary object.
*
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 60f451a..aa55930 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -28,6 +28,8 @@
import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.IActivityManager;
@@ -60,6 +62,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.ShellCallback;
import android.os.StrictMode;
import android.os.SystemProperties;
@@ -75,6 +78,10 @@
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore.UserNotAuthenticatedException;
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader.RecoverableKeyStoreLoaderException;
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
@@ -95,9 +102,10 @@
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.SystemService;
import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
-import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import libcore.util.HexEncoding;
@@ -169,6 +177,8 @@
private final KeyStore mKeyStore;
+ private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+
private boolean mFirstCallToVold;
protected IGateKeeperService mGateKeeperService;
@@ -367,6 +377,10 @@
return KeyStore.getInstance();
}
+ public RecoverableKeyStoreManager getRecoverableKeyStoreManager() {
+ return RecoverableKeyStoreManager.getInstance(mContext);
+ }
+
public IStorageManager getStorageManager() {
final IBinder service = ServiceManager.getService("mount");
if (service != null) {
@@ -393,6 +407,7 @@
mInjector = injector;
mContext = injector.getContext();
mKeyStore = injector.getKeyStore();
+ mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
mHandler = injector.getHandler();
mStrongAuth = injector.getStrongAuth();
mActivityManager = injector.getActivityManager();
@@ -1914,6 +1929,72 @@
}
}
+ @Override
+ public void initRecoveryService(@NonNull String rootCertificateAlias,
+ @NonNull byte[] signedPublicKeyList, int userId)
+ throws RemoteException {
+ mRecoverableKeyStoreManager.initRecoveryService(rootCertificateAlias,
+ signedPublicKeyList, userId);
+ }
+
+ @Override
+ public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
+ throws RemoteException {
+ return mRecoverableKeyStoreManager.getRecoveryData(account, userId);
+ }
+
+ @Override
+ public void setServerParameters(long serverParameters, int userId) throws RemoteException {
+ mRecoverableKeyStoreManager.setServerParameters(serverParameters, userId);
+ }
+
+ @Override
+ public void setRecoveryStatus(@NonNull String packageName, @Nullable String[] aliases,
+ int status, int userId) throws RemoteException {
+ mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId);
+ }
+
+ @Override
+ public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
+ int[] secretTypes, int userId) throws RemoteException {
+ mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes, userId);
+ }
+
+ @Override
+ public int[] getRecoverySecretTypes(int userId) throws RemoteException {
+ return mRecoverableKeyStoreManager.getRecoverySecretTypes(userId);
+
+ }
+
+ @Override
+ public int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
+ throw new SecurityException("Not implemented");
+ }
+
+ @Override
+ public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret,
+ int userId)
+ throws RemoteException {
+ mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret, userId);
+ }
+
+ @Override
+ public byte[] startRecoverySession(@NonNull String sessionId,
+ @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams,
+ @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets,
+ int userId) throws RemoteException {
+ return mRecoverableKeyStoreManager.startRecoverySession(sessionId, verifierPublicKey,
+ vaultParams, vaultChallenge, secrets, userId);
+ }
+
+ @Override
+ public void recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob,
+ @NonNull List<KeyEntryRecoveryData> applicationKeys, int userId)
+ throws RemoteException {
+ mRecoverableKeyStoreManager.recoverKeys(sessionId, recoveryKeyBlob, applicationKeys,
+ userId);
+ }
+
private static final String[] VALID_SETTINGS = new String[] {
LockPatternUtils.LOCKOUT_PERMANENT_KEY,
LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java
new file mode 100644
index 0000000..9a4d051
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import android.security.keystore.AndroidKeyStoreProvider;
+
+import java.security.KeyStoreException;
+import java.security.NoSuchProviderException;
+
+public interface AndroidKeyStoreFactory {
+ KeyStoreProxy getKeyStoreForUid(int uid) throws KeyStoreException, NoSuchProviderException;
+
+ class Impl implements AndroidKeyStoreFactory {
+ @Override
+ public KeyStoreProxy getKeyStoreForUid(int uid)
+ throws KeyStoreException, NoSuchProviderException {
+ return new KeyStoreProxyImpl(AndroidKeyStoreProvider.getKeyStoreForUid(uid));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/InsecureUserException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/InsecureUserException.java
new file mode 100644
index 0000000..5155a99
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/InsecureUserException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+/**
+ * Error thrown initializing {@link PlatformKeyManager} if the user is not secure (i.e., has no
+ * lock screen set).
+ */
+public class InsecureUserException extends Exception {
+
+ /**
+ * A new instance with {@code message} error message.
+ */
+ public InsecureUserException(String message) {
+ super(message);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
new file mode 100644
index 0000000..8103177
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+
+/**
+ * Proxies {@link java.security.KeyStore}. As all of its methods are final, it cannot otherwise be
+ * mocked for tests.
+ *
+ * @hide
+ */
+public interface KeyStoreProxy {
+
+ /** @see KeyStore#containsAlias(String) */
+ boolean containsAlias(String alias) throws KeyStoreException;
+
+ /** @see KeyStore#getKey(String, char[]) */
+ Key getKey(String alias, char[] password)
+ throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException;
+
+ /** @see KeyStore#setEntry(String, KeyStore.Entry, KeyStore.ProtectionParameter) */
+ void setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam)
+ throws KeyStoreException;
+
+ /** @see KeyStore#deleteEntry(String) */
+ void deleteEntry(String alias) throws KeyStoreException;
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
new file mode 100644
index 0000000..59132da
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+
+/**
+ * Implementation of {@link KeyStoreProxy} that delegates all method calls to the {@link KeyStore}.
+ */
+public class KeyStoreProxyImpl implements KeyStoreProxy {
+
+ private final KeyStore mKeyStore;
+
+ /**
+ * A new instance, delegating to {@code keyStore}.
+ */
+ public KeyStoreProxyImpl(KeyStore keyStore) {
+ mKeyStore = keyStore;
+ }
+
+ @Override
+ public boolean containsAlias(String alias) throws KeyStoreException {
+ return mKeyStore.containsAlias(alias);
+ }
+
+ @Override
+ public Key getKey(String alias, char[] password)
+ throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
+ return mKeyStore.getKey(alias, password);
+ }
+
+ @Override
+ public void setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam)
+ throws KeyStoreException {
+ mKeyStore.setEntry(alias, entry, protParam);
+ }
+
+ @Override
+ public void deleteEntry(String alias) throws KeyStoreException {
+ mKeyStore.deleteEntry(alias);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java
new file mode 100644
index 0000000..38f5b45
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import android.security.keystore.AndroidKeyStoreSecretKey;
+
+/**
+ * Private key stored in AndroidKeyStore. Used to wrap recoverable keys before writing them to disk.
+ *
+ * <p>Identified by a generation ID, which increments whenever a new platform key is generated. A
+ * new key must be generated whenever the user disables their lock screen, as the decryption key is
+ * tied to lock-screen authentication.
+ *
+ * <p>One current platform key exists per profile on the device. (As each must be tied to a
+ * different user's lock screen.)
+ *
+ * @hide
+ */
+public class PlatformEncryptionKey {
+
+ private final int mGenerationId;
+ private final AndroidKeyStoreSecretKey mKey;
+
+ /**
+ * A new instance.
+ *
+ * @param generationId The generation ID of the key.
+ * @param key The secret key handle. Can be used to encrypt WITHOUT requiring screen unlock.
+ */
+ public PlatformEncryptionKey(int generationId, AndroidKeyStoreSecretKey key) {
+ mGenerationId = generationId;
+ mKey = key;
+ }
+
+ /**
+ * Returns the generation ID of the key.
+ */
+ public int getGenerationId() {
+ return mGenerationId;
+ }
+
+ /**
+ * Returns the actual key, which can only be used to encrypt.
+ */
+ public AndroidKeyStoreSecretKey getKey() {
+ return mKey;
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
new file mode 100644
index 0000000..24f3f65
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.security.keystore.AndroidKeyStoreSecretKey;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Locale;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.security.auth.DestroyFailedException;
+
+/**
+ * Manages creating and checking the validity of the platform key.
+ *
+ * <p>The platform key is used to wrap the material of recoverable keys before persisting them to
+ * disk. It is also used to decrypt the same keys on a screen unlock, before re-wrapping them with
+ * a recovery key and syncing them with remote storage.
+ *
+ * <p>Each platform key has two entries in AndroidKeyStore:
+ *
+ * <ul>
+ * <li>Encrypt entry - this entry enables the root user to at any time encrypt.
+ * <li>Decrypt entry - this entry enables the root user to decrypt only after recent user
+ * authentication, i.e., within 15 seconds after a screen unlock.
+ * </ul>
+ *
+ * <p>Both entries are enabled only for AES/GCM/NoPadding Cipher algorithm.
+ *
+ * @hide
+ */
+public class PlatformKeyManager {
+ private static final String TAG = "PlatformKeyManager";
+
+ private static final String KEY_ALGORITHM = "AES";
+ private static final int KEY_SIZE_BITS = 256;
+ private static final String KEY_ALIAS_PREFIX =
+ "com.android.server.locksettings.recoverablekeystore/platform/";
+ private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt";
+ private static final String DECRYPT_KEY_ALIAS_SUFFIX = "decrypt";
+ private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
+
+ private final Context mContext;
+ private final KeyStoreProxy mKeyStore;
+ private final RecoverableKeyStoreDb mDatabase;
+ private final int mUserId;
+
+ private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
+
+ /**
+ * A new instance operating on behalf of {@code userId}, storing its prefs in the location
+ * defined by {@code context}.
+ *
+ * @param context This should be the context of the RecoverableKeyStoreLoader service.
+ * @param userId The ID of the user to whose lock screen the platform key must be bound.
+ * @throws KeyStoreException if failed to initialize AndroidKeyStore.
+ * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
+ * @throws InsecureUserException if the user does not have a lock screen set.
+ * @throws SecurityException if the caller does not have permission to write to /data/system.
+ *
+ * @hide
+ */
+ public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database, int userId)
+ throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
+ context = context.getApplicationContext();
+ PlatformKeyManager keyManager = new PlatformKeyManager(
+ userId,
+ context,
+ new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()),
+ database);
+ keyManager.init();
+ return keyManager;
+ }
+
+ @VisibleForTesting
+ PlatformKeyManager(
+ int userId,
+ Context context,
+ KeyStoreProxy keyStore,
+ RecoverableKeyStoreDb database) {
+ mUserId = userId;
+ mKeyStore = keyStore;
+ mContext = context;
+ mDatabase = database;
+ }
+
+ /**
+ * Returns the current generation ID of the platform key. This increments whenever a platform
+ * key has to be replaced. (e.g., because the user has removed and then re-added their lock
+ * screen).
+ *
+ * @hide
+ */
+ public int getGenerationId() {
+ int generationId = mDatabase.getPlatformKeyGenerationId(mUserId);
+ if (generationId == -1) {
+ return 1;
+ }
+ return generationId;
+ }
+
+ /**
+ * Returns {@code true} if the platform key is available. A platform key won't be available if
+ * the user has not set up a lock screen.
+ *
+ * @hide
+ */
+ public boolean isAvailable() {
+ return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mUserId);
+ }
+
+ /**
+ * Generates a new key and increments the generation ID. Should be invoked if the platform key
+ * is corrupted and needs to be rotated.
+ *
+ * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
+ * @throws KeyStoreException if there is an error in AndroidKeyStore.
+ *
+ * @hide
+ */
+ public void regenerate() throws NoSuchAlgorithmException, KeyStoreException {
+ int nextId = getGenerationId() + 1;
+ generateAndLoadKey(nextId);
+ setGenerationId(nextId);
+ }
+
+ /**
+ * Returns the platform key used for encryption.
+ *
+ * @throws KeyStoreException if there was an AndroidKeyStore error.
+ * @throws UnrecoverableKeyException if the key could not be recovered.
+ * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
+ *
+ * @hide
+ */
+ public PlatformEncryptionKey getEncryptKey()
+ throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
+ int generationId = getGenerationId();
+ AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
+ getEncryptAlias(generationId), /*password=*/ null);
+ return new PlatformEncryptionKey(generationId, key);
+ }
+
+ /**
+ * Returns the platform key used for decryption. Only works after a recent screen unlock.
+ *
+ * @throws KeyStoreException if there was an AndroidKeyStore error.
+ * @throws UnrecoverableKeyException if the key could not be recovered.
+ * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
+ *
+ * @hide
+ */
+ public PlatformDecryptionKey getDecryptKey()
+ throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
+ int generationId = getGenerationId();
+ AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
+ getDecryptAlias(generationId), /*password=*/ null);
+ return new PlatformDecryptionKey(generationId, key);
+ }
+
+ /**
+ * Initializes the class. If there is no current platform key, and the user has a lock screen
+ * set, will create the platform key and set the generation ID.
+ *
+ * @throws KeyStoreException if there was an error in AndroidKeyStore.
+ * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
+ *
+ * @hide
+ */
+ public void init() throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
+ if (!isAvailable()) {
+ throw new InsecureUserException(String.format(
+ Locale.US, "%d does not have a lock screen set.", mUserId));
+ }
+
+ int generationId = getGenerationId();
+ if (isKeyLoaded(generationId)) {
+ Log.i(TAG, String.format(
+ Locale.US, "Platform key generation %d exists already.", generationId));
+ return;
+ }
+ if (generationId == 1) {
+ Log.i(TAG, "Generating initial platform ID.");
+ } else {
+ Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no "
+ + "entry was present in AndroidKeyStore. Generating fresh key.", generationId));
+ }
+
+ generateAndLoadKey(generationId);
+ }
+
+ /**
+ * Returns the alias of the encryption key with the specific {@code generationId} in the
+ * AndroidKeyStore.
+ *
+ * <p>These IDs look as follows:
+ * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/encrypt}
+ *
+ * @param generationId The generation ID.
+ * @return The alias.
+ */
+ private String getEncryptAlias(int generationId) {
+ return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX;
+ }
+
+ /**
+ * Returns the alias of the decryption key with the specific {@code generationId} in the
+ * AndroidKeyStore.
+ *
+ * <p>These IDs look as follows:
+ * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/decrypt}
+ *
+ * @param generationId The generation ID.
+ * @return The alias.
+ */
+ private String getDecryptAlias(int generationId) {
+ return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX;
+ }
+
+ /**
+ * Sets the current generation ID to {@code generationId}.
+ */
+ private void setGenerationId(int generationId) {
+ mDatabase.setPlatformKeyGenerationId(mUserId, generationId);
+ }
+
+ /**
+ * Returns {@code true} if a key has been loaded with the given {@code generationId} into
+ * AndroidKeyStore.
+ *
+ * @throws KeyStoreException if there was an error checking AndroidKeyStore.
+ */
+ private boolean isKeyLoaded(int generationId) throws KeyStoreException {
+ return mKeyStore.containsAlias(getEncryptAlias(generationId))
+ && mKeyStore.containsAlias(getDecryptAlias(generationId));
+ }
+
+ /**
+ * Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given
+ * {@code generationId} determining its aliases.
+ *
+ * @throws NoSuchAlgorithmException if AES is unavailable. This should never happen, as it is
+ * available since API version 1.
+ * @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore.
+ */
+ private void generateAndLoadKey(int generationId)
+ throws NoSuchAlgorithmException, KeyStoreException {
+ String encryptAlias = getEncryptAlias(generationId);
+ String decryptAlias = getDecryptAlias(generationId);
+ SecretKey secretKey = generateAesKey();
+
+ mKeyStore.setEntry(
+ encryptAlias,
+ new KeyStore.SecretKeyEntry(secretKey),
+ new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+ mKeyStore.setEntry(
+ decryptAlias,
+ new KeyStore.SecretKeyEntry(secretKey),
+ new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
+ .setUserAuthenticationRequired(true)
+ .setUserAuthenticationValidityDurationSeconds(
+ USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setBoundToSpecificSecureUserId(mUserId)
+ .build());
+
+ try {
+ secretKey.destroy();
+ } catch (DestroyFailedException e) {
+ Log.w(TAG, "Failed to destroy in-memory platform key.", e);
+ }
+ }
+
+ /**
+ * Generates a new 256-bit AES key, in software.
+ *
+ * @return The software-generated AES key.
+ * @throws NoSuchAlgorithmException if AES key generation is not available. This should never
+ * happen, as AES has been supported since API level 1.
+ */
+ private static SecretKey generateAesKey() throws NoSuchAlgorithmException {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
+ keyGenerator.init(KEY_SIZE_BITS);
+ return keyGenerator.generateKey();
+ }
+
+ /**
+ * Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked
+ * {@link KeyStore#load(KeyStore.LoadStoreParameter)}.
+ *
+ * @throws KeyStoreException if there was a problem getting or initializing the key store.
+ */
+ private static KeyStore getAndLoadAndroidKeyStore() throws KeyStoreException {
+ KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
+ try {
+ keyStore.load(/*param=*/ null);
+ } catch (CertificateException | IOException | NoSuchAlgorithmException e) {
+ // Should never happen.
+ throw new KeyStoreException("Unable to load keystore.", e);
+ }
+ return keyStore;
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
index 40c7889..d50a736 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
@@ -16,16 +16,18 @@
package com.android.server.locksettings.recoverablekeystore;
-import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.util.Log;
-import java.io.IOException;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
import java.security.InvalidKeyException;
+import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
+import java.security.NoSuchProviderException;
+import java.util.Locale;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -42,39 +44,39 @@
*/
public class RecoverableKeyGenerator {
private static final String TAG = "RecoverableKeyGenerator";
+
+ private static final int RESULT_CANNOT_INSERT_ROW = -1;
private static final String KEY_GENERATOR_ALGORITHM = "AES";
private static final int KEY_SIZE_BITS = 256;
/**
* A new {@link RecoverableKeyGenerator} instance.
*
- * @param platformKey Secret key used to wrap generated keys before persisting to disk.
- * @param recoverableKeyStorage Class that manages persisting wrapped keys to disk.
* @throws NoSuchAlgorithmException if "AES" key generation or "AES/GCM/NoPadding" cipher is
* unavailable. Should never happen.
*
* @hide
*/
- public static RecoverableKeyGenerator newInstance(
- AndroidKeyStoreSecretKey platformKey, RecoverableKeyStorage recoverableKeyStorage)
+ public static RecoverableKeyGenerator newInstance(RecoverableKeyStoreDb database)
throws NoSuchAlgorithmException {
// NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key
// material, so that it can be synced to disk in encrypted form.
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM);
- return new RecoverableKeyGenerator(keyGenerator, platformKey, recoverableKeyStorage);
+ return new RecoverableKeyGenerator(
+ keyGenerator, database, new AndroidKeyStoreFactory.Impl());
}
private final KeyGenerator mKeyGenerator;
- private final RecoverableKeyStorage mRecoverableKeyStorage;
- private final AndroidKeyStoreSecretKey mPlatformKey;
+ private final RecoverableKeyStoreDb mDatabase;
+ private final AndroidKeyStoreFactory mAndroidKeyStoreFactory;
private RecoverableKeyGenerator(
KeyGenerator keyGenerator,
- AndroidKeyStoreSecretKey platformKey,
- RecoverableKeyStorage recoverableKeyStorage) {
+ RecoverableKeyStoreDb recoverableKeyStoreDb,
+ AndroidKeyStoreFactory androidKeyStoreFactory) {
mKeyGenerator = keyGenerator;
- mRecoverableKeyStorage = recoverableKeyStorage;
- mPlatformKey = platformKey;
+ mAndroidKeyStoreFactory = androidKeyStoreFactory;
+ mDatabase = recoverableKeyStoreDb;
}
/**
@@ -84,50 +86,70 @@
* persisted to disk so that it can be synced remotely, and then recovered on another device.
* The generated key allows encrypt/decrypt only using AES/GCM/NoPadding.
*
- * <p>The key handle returned to the caller is a reference to the AndroidKeyStore key,
- * meaning that the caller is never able to access the raw, unencrypted key.
- *
+ * @param platformKey The user's platform key, with which to wrap the generated key.
+ * @param uid The uid of the application that will own the key.
* @param alias The alias by which the key will be known in AndroidKeyStore.
+ * @throws RecoverableKeyStorageException if there is some error persisting the key either to
+ * the AndroidKeyStore or the database.
+ * @throws KeyStoreException if there is a KeyStore error wrapping the generated key.
* @throws InvalidKeyException if the platform key cannot be used to wrap keys.
- * @throws IOException if there was an issue writing the wrapped key to the wrapped key store.
- * @throws UnrecoverableEntryException if could not retrieve key after putting it in
- * AndroidKeyStore. This should not happen.
- * @return A handle to the AndroidKeyStore key.
*
* @hide
*/
- public SecretKey generateAndStoreKey(String alias) throws KeyStoreException,
- InvalidKeyException, IOException, UnrecoverableEntryException {
+ public void generateAndStoreKey(PlatformEncryptionKey platformKey, int uid, String alias)
+ throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
mKeyGenerator.init(KEY_SIZE_BITS);
SecretKey key = mKeyGenerator.generateKey();
- mRecoverableKeyStorage.importIntoAndroidKeyStore(
- alias,
- key,
- new KeyProtection.Builder(
- KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
- .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .build());
- WrappedKey wrappedKey = WrappedKey.fromSecretKey(mPlatformKey, key);
+ KeyStoreProxy keyStore;
try {
+ keyStore = mAndroidKeyStoreFactory.getKeyStoreForUid(uid);
+ } catch (NoSuchProviderException e) {
+ throw new RecoverableKeyStorageException(
+ "Impossible: AndroidKeyStore provider did not exist", e);
+ } catch (KeyStoreException e) {
+ throw new RecoverableKeyStorageException(
+ "Could not load AndroidKeyStore for " + uid, e);
+ }
+
+ try {
+ keyStore.setEntry(
+ alias,
+ new KeyStore.SecretKeyEntry(key),
+ new KeyProtection.Builder(
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+ } catch (KeyStoreException e) {
+ throw new RecoverableKeyStorageException(
+ "Failed to load (%d, %s) into AndroidKeyStore", e);
+ }
+
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
+ try {
// Keep raw key material in memory for minimum possible time.
key.destroy();
} catch (DestroyFailedException e) {
Log.w(TAG, "Could not destroy SecretKey.");
}
+ long result = mDatabase.insertKey(uid, alias, wrappedKey);
- mRecoverableKeyStorage.persistToDisk(alias, wrappedKey);
+ if (result == RESULT_CANNOT_INSERT_ROW) {
+ // Attempt to clean up
+ try {
+ keyStore.deleteEntry(alias);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, String.format(Locale.US,
+ "Could not delete recoverable key (%d, %s) from "
+ + "AndroidKeyStore after error writing to database.", uid, alias),
+ e);
+ }
- try {
- // Reload from the keystore, so that the caller is only provided with the handle of the
- // key, not the raw key material.
- return mRecoverableKeyStorage.loadFromAndroidKeyStore(alias);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(
- "Impossible: NoSuchAlgorithmException when attempting to retrieve a key "
- + "that has only just been stored in AndroidKeyStore.", e);
+ throw new RecoverableKeyStorageException(
+ String.format(
+ Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java
deleted file mode 100644
index 6a189ef..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import android.security.keystore.KeyProtection;
-
-import java.io.IOException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
-
-import javax.crypto.SecretKey;
-
-/**
- * Stores wrapped keys to disk, so they can be synced on the next screen unlock event.
- *
- * @hide
- */
-public interface RecoverableKeyStorage {
-
- /**
- * Writes {@code wrappedKey} to disk, keyed by the application's uid and the {@code alias}.
- *
- * @throws IOException if an error occurred writing to disk.
- *
- * @hide
- */
- void persistToDisk(String alias, WrappedKey wrappedKey) throws IOException;
-
- /**
- * Imports {@code key} into AndroidKeyStore, keyed by the application's uid and
- * the {@code alias}.
- *
- * @param alias The alias of the key.
- * @param key The key.
- * @param keyProtection Protection params denoting what the key can be used for. (e.g., what
- * Cipher modes, whether for encrpyt/decrypt or signing, etc.)
- * @throws KeyStoreException if an error occurred loading the key into the AndroidKeyStore.
- *
- * @hide
- */
- void importIntoAndroidKeyStore(String alias, SecretKey key, KeyProtection keyProtection) throws
- KeyStoreException;
-
- /**
- * Loads a key handle from AndroidKeyStore.
- *
- * @param alias Alias of the key to load.
- * @return The key handle.
- * @throws KeyStoreException if an error occurred loading the key from AndroidKeyStore.
- *
- * @hide
- */
- SecretKey loadFromAndroidKeyStore(String alias) throws KeyStoreException,
- NoSuchAlgorithmException,
- UnrecoverableEntryException;
-
- /**
- * Removes the entry with the given {@code alias} from AndroidKeyStore.
- *
- * @throws KeyStoreException if an error occurred deleting the key from AndroidKeyStore.
- *
- * @hide
- */
- void removeFromAndroidKeyStore(String alias) throws KeyStoreException;
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java
new file mode 100644
index 0000000..f9d28f1
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+/**
+ * Error thrown when there was a problem writing or reading recoverable key information to or from
+ * storage.
+ *
+ * <p>Storage is typically
+ * {@link com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb} or
+ * AndroidKeyStore.
+ */
+public class RecoverableKeyStorageException extends Exception {
+ public RecoverableKeyStorageException(String message) {
+ super(message);
+ }
+
+ public RecoverableKeyStorageException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java
deleted file mode 100644
index d4dede1..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import android.security.keystore.AndroidKeyStoreProvider;
-import android.security.keystore.KeyProtection;
-
-import java.io.IOException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.UnrecoverableEntryException;
-
-import javax.crypto.SecretKey;
-
-/**
- * Implementation of {@link RecoverableKeyStorage} for a specific application.
- *
- * <p>Persists wrapped keys to disk, and loads raw keys into AndroidKeyStore.
- *
- * @hide
- */
-public class RecoverableKeyStorageImpl implements RecoverableKeyStorage {
- private final KeyStore mKeyStore;
-
- /**
- * A new instance, storing recoverable keys for the given {@code userId}.
- *
- * @throws KeyStoreException if unable to load AndroidKeyStore.
- * @throws NoSuchProviderException if AndroidKeyStore is not in this version of Android. Should
- * never occur.
- *
- * @hide
- */
- public static RecoverableKeyStorageImpl newInstance(int userId) throws KeyStoreException,
- NoSuchProviderException {
- KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(userId);
- return new RecoverableKeyStorageImpl(keyStore);
- }
-
- private RecoverableKeyStorageImpl(KeyStore keyStore) {
- mKeyStore = keyStore;
- }
-
- /**
- * Writes {@code wrappedKey} to disk, keyed by the application's uid and the {@code alias}.
- *
- * @throws IOException if an error occurred writing to disk.
- *
- * @hide
- */
- @Override
- public void persistToDisk(String alias, WrappedKey wrappedKey) throws IOException {
- // TODO(robertberry) Add implementation.
- throw new UnsupportedOperationException();
- }
-
- /**
- * Imports {@code key} into the application's AndroidKeyStore, keyed by {@code alias}.
- *
- * @param alias The alias of the key.
- * @param key The key.
- * @param keyProtection Protection params denoting what the key can be used for. (e.g., what
- * Cipher modes, whether for encrpyt/decrypt or signing, etc.)
- * @throws KeyStoreException if an error occurred loading the key into the AndroidKeyStore.
- *
- * @hide
- */
- @Override
- public void importIntoAndroidKeyStore(String alias, SecretKey key, KeyProtection keyProtection)
- throws KeyStoreException {
- mKeyStore.setEntry(alias, new KeyStore.SecretKeyEntry(key), keyProtection);
- }
-
- /**
- * Loads a key handle from the application's AndroidKeyStore.
- *
- * @param alias Alias of the key to load.
- * @return The key handle.
- * @throws KeyStoreException if an error occurred loading the key from AndroidKeyStore.
- *
- * @hide
- */
- @Override
- public SecretKey loadFromAndroidKeyStore(String alias)
- throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
- return ((SecretKey) mKeyStore.getKey(alias, /*password=*/ null));
- }
-
- /**
- * Removes the entry with the given {@code alias} from the application's AndroidKeyStore.
- *
- * @throws KeyStoreException if an error occurred deleting the key from AndroidKeyStore.
- *
- * @hide
- */
- @Override
- public void removeFromAndroidKeyStore(String alias) throws KeyStoreException {
- mKeyStore.deleteEntry(alias);
- }
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
new file mode 100644
index 0000000..e459f28
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact
+ * with {@code LockSettingsService}.
+ *
+ * @hide
+ */
+public class RecoverableKeyStoreManager {
+ private static final String TAG = "RecoverableKeyStoreManager";
+
+ private static RecoverableKeyStoreManager mInstance;
+ private Context mContext;
+
+ /**
+ * Returns a new or existing instance.
+ *
+ * @hide
+ */
+ public static synchronized RecoverableKeyStoreManager getInstance(Context mContext) {
+ if (mInstance == null) {
+ mInstance = new RecoverableKeyStoreManager(mContext);
+ }
+ return mInstance;
+ }
+
+ @VisibleForTesting
+ RecoverableKeyStoreManager(Context context) {
+ mContext = context;
+ }
+
+ public int initRecoveryService(
+ @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ // TODO open /system/etc/security/... cert file
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Gets all data necessary to recover application keys on new device.
+ *
+ * @return recovery data
+ * @hide
+ */
+ public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
+ final int callingUserId = UserHandle.getCallingUserId();
+ final long callingIdentiy = Binder.clearCallingIdentity();
+ try {
+ // TODO: Return the latest snapshot for the calling recovery agent.
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentiy);
+ }
+
+ // KeyStoreRecoveryData without application keys and empty recovery blob.
+ KeyStoreRecoveryData recoveryData =
+ new KeyStoreRecoveryData(
+ /*snapshotVersion=*/ 1,
+ new ArrayList<KeyStoreRecoveryMetadata>(),
+ new ArrayList<KeyEntryRecoveryData>(),
+ /*encryptedRecoveryKeyBlob=*/ new byte[] {});
+ throw new ServiceSpecificException(
+ RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY);
+ }
+
+ public void setServerParameters(long serverParameters, int userId) throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ public void setRecoveryStatus(
+ @NonNull String packageName, @Nullable String[] aliases, int status, int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Sets recovery secrets list used by all recovery agents for given {@code userId}
+ *
+ * @hide
+ */
+ public void setRecoverySecretTypes(
+ @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Gets secret types necessary to create Recovery Data.
+ *
+ * @return secret types
+ * @hide
+ */
+ public int[] getRecoverySecretTypes(int userId) throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Gets secret types RecoverableKeyStoreLoaders is waiting for to create new Recovery Data.
+ *
+ * @return secret types
+ * @hide
+ */
+ public int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ public void recoverySecretAvailable(
+ @NonNull KeyStoreRecoveryMetadata recoverySecret, int userId) throws RemoteException {
+ final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
+ if (recoverySecret.getLockScreenUiFormat() == KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN) {
+ throw new SecurityException(
+ "Caller " + callingUid + "is not allowed to set lock screen secret");
+ }
+ checkRecoverKeyStorePermission();
+ // TODO: add hook from LockSettingsService to set lock screen secret.
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Initializes recovery session.
+ *
+ * @return recovery claim
+ * @hide
+ */
+ public byte[] startRecoverySession(
+ @NonNull String sessionId,
+ @NonNull byte[] verifierPublicKey,
+ @NonNull byte[] vaultParams,
+ @NonNull byte[] vaultChallenge,
+ @NonNull List<KeyStoreRecoveryMetadata> secrets,
+ int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ public void recoverKeys(
+ @NonNull String sessionId,
+ @NonNull byte[] recoveryKeyBlob,
+ @NonNull List<KeyEntryRecoveryData> applicationKeys,
+ int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ /** This function can only be used inside LockSettingsService. */
+ public void lockScreenSecretAvailable(
+ @KeyStoreRecoveryMetadata.LockScreenUiFormat int type,
+ String unencryptedPassword,
+ int userId) {
+ // TODO: compute SHA256 or Argon2id depending on secret type.
+ throw new UnsupportedOperationException();
+ }
+
+ /** This function can only be used inside LockSettingsService. */
+ public void lockScreenSecretChanged(
+ @KeyStoreRecoveryMetadata.LockScreenUiFormat int type,
+ @Nullable String unencryptedPassword,
+ int userId) {
+ throw new UnsupportedOperationException();
+ }
+
+ private void checkRecoverKeyStorePermission() {
+ mContext.enforceCallingOrSelfPermission(
+ RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE,
+ "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
index 742cb45..d8a2d31 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -17,23 +17,159 @@
package com.android.server.locksettings.recoverablekeystore;
import android.annotation.Nullable;
-
+import com.android.internal.annotations.VisibleForTesting;
+import java.math.BigInteger;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
-
+import java.security.SecureRandom;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyAgreement;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
/**
- * TODO(b/69056040) Add implementation of SecureBox. This is a placeholder so KeySyncUtils compiles.
+ * Implementation of the SecureBox v2 crypto functions.
+ *
+ * <p>Securebox v2 provides a simple interface to perform encryptions by using any of the following
+ * credential types:
+ *
+ * <ul>
+ * <li>A public key owned by the recipient,
+ * <li>A secret shared between the sender and the recipient, or
+ * <li>Both a recipient's public key and a shared secret.
+ * </ul>
*
* @hide
*/
public class SecureBox {
+
+ private static final byte[] VERSION = new byte[] {(byte) 0x02, 0}; // LITTLE_ENDIAN_TWO_BYTES(2)
+ private static final byte[] HKDF_SALT =
+ concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
+ private static final byte[] HKDF_INFO_WITH_PUBLIC_KEY =
+ "P256 HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] HKDF_INFO_WITHOUT_PUBLIC_KEY =
+ "SHARED HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] CONSTANT_01 = {(byte) 0x01};
+ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+ private static final byte EC_PUBLIC_KEY_PREFIX = (byte) 0x04;
+
+ private static final String CIPHER_ALG = "AES";
+ private static final String EC_ALG = "EC";
+ private static final String EC_P256_COMMON_NAME = "secp256r1";
+ private static final String EC_P256_OPENSSL_NAME = "prime256v1";
+ private static final String ENC_ALG = "AES/GCM/NoPadding";
+ private static final String KA_ALG = "ECDH";
+ private static final String MAC_ALG = "HmacSHA256";
+
+ private static final int EC_COORDINATE_LEN_BYTES = 32;
+ private static final int EC_PUBLIC_KEY_LEN_BYTES = 2 * EC_COORDINATE_LEN_BYTES + 1;
+ private static final int GCM_NONCE_LEN_BYTES = 12;
+ private static final int GCM_KEY_LEN_BYTES = 16;
+ private static final int GCM_TAG_LEN_BYTES = 16;
+
+ private static final BigInteger BIG_INT_02 = BigInteger.valueOf(2);
+
+ private enum AesGcmOperation {
+ ENCRYPT,
+ DECRYPT
+ }
+
+ // Parameters for the NIST P-256 curve y^2 = x^3 + ax + b (mod p)
+ private static final BigInteger EC_PARAM_P =
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16);
+ private static final BigInteger EC_PARAM_A = EC_PARAM_P.subtract(new BigInteger("3"));
+ private static final BigInteger EC_PARAM_B =
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);
+
+ @VisibleForTesting static final ECParameterSpec EC_PARAM_SPEC;
+
+ static {
+ EllipticCurve curveSpec =
+ new EllipticCurve(new ECFieldFp(EC_PARAM_P), EC_PARAM_A, EC_PARAM_B);
+ ECPoint generator =
+ new ECPoint(
+ new BigInteger(
+ "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
+ 16),
+ new BigInteger(
+ "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5",
+ 16));
+ BigInteger generatorOrder =
+ new BigInteger(
+ "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16);
+ EC_PARAM_SPEC = new ECParameterSpec(curveSpec, generator, generatorOrder, /* cofactor */ 1);
+ }
+
+ private SecureBox() {}
+
/**
- * TODO(b/69056040) Add implementation of encrypt.
+ * Randomly generates a public-key pair that can be used for the functions {@link #encrypt} and
+ * {@link #decrypt}.
*
+ * @return the randomly generated public-key pair
+ * @throws NoSuchAlgorithmException if the underlying crypto algorithm is not supported
+ * @hide
+ */
+ public static KeyPair genKeyPair() throws NoSuchAlgorithmException {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(EC_ALG);
+ try {
+ // Try using the OpenSSL provider first
+ keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_OPENSSL_NAME));
+ return keyPairGenerator.generateKeyPair();
+ } catch (InvalidAlgorithmParameterException ex) {
+ // Try another name for NIST P-256
+ }
+ try {
+ keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_COMMON_NAME));
+ return keyPairGenerator.generateKeyPair();
+ } catch (InvalidAlgorithmParameterException ex) {
+ throw new NoSuchAlgorithmException("Unable to find the NIST P-256 curve", ex);
+ }
+ }
+
+ /**
+ * Encrypts {@code payload} by using {@code theirPublicKey} and/or {@code sharedSecret}. At
+ * least one of {@code theirPublicKey} and {@code sharedSecret} must be non-null, and an empty
+ * {@code sharedSecret} is equivalent to null.
+ *
+ * <p>Note that {@code header} will be authenticated (but not encrypted) together with {@code
+ * payload}, and the same {@code header} has to be provided for {@link #decrypt}.
+ *
+ * @param theirPublicKey the recipient's public key, or null if the payload is to be encrypted
+ * only with the shared secret
+ * @param sharedSecret the secret shared between the sender and the recipient, or null if the
+ * payload is to be encrypted only with the recipient's public key
+ * @param header the data that will be authenticated with {@code payload} but not encrypted, or
+ * null if the data is empty
+ * @param payload the data to be encrypted, or null if the data is empty
+ * @return the encrypted payload
+ * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
+ * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
* @hide
*/
public static byte[] encrypt(
@@ -42,12 +178,59 @@
@Nullable byte[] header,
@Nullable byte[] payload)
throws NoSuchAlgorithmException, InvalidKeyException {
- throw new UnsupportedOperationException("Needs to be implemented.");
+ sharedSecret = emptyByteArrayIfNull(sharedSecret);
+ if (theirPublicKey == null && sharedSecret.length == 0) {
+ throw new IllegalArgumentException("Both the public key and shared secret are empty");
+ }
+ header = emptyByteArrayIfNull(header);
+ payload = emptyByteArrayIfNull(payload);
+
+ KeyPair senderKeyPair;
+ byte[] dhSecret;
+ byte[] hkdfInfo;
+ if (theirPublicKey == null) {
+ senderKeyPair = null;
+ dhSecret = EMPTY_BYTE_ARRAY;
+ hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
+ } else {
+ senderKeyPair = genKeyPair();
+ dhSecret = dhComputeSecret(senderKeyPair.getPrivate(), theirPublicKey);
+ hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
+ }
+
+ byte[] randNonce = genRandomNonce();
+ byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+ SecretKey encryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
+ byte[] ciphertext = aesGcmEncrypt(encryptionKey, randNonce, payload, header);
+ if (senderKeyPair == null) {
+ return concat(VERSION, randNonce, ciphertext);
+ } else {
+ return concat(
+ VERSION, encodePublicKey(senderKeyPair.getPublic()), randNonce, ciphertext);
+ }
}
/**
- * TODO(b/69056040) Add implementation of decrypt.
+ * Decrypts {@code encryptedPayload} by using {@code ourPrivateKey} and/or {@code sharedSecret}.
+ * At least one of {@code ourPrivateKey} and {@code sharedSecret} must be non-null, and an empty
+ * {@code sharedSecret} is equivalent to null.
*
+ * <p>Note that {@code header} should be the same data used for {@link #encrypt}, which is
+ * authenticated (but not encrypted) together with {@code payload}; otherwise, an {@code
+ * AEADBadTagException} will be thrown.
+ *
+ * @param ourPrivateKey the recipient's private key, or null if the payload was encrypted only
+ * with the shared secret
+ * @param sharedSecret the secret shared between the sender and the recipient, or null if the
+ * payload was encrypted only with the recipient's public key
+ * @param header the data that was authenticated with the original payload but not encrypted, or
+ * null if the data is empty
+ * @param encryptedPayload the data to be decrypted
+ * @return the original payload that was encrypted
+ * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
+ * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
+ * @throws AEADBadTagException if the authentication tag contained in {@code encryptedPayload}
+ * cannot be validated
* @hide
*/
public static byte[] decrypt(
@@ -56,6 +239,224 @@
@Nullable byte[] header,
byte[] encryptedPayload)
throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
- throw new UnsupportedOperationException("Needs to be implemented.");
+ sharedSecret = emptyByteArrayIfNull(sharedSecret);
+ if (ourPrivateKey == null && sharedSecret.length == 0) {
+ throw new IllegalArgumentException("Both the private key and shared secret are empty");
+ }
+ header = emptyByteArrayIfNull(header);
+ encryptedPayload = emptyByteArrayIfNull(encryptedPayload);
+
+ ByteBuffer ciphertextBuffer = ByteBuffer.wrap(encryptedPayload);
+ byte[] version = readEncryptedPayload(ciphertextBuffer, VERSION.length);
+ if (!Arrays.equals(version, VERSION)) {
+ throw new IllegalArgumentException("The payload was not encrypted by SecureBox v2");
+ }
+
+ byte[] senderPublicKeyBytes;
+ byte[] dhSecret;
+ byte[] hkdfInfo;
+ if (ourPrivateKey == null) {
+ dhSecret = EMPTY_BYTE_ARRAY;
+ hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
+ } else {
+ senderPublicKeyBytes = readEncryptedPayload(ciphertextBuffer, EC_PUBLIC_KEY_LEN_BYTES);
+ dhSecret = dhComputeSecret(ourPrivateKey, decodePublicKey(senderPublicKeyBytes));
+ hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
+ }
+
+ byte[] randNonce = readEncryptedPayload(ciphertextBuffer, GCM_NONCE_LEN_BYTES);
+ byte[] ciphertext = readEncryptedPayload(ciphertextBuffer, ciphertextBuffer.remaining());
+ byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+ SecretKey decryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
+ return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
+ }
+
+ private static byte[] readEncryptedPayload(ByteBuffer buffer, int length) {
+ byte[] output = new byte[length];
+ try {
+ buffer.get(output);
+ } catch (BufferUnderflowException ex) {
+ throw new IllegalArgumentException("The encrypted payload is too short");
+ }
+ return output;
+ }
+
+ private static byte[] dhComputeSecret(PrivateKey ourPrivateKey, PublicKey theirPublicKey)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+ KeyAgreement agreement = KeyAgreement.getInstance(KA_ALG);
+ try {
+ agreement.init(ourPrivateKey);
+ } catch (RuntimeException ex) {
+ // Rethrow the RuntimeException as InvalidKeyException
+ throw new InvalidKeyException(ex);
+ }
+ agreement.doPhase(theirPublicKey, /*lastPhase=*/ true);
+ return agreement.generateSecret();
+ }
+
+ /** Derives a 128-bit AES key. */
+ private static SecretKey hkdfDeriveKey(byte[] secret, byte[] salt, byte[] info)
+ throws NoSuchAlgorithmException {
+ Mac mac = Mac.getInstance(MAC_ALG);
+ try {
+ mac.init(new SecretKeySpec(salt, MAC_ALG));
+ } catch (InvalidKeyException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ byte[] pseudorandomKey = mac.doFinal(secret);
+
+ try {
+ mac.init(new SecretKeySpec(pseudorandomKey, MAC_ALG));
+ } catch (InvalidKeyException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ mac.update(info);
+ // Hashing just one block will yield 256 bits, which is enough to construct the AES key
+ byte[] hkdfOutput = mac.doFinal(CONSTANT_01);
+
+ return new SecretKeySpec(Arrays.copyOf(hkdfOutput, GCM_KEY_LEN_BYTES), CIPHER_ALG);
+ }
+
+ private static byte[] aesGcmEncrypt(SecretKey key, byte[] nonce, byte[] plaintext, byte[] aad)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+ try {
+ return aesGcmInternal(AesGcmOperation.ENCRYPT, key, nonce, plaintext, aad);
+ } catch (AEADBadTagException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static byte[] aesGcmDecrypt(SecretKey key, byte[] nonce, byte[] ciphertext, byte[] aad)
+ throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+ return aesGcmInternal(AesGcmOperation.DECRYPT, key, nonce, ciphertext, aad);
+ }
+
+ private static byte[] aesGcmInternal(
+ AesGcmOperation operation, SecretKey key, byte[] nonce, byte[] text, byte[] aad)
+ throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+ Cipher cipher;
+ try {
+ cipher = Cipher.getInstance(ENC_ALG);
+ } catch (NoSuchPaddingException ex) {
+ // This should never happen because AES-GCM doesn't use padding
+ throw new RuntimeException(ex);
+ }
+ GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LEN_BYTES * 8, nonce);
+ try {
+ if (operation == AesGcmOperation.DECRYPT) {
+ cipher.init(Cipher.DECRYPT_MODE, key, spec);
+ } else {
+ cipher.init(Cipher.ENCRYPT_MODE, key, spec);
+ }
+ } catch (InvalidAlgorithmParameterException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ try {
+ cipher.updateAAD(aad);
+ return cipher.doFinal(text);
+ } catch (AEADBadTagException ex) {
+ // Catch and rethrow AEADBadTagException first because it's a subclass of
+ // BadPaddingException
+ throw ex;
+ } catch (IllegalBlockSizeException | BadPaddingException ex) {
+ // This should never happen because AES-GCM can handle inputs of any length without
+ // padding
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @VisibleForTesting
+ static byte[] encodePublicKey(PublicKey publicKey) {
+ ECPoint point = ((ECPublicKey) publicKey).getW();
+ byte[] x = point.getAffineX().toByteArray();
+ byte[] y = point.getAffineY().toByteArray();
+
+ byte[] output = new byte[EC_PUBLIC_KEY_LEN_BYTES];
+ // The order of arraycopy() is important, because the coordinates may have a one-byte
+ // leading 0 for the sign bit of two's complement form
+ System.arraycopy(y, 0, output, EC_PUBLIC_KEY_LEN_BYTES - y.length, y.length);
+ System.arraycopy(x, 0, output, 1 + EC_COORDINATE_LEN_BYTES - x.length, x.length);
+ output[0] = EC_PUBLIC_KEY_PREFIX;
+ return output;
+ }
+
+ @VisibleForTesting
+ static PublicKey decodePublicKey(byte[] keyBytes)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+ BigInteger x =
+ new BigInteger(
+ /*signum=*/ 1,
+ Arrays.copyOfRange(keyBytes, 1, 1 + EC_COORDINATE_LEN_BYTES));
+ BigInteger y =
+ new BigInteger(
+ /*signum=*/ 1,
+ Arrays.copyOfRange(
+ keyBytes, 1 + EC_COORDINATE_LEN_BYTES, EC_PUBLIC_KEY_LEN_BYTES));
+
+ // Checks if the point is indeed on the P-256 curve for security considerations
+ validateEcPoint(x, y);
+
+ KeyFactory keyFactory = KeyFactory.getInstance(EC_ALG);
+ try {
+ return keyFactory.generatePublic(new ECPublicKeySpec(new ECPoint(x, y), EC_PARAM_SPEC));
+ } catch (InvalidKeySpecException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static void validateEcPoint(BigInteger x, BigInteger y) throws InvalidKeyException {
+ if (x.compareTo(EC_PARAM_P) >= 0
+ || y.compareTo(EC_PARAM_P) >= 0
+ || x.signum() == -1
+ || y.signum() == -1) {
+ throw new InvalidKeyException("Point lies outside of the expected curve");
+ }
+
+ // Points on the curve satisfy y^2 = x^3 + ax + b (mod p)
+ BigInteger lhs = y.modPow(BIG_INT_02, EC_PARAM_P);
+ BigInteger rhs =
+ x.modPow(BIG_INT_02, EC_PARAM_P) // x^2
+ .add(EC_PARAM_A) // x^2 + a
+ .mod(EC_PARAM_P) // This will speed up the next multiplication
+ .multiply(x) // (x^2 + a) * x = x^3 + ax
+ .add(EC_PARAM_B) // x^3 + ax + b
+ .mod(EC_PARAM_P);
+ if (!lhs.equals(rhs)) {
+ throw new InvalidKeyException("Point lies outside of the expected curve");
+ }
+ }
+
+ private static byte[] genRandomNonce() throws NoSuchAlgorithmException {
+ byte[] nonce = new byte[GCM_NONCE_LEN_BYTES];
+ new SecureRandom().nextBytes(nonce);
+ return nonce;
+ }
+
+ @VisibleForTesting
+ static byte[] concat(byte[]... inputs) {
+ int length = 0;
+ for (int i = 0; i < inputs.length; i++) {
+ if (inputs[i] == null) {
+ inputs[i] = EMPTY_BYTE_ARRAY;
+ }
+ length += inputs[i].length;
+ }
+
+ byte[] output = new byte[length];
+ int outputPos = 0;
+ for (byte[] input : inputs) {
+ System.arraycopy(input, /*srcPos=*/ 0, output, outputPos, input.length);
+ outputPos += input.length;
+ }
+ return output;
+ }
+
+ private static byte[] emptyByteArrayIfNull(@Nullable byte[] input) {
+ return input == null ? EMPTY_BYTE_ARRAY : input;
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
index f18e796..dfa173c 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
@@ -44,6 +44,7 @@
private static final String APPLICATION_KEY_ALGORITHM = "AES";
private static final int GCM_TAG_LENGTH_BITS = 128;
+ private final int mPlatformKeyGenerationId;
private final byte[] mNonce;
private final byte[] mKeyMaterial;
@@ -55,8 +56,8 @@
* {@link android.security.keystore.AndroidKeyStoreKey} for an example of a key that does
* not expose its key material.
*/
- public static WrappedKey fromSecretKey(
- SecretKey wrappingKey, SecretKey key) throws InvalidKeyException, KeyStoreException {
+ public static WrappedKey fromSecretKey(PlatformEncryptionKey wrappingKey, SecretKey key)
+ throws InvalidKeyException, KeyStoreException {
if (key.getEncoded() == null) {
throw new InvalidKeyException(
"key does not expose encoded material. It cannot be wrapped.");
@@ -70,7 +71,7 @@
"Android does not support AES/GCM/NoPadding. This should never happen.");
}
- cipher.init(Cipher.WRAP_MODE, wrappingKey);
+ cipher.init(Cipher.WRAP_MODE, wrappingKey.getKey());
byte[] encryptedKeyMaterial;
try {
encryptedKeyMaterial = cipher.wrap(key);
@@ -90,7 +91,10 @@
}
}
- return new WrappedKey(/*mNonce=*/ cipher.getIV(), /*mKeyMaterial=*/ encryptedKeyMaterial);
+ return new WrappedKey(
+ /*nonce=*/ cipher.getIV(),
+ /*keyMaterial=*/ encryptedKeyMaterial,
+ /*platformKeyGenerationId=*/ wrappingKey.getGenerationId());
}
/**
@@ -98,12 +102,14 @@
*
* @param nonce The nonce with which the key material was encrypted.
* @param keyMaterial The encrypted bytes of the key material.
+ * @param platformKeyGenerationId The generation ID of the key used to wrap this key.
*
* @hide
*/
- public WrappedKey(byte[] nonce, byte[] keyMaterial) {
+ public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) {
mNonce = nonce;
mKeyMaterial = keyMaterial;
+ mPlatformKeyGenerationId = platformKeyGenerationId;
}
/**
@@ -131,8 +137,7 @@
* @hide
*/
public int getPlatformKeyGenerationId() {
- // TODO(robertberry) Implement. See ag/3362855.
- return 1;
+ return mPlatformKeyGenerationId;
}
/**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index 79bf5aa..3644d36 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -25,6 +25,7 @@
import com.android.server.locksettings.recoverablekeystore.WrappedKey;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
import java.util.HashMap;
import java.util.Locale;
@@ -62,13 +63,12 @@
*
* @param uid Uid of the application to whom the key belongs.
* @param alias The alias of the key in the AndroidKeyStore.
- * @param wrappedKey The wrapped bytes of the key.
- * @param generationId The generation ID of the platform key that wrapped the key.
+ * @param wrappedKey The wrapped key.
* @return The primary key of the inserted row, or -1 if failed.
*
* @hide
*/
- public long insertKey(int uid, String alias, WrappedKey wrappedKey, int generationId) {
+ public long insertKey(int uid, String alias, WrappedKey wrappedKey) {
SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KeysEntry.COLUMN_NAME_UID, uid);
@@ -76,7 +76,7 @@
values.put(KeysEntry.COLUMN_NAME_NONCE, wrappedKey.getNonce());
values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial());
values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, -1);
- values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, generationId);
+ values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId());
return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
}
@@ -123,7 +123,9 @@
cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE));
byte[] keyMaterial = cursor.getBlob(
cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
- return new WrappedKey(nonce, keyMaterial);
+ int generationId = cursor.getInt(
+ cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID));
+ return new WrappedKey(nonce, keyMaterial, generationId);
}
}
@@ -168,13 +170,59 @@
cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
String alias = cursor.getString(
cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
- keys.put(alias, new WrappedKey(nonce, keyMaterial));
+ keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId));
}
return keys;
}
}
/**
+ * Sets the {@code generationId} of the platform key for the account owned by {@code userId}.
+ *
+ * @return The primary key ID of the relation.
+ */
+ public long setPlatformKeyGenerationId(int userId, int generationId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
+ values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId);
+ return db.replace(
+ UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
+ }
+
+ /**
+ * Returns the generation ID associated with the platform key of the user with {@code userId}.
+ */
+ public int getPlatformKeyGenerationId(int userId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+ String[] projection = {
+ UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID};
+ String selection =
+ UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+ String[] selectionArguments = {
+ Integer.toString(userId)};
+
+ try (
+ Cursor cursor = db.query(
+ UserMetadataEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ if (cursor.getCount() == 0) {
+ return -1;
+ }
+ cursor.moveToFirst();
+ return cursor.getInt(
+ cursor.getColumnIndexOrThrow(
+ UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID));
+ }
+ }
+
+ /**
* Closes all open connections to the database.
*/
public void close() {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index c54d0a6..b6c168f 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -58,4 +58,22 @@
*/
static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at";
}
+
+ /**
+ * Recoverable KeyStore metadata for a specific user profile.
+ */
+ static class UserMetadataEntry implements BaseColumns {
+ static final String TABLE_NAME = "user_metadata";
+
+ /**
+ * User ID of the profile.
+ */
+ static final String COLUMN_NAME_USER_ID = "user_id";
+
+ /**
+ * Every time a new platform key is generated for a user, this increments. The platform key
+ * is used to wrap recoverable keys on disk.
+ */
+ static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id";
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index e3783c4..6868203 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -5,6 +5,7 @@
import android.database.sqlite.SQLiteOpenHelper;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
/**
* Helper for creating the recoverable key database.
@@ -13,31 +14,44 @@
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "recoverablekeystore.db";
- private static final String SQL_CREATE_ENTRIES =
+ private static final String SQL_CREATE_KEYS_ENTRY =
"CREATE TABLE " + KeysEntry.TABLE_NAME + "( "
+ KeysEntry._ID + " INTEGER PRIMARY KEY,"
- + KeysEntry.COLUMN_NAME_UID + " INTEGER UNIQUE,"
- + KeysEntry.COLUMN_NAME_ALIAS + " TEXT UNIQUE,"
+ + KeysEntry.COLUMN_NAME_UID + " INTEGER,"
+ + KeysEntry.COLUMN_NAME_ALIAS + " TEXT,"
+ KeysEntry.COLUMN_NAME_NONCE + " BLOB,"
+ KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB,"
+ KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER,"
- + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER)";
+ + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER,"
+ + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + ","
+ + KeysEntry.COLUMN_NAME_ALIAS + "))";
- private static final String SQL_DELETE_ENTRIES =
+ private static final String SQL_CREATE_USER_METADATA_ENTRY =
+ "CREATE TABLE " + UserMetadataEntry.TABLE_NAME + "( "
+ + UserMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+ + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
+ + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)";
+
+ private static final String SQL_DELETE_KEYS_ENTRY =
"DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME;
+ private static final String SQL_DELETE_USER_METADATA_ENTRY =
+ "DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME;
+
RecoverableKeyStoreDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
- db.execSQL(SQL_CREATE_ENTRIES);
+ db.execSQL(SQL_CREATE_KEYS_ENTRY);
+ db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- db.execSQL(SQL_DELETE_ENTRIES);
+ db.execSQL(SQL_DELETE_KEYS_ENTRY);
+ db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
onCreate(db);
}
}
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 21c6889..de723c6 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -59,6 +59,8 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+
+import com.android.server.FgThread;
import com.android.server.wm.WindowManagerInternal;
import android.view.inputmethod.InputMethodManagerInternal;
@@ -825,9 +827,11 @@
@Override
public void onSwitchUser(int userHandle) {
- synchronized (mLock) {
- mComponentObserver.onUsersChanged();
- }
+ FgThread.getHandler().post(() -> {
+ synchronized (mLock) {
+ mComponentObserver.onUsersChanged();
+ }
+ });
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 7b0ed0d..8e916ad 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -675,6 +675,7 @@
final SparseArray<Boolean> mUserRestorecon = new SparseArray<Boolean>();
int mCurrentUserId;
+ boolean mInAmbientMode;
static class WallpaperData {
@@ -953,6 +954,13 @@
}
mPaddingChanged = false;
}
+ if (mInfo != null && mInfo.getSupportsAmbientMode()) {
+ try {
+ mEngine.setInAmbientMode(mInAmbientMode);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to set ambient mode state", e);
+ }
+ }
try {
// This will trigger onComputeColors in the wallpaper engine.
// It's fine to be locked in here since the binder is oneway.
@@ -1743,6 +1751,28 @@
}
}
+ public void setInAmbientMode(boolean inAmbienMode) {
+ final IWallpaperEngine engine;
+ synchronized (mLock) {
+ mInAmbientMode = inAmbienMode;
+ final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+ if (data != null && data.connection != null && data.connection.mInfo != null
+ && data.connection.mInfo.getSupportsAmbientMode()) {
+ engine = data.connection.mEngine;
+ } else {
+ engine = null;
+ }
+ }
+
+ if (engine != null) {
+ try {
+ engine.setInAmbientMode(inAmbienMode);
+ } catch (RemoteException e) {
+ // Cannot talk to wallpaper engine.
+ }
+ }
+ }
+
@Override
public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index 39cc953..02ad6c7 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -101,7 +101,7 @@
return -1;
}
ALOGV("Registering callback...");
- set_wakeup_callback(&wakeup_callback);
+ autosuspend_set_wakeup_callback(&wakeup_callback);
}
// Wait for wakeup.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 5b9e3a1..e55d4ea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -15,6 +15,7 @@
*/
package com.android.server.devicepolicy;
+import android.annotation.UserIdInt;
import android.app.admin.IDevicePolicyManager;
import android.content.ComponentName;
import android.os.PersistableBundle;
@@ -24,6 +25,8 @@
import com.android.internal.R;
import com.android.server.SystemService;
+import java.util.List;
+
/**
* Defines the required interface for IDevicePolicyManager implemenation.
*
@@ -68,7 +71,29 @@
return false;
}
+ @Override
+ public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist,
+ boolean parent) {
+ return false;
+ }
+
+ @Override
+ public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId,
+ boolean parent) {
+ return null;
+ }
+
+ @Override
+ public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) {
+ return false;
+ }
+
public boolean isUsingUnifiedPassword(ComponentName who) {
return true;
}
+
+ public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias,
+ byte[] cert, byte[] chain, boolean isUserSelectable) {
+ 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 bead31f..387818b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -734,6 +734,7 @@
private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length";
private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length";
private static final String ATTR_VALUE = "value";
+ private static final String TAG_PASSWORD_BLACKLIST = "password-blacklist";
private static final String TAG_PASSWORD_QUALITY = "password-quality";
private static final String TAG_POLICIES = "policies";
private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS =
@@ -866,6 +867,9 @@
// Default title of confirm credentials screen
String organizationName = null;
+ // The blacklist data is stored in a file whose name is stored in the XML
+ String passwordBlacklistFile = null;
+
ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
info = _info;
isParent = parent;
@@ -947,6 +951,11 @@
out.endTag(null, TAG_MIN_PASSWORD_NONLETTER);
}
}
+ if (passwordBlacklistFile != null) {
+ out.startTag(null, TAG_PASSWORD_BLACKLIST);
+ out.attribute(null, ATTR_VALUE, passwordBlacklistFile);
+ out.endTag(null, TAG_PASSWORD_BLACKLIST);
+ }
if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
out.startTag(null, TAG_MAX_TIME_TO_UNLOCK);
out.attribute(null, ATTR_VALUE, Long.toString(maximumTimeToUnlock));
@@ -1186,7 +1195,9 @@
} else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
minimumPasswordMetrics.nonLetter = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
- } else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
+ } else if (TAG_PASSWORD_BLACKLIST.equals(tag)) {
+ passwordBlacklistFile = parser.getAttributeValue(null, ATTR_VALUE);
+ }else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
maximumTimeToUnlock = Long.parseLong(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_STRONG_AUTH_UNLOCK_TIMEOUT.equals(tag)) {
@@ -1441,6 +1452,8 @@
pw.println(minimumPasswordMetrics.symbols);
pw.print(prefix); pw.print("minimumPasswordNonLetter=");
pw.println(minimumPasswordMetrics.nonLetter);
+ pw.print(prefix); pw.print("passwordBlacklist=");
+ pw.println(passwordBlacklistFile != null);
pw.print(prefix); pw.print("maximumTimeToUnlock=");
pw.println(maximumTimeToUnlock);
pw.print(prefix); pw.print("strongAuthUnlockTimeout=");
@@ -1693,6 +1706,10 @@
return new LockPatternUtils(mContext);
}
+ PasswordBlacklist newPasswordBlacklist(File file) {
+ return new PasswordBlacklist(file);
+ }
+
boolean storageManagerIsFileBasedEncryptionEnabled() {
return StorageManager.isFileEncryptedNativeOnly();
}
@@ -2589,11 +2606,15 @@
}
}
- private JournaledFile makeJournaledFile(int userHandle) {
- final String base = userHandle == UserHandle.USER_SYSTEM
- ? mInjector.getDevicePolicyFilePathForSystemUser() + DEVICE_POLICIES_XML
- : new File(mInjector.environmentGetUserSystemDirectory(userHandle),
- DEVICE_POLICIES_XML).getAbsolutePath();
+ private File getPolicyFileDirectory(@UserIdInt int userId) {
+ return userId == UserHandle.USER_SYSTEM
+ ? new File(mInjector.getDevicePolicyFilePathForSystemUser())
+ : mInjector.environmentGetUserSystemDirectory(userId);
+ }
+
+ private JournaledFile makeJournaledFile(@UserIdInt int userId) {
+ final String base = new File(getPolicyFileDirectory(userId), DEVICE_POLICIES_XML)
+ .getAbsolutePath();
if (VERBOSE_LOG) {
Log.v(LOG_TAG, "Opening " + base);
}
@@ -4064,6 +4085,136 @@
}
}
+ /* @return the password blacklist set by the admin or {@code null} if none. */
+ PasswordBlacklist getAdminPasswordBlacklistLocked(@NonNull ActiveAdmin admin) {
+ final int userId = UserHandle.getUserId(admin.getUid());
+ return admin.passwordBlacklistFile == null ? null : new PasswordBlacklist(
+ new File(getPolicyFileDirectory(userId), admin.passwordBlacklistFile));
+ }
+
+ private static final String PASSWORD_BLACKLIST_FILE_PREFIX = "password-blacklist-";
+ private static final String PASSWORD_BLACKLIST_FILE_SUFFIX = "";
+
+ @Override
+ public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist,
+ boolean parent) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(who, "who is null");
+
+ synchronized (this) {
+ final ActiveAdmin admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+ final int userId = mInjector.userHandleGetCallingUserId();
+ PasswordBlacklist adminBlacklist = getAdminPasswordBlacklistLocked(admin);
+
+ if (blacklist == null || blacklist.isEmpty()) {
+ // Remove the adminBlacklist
+ admin.passwordBlacklistFile = null;
+ saveSettingsLocked(userId);
+ if (adminBlacklist != null) {
+ adminBlacklist.delete();
+ }
+ return true;
+ }
+
+ // Validate server side
+ Preconditions.checkNotNull(name, "name is null");
+ DevicePolicyManager.enforcePasswordBlacklistSize(blacklist);
+
+ // Blacklist is case insensitive so normalize to lower case
+ final int blacklistSize = blacklist.size();
+ for (int i = 0; i < blacklistSize; ++i) {
+ blacklist.set(i, blacklist.get(i).toLowerCase());
+ }
+
+ final boolean isNewBlacklist = adminBlacklist == null;
+ if (isNewBlacklist) {
+ // Create a new file for the blacklist. There could be multiple admins, each setting
+ // different blacklists, to restrict a user's credential, for example a managed
+ // profile can impose restrictions on its parent while the parent is already
+ // restricted by its own admin. A deterministic naming scheme would be fragile if
+ // new types of admin are introduced so we generate and save the file name instead.
+ // This isn't a temporary file but it reuses the name generation logic
+ final File file;
+ try {
+ file = File.createTempFile(PASSWORD_BLACKLIST_FILE_PREFIX,
+ PASSWORD_BLACKLIST_FILE_SUFFIX, getPolicyFileDirectory(userId));
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Failed to make a file for the blacklist", e);
+ return false;
+ }
+ adminBlacklist = mInjector.newPasswordBlacklist(file);
+ }
+
+ if (adminBlacklist.savePasswordBlacklist(name, blacklist)) {
+ if (isNewBlacklist) {
+ // The blacklist was saved so point the admin to the file
+ admin.passwordBlacklistFile = adminBlacklist.getFile().getName();
+ saveSettingsLocked(userId);
+ }
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId,
+ boolean parent) {
+ if (!mHasFeature) {
+ return null;
+ }
+ Preconditions.checkNotNull(who, "who is null");
+ enforceFullCrossUsersPermission(userId);
+ synchronized (this) {
+ final ActiveAdmin admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+ final PasswordBlacklist blacklist = getAdminPasswordBlacklistLocked(admin);
+ if (blacklist == null) {
+ return null;
+ }
+ return blacklist.getName();
+ }
+ }
+
+ @Override
+ public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) {
+ if (!mHasFeature) {
+ return false;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.TEST_BLACKLISTED_PASSWORD, null);
+ return isPasswordBlacklistedInternal(userId, password);
+ }
+
+ private boolean isPasswordBlacklistedInternal(@UserIdInt int userId, String password) {
+ Preconditions.checkNotNull(password, "Password is null");
+ enforceFullCrossUsersPermission(userId);
+
+ // Normalize to lower case for case insensitive blacklist match
+ final String lowerCasePassword = password.toLowerCase();
+
+ synchronized (this) {
+ final List<ActiveAdmin> admins =
+ getActiveAdminsForLockscreenPoliciesLocked(userId, /* parent */ false);
+ final int N = admins.size();
+ for (int i = 0; i < N; i++) {
+ final PasswordBlacklist blacklist
+ = getAdminPasswordBlacklistLocked(admins.get(i));
+ if (blacklist != null) {
+ if (blacklist.isPasswordBlacklisted(lowerCasePassword)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
@Override
public boolean isActivePasswordSufficient(int userHandle, boolean parent) {
if (!mHasFeature) {
@@ -4420,6 +4571,11 @@
return false;
}
}
+
+ if (isPasswordBlacklistedInternal(userHandle, password)) {
+ Slog.w(LOG_TAG, "resetPassword: the password is blacklisted");
+ return false;
+ }
}
DevicePolicyData policy = getUserData(userHandle);
@@ -4998,6 +5154,33 @@
}
@Override
+ public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias,
+ byte[] cert, byte[] chain, boolean isUserSelectable) {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_CERT_INSTALL);
+
+ final int callingUid = mInjector.binderGetCallingUid();
+ final long id = mInjector.binderClearCallingIdentity();
+ try (final KeyChainConnection keyChainConnection =
+ KeyChain.bindAsUser(mContext, UserHandle.getUserHandleForUid(callingUid))) {
+ IKeyChainService keyChain = keyChainConnection.getService();
+ if (!keyChain.setKeyPairCertificate(alias, cert, chain)) {
+ return false;
+ }
+ keyChain.setUserSelectable(alias, isUserSelectable);
+ return true;
+ } catch (InterruptedException e) {
+ Log.w(LOG_TAG, "Interrupted while setting keypair certificate", e);
+ Thread.currentThread().interrupt();
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Failed setting keypair certificate", e);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ return false;
+ }
+
+ @Override
public void choosePrivateKeyAlias(final int uid, final Uri uri, final String alias,
final IBinder response) {
// Caller UID needs to be trusted, so we restrict this method to SYSTEM_UID callers.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 4a6bee5..f91f959 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -29,10 +29,10 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.TimeUnit;
/**
* A Handler class for managing network logging on a background thread.
@@ -81,6 +81,7 @@
}
};
+ @VisibleForTesting
static final int LOG_NETWORK_EVENT_MSG = 1;
/** Network events accumulated so far to be finalized into a batch at some point. */
@@ -106,9 +107,15 @@
private long mLastRetrievedBatchToken;
NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
+ this(looper, dpm, 0 /* event id */);
+ }
+
+ @VisibleForTesting
+ NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id) {
super(looper);
- mDpm = dpm;
- mAlarmManager = mDpm.mInjector.getAlarmManager();
+ this.mDpm = dpm;
+ this.mAlarmManager = mDpm.mInjector.getAlarmManager();
+ this.mId = id;
}
@Override
@@ -189,7 +196,13 @@
if (mNetworkEvents.size() > 0) {
// Assign ids to the events.
for (NetworkEvent event : mNetworkEvents) {
- event.setId(mId++);
+ event.setId(mId);
+ if (mId == Long.MAX_VALUE) {
+ Slog.i(TAG, "Reached maximum id value; wrapping around ." + mCurrentBatchToken);
+ mId = 0;
+ } else {
+ mId++;
+ }
}
// Finalize the batch and start a new one from scratch.
if (mBatches.size() >= MAX_BATCHES) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java b/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java
new file mode 100644
index 0000000..6a9b53a
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Manages the blacklisted passwords.
+ *
+ * This caller must ensure synchronized access.
+ */
+public class PasswordBlacklist {
+ private static final String TAG = "PasswordBlacklist";
+
+ private final AtomicFile mFile;
+
+ /**
+ * Create an object to manage the password blacklist.
+ *
+ * This is a lightweight operation to prepare variables but not perform any IO.
+ */
+ public PasswordBlacklist(File file) {
+ mFile = new AtomicFile(file);
+ }
+
+ /**
+ * Atomically replace the blacklist.
+ *
+ * Pass {@code null} for an empty list.
+ */
+ public boolean savePasswordBlacklist(@NonNull String name, @NonNull List<String> blacklist) {
+ FileOutputStream fos = null;
+ try {
+ fos = mFile.startWrite();
+ final DataOutputStream out = buildStreamForWriting(fos);
+ final Header header = new Header(Header.VERSION_1, name, blacklist.size());
+ header.write(out);
+ final int blacklistSize = blacklist.size();
+ for (int i = 0; i < blacklistSize; ++i) {
+ out.writeUTF(blacklist.get(i));
+ }
+ out.flush();
+ mFile.finishWrite(fos);
+ return true;
+ } catch (IOException e) {
+ mFile.failWrite(fos);
+ return false;
+ }
+ }
+
+ /** @return the name of the blacklist or {@code null} if none set. */
+ public String getName() {
+ try (DataInputStream in = openForReading()) {
+ return Header.read(in).mName;
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed to read blacklist file", e);
+ }
+ return null;
+ }
+
+ /** @return the number of blacklisted passwords. */
+ public int getSize() {
+ final int blacklistSize;
+ try (DataInputStream in = openForReading()) {
+ return Header.read(in).mSize;
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed to read blacklist file", e);
+ }
+ return 0;
+ }
+
+ /** @return whether the password matches an blacklisted item. */
+ public boolean isPasswordBlacklisted(@NonNull String password) {
+ final int blacklistSize;
+ try (DataInputStream in = openForReading()) {
+ final Header header = Header.read(in);
+ for (int i = 0; i < header.mSize; ++i) {
+ if (in.readUTF().equals(password)) {
+ return true;
+ }
+ }
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed to read blacklist file", e);
+ // Fail safe and block all passwords. Setting a new blacklist should resolve this
+ // problem which can be identified by examining the log.
+ return true;
+ }
+ return false;
+ }
+
+ /** Delete the blacklist completely from disk. */
+ public void delete() {
+ mFile.delete();
+ }
+
+ /** Get the file the blacklist is stored in. */
+ public File getFile() {
+ return mFile.getBaseFile();
+ }
+
+ private DataOutputStream buildStreamForWriting(FileOutputStream fos) {
+ return new DataOutputStream(new BufferedOutputStream(fos));
+ }
+
+ private DataInputStream openForReading() throws IOException {
+ return new DataInputStream(new BufferedInputStream(mFile.openRead()));
+ }
+
+ /**
+ * Helper to read and write the header of the blacklist file.
+ */
+ private static class Header {
+ static final int VERSION_1 = 1;
+
+ final int mVersion; // File format version
+ final String mName;
+ final int mSize;
+
+ Header(int version, String name, int size) {
+ mVersion = version;
+ mName = name;
+ mSize = size;
+ }
+
+ void write(DataOutputStream out) throws IOException {
+ out.writeInt(mVersion);
+ out.writeUTF(mName);
+ out.writeInt(mSize);
+ }
+
+ static Header read(DataInputStream in) throws IOException {
+ final int version = in.readInt();
+ final String name = in.readUTF();
+ final int size = in.readInt();
+ return new Header(version, name, size);
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 7aa628a..4310a98 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -26,6 +26,7 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources.Theme;
+import android.database.sqlite.SQLiteCompatibilityWalFlags;
import android.os.BaseBundle;
import android.os.Binder;
import android.os.Build;
@@ -327,6 +328,8 @@
// The system server should never make non-oneway calls
Binder.setWarnOnBlocking(true);
+ // Deactivate SQLiteCompatibilityWalFlags until settings provider is initialized
+ SQLiteCompatibilityWalFlags.init(null);
// Here we go!
Slog.i(TAG, "Entered the Android system server!");
@@ -803,6 +806,8 @@
traceBeginAndSlog("InstallSystemProviders");
mActivityManagerService.installSystemProviders();
+ // Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags
+ SQLiteCompatibilityWalFlags.reset();
traceEnd();
traceBeginAndSlog("StartVibratorService");
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index c14f74c..0462b14 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -62,7 +62,7 @@
@Mock AccessibilityServiceInfo mMockServiceInfo;
@Mock ResolveInfo mMockResolveInfo;
@Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
- @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+ @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock WindowManagerInternal mMockWindowManagerInternal;
@Mock GlobalActionPerformer mMockGlobalActionPerformer;
@Mock KeyEventDispatcher mMockKeyEventDispatcher;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 45ecbfb..8853db2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -59,7 +59,7 @@
@Mock AccessibilityServiceInfo mMockServiceInfo;
@Mock ResolveInfo mMockResolveInfo;
@Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
- @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+ @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock WindowManagerInternal mMockWindowManagerInternal;
@Mock GlobalActionPerformer mMockGlobalActionPerformer;
@Mock IBinder mMockOwner;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index d168479..0650acb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -102,6 +102,11 @@
this.context = injector.context;
}
+ @Override
+ public boolean isPasswordBlacklisted(int userId, String password) {
+ return false;
+ }
+
public void notifyChangeToContentObserver(Uri uri, int userHandle) {
ContentObserver co = mMockInjector.mContentObservers.get(new Pair<>(uri, userHandle));
@@ -205,6 +210,11 @@
}
@Override
+ PasswordBlacklist newPasswordBlacklist(File file) {
+ return services.passwordBlacklist;
+ }
+
+ @Override
boolean storageManagerIsFileBasedEncryptionEnabled() {
return services.storageManager.isFileBasedEncryptionEnabled();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ca918c6..4779474 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3765,6 +3765,36 @@
assertTrue(dpm.clearResetPasswordToken(admin1));
}
+ public void testSetPasswordBlacklistCannotBeCalledByNonAdmin() throws Exception {
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setPasswordBlacklist(admin1, null, null));
+ verifyZeroInteractions(getServices().passwordBlacklist);
+ }
+
+ public void testClearingPasswordBlacklistDoesNotCreateNewBlacklist() throws Exception {
+ setupProfileOwner();
+ dpm.setPasswordBlacklist(admin1, null, null);
+ verifyZeroInteractions(getServices().passwordBlacklist);
+ }
+
+ public void testSetPasswordBlacklistCreatesNewBlacklist() throws Exception {
+ final String name = "myblacklist";
+ final List<String> explicit = Arrays.asList("password", "letmein");
+ setupProfileOwner();
+ dpm.setPasswordBlacklist(admin1, name, explicit);
+ verify(getServices().passwordBlacklist).savePasswordBlacklist(name, explicit);
+ }
+
+ public void testSetPasswordBlacklistOnlyConvertsExplicitToLowerCase() throws Exception {
+ final List<String> mixedCase = Arrays.asList("password", "LETMEIN", "FooTBAll");
+ final List<String> lowerCase = Arrays.asList("password", "letmein", "football");
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ final String name = "Name of the Blacklist";
+ dpm.setPasswordBlacklist(admin1, name, mixedCase);
+ verify(getServices().passwordBlacklist).savePasswordBlacklist(name, lowerCase);
+ }
+
public void testIsActivePasswordSufficient() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
mContext.packageName = admin1.getPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 4ee5ba6..8cb0459 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -86,6 +86,7 @@
public final IBackupManager ibackupManager;
public final IAudioService iaudioService;
public final LockPatternUtils lockPatternUtils;
+ public final PasswordBlacklist passwordBlacklist;
public final StorageManagerForMock storageManager;
public final WifiManager wifiManager;
public final SettingsForMock settings;
@@ -120,6 +121,7 @@
ibackupManager = mock(IBackupManager.class);
iaudioService = mock(IAudioService.class);
lockPatternUtils = mock(LockPatternUtils.class);
+ passwordBlacklist = mock(PasswordBlacklist.class);
storageManager = mock(StorageManagerForMock.class);
wifiManager = mock(WifiManager.class);
settings = mock(SettingsForMock.class);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index a92d1db..c698312 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -15,13 +15,131 @@
*/
package com.android.server.devicepolicy;
+import static com.android.server.devicepolicy.NetworkLoggingHandler.LOG_NETWORK_EVENT_MSG;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
import android.app.admin.ConnectEvent;
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DnsEvent;
+import android.app.admin.NetworkEvent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
import android.os.Parcel;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
import android.test.suitebuilder.annotation.SmallTest;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
+
@SmallTest
public class NetworkEventTest extends DpmTestBase {
+ private static final int MAX_EVENTS_PER_BATCH = 1200;
+
+ private DpmMockContext mSpiedDpmMockContext;
+ private DevicePolicyManagerServiceTestable mDpmTestable;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mSpiedDpmMockContext = spy(mMockContext);
+ mSpiedDpmMockContext.callerPermissions.add(
+ android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+ doNothing().when(mSpiedDpmMockContext).sendBroadcastAsUser(any(Intent.class),
+ any(UserHandle.class));
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ mDpmTestable = new DevicePolicyManagerServiceTestable(getServices(), mSpiedDpmMockContext);
+ setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
+ mDpmTestable.setActiveAdmin(admin1, true, DpmMockContext.CALLER_USER_HANDLE);
+ }
+
+ public void testNetworkEventId_monotonicallyIncreasing() throws Exception {
+ // GIVEN the handler has not processed any events.
+ long startingId = 0;
+
+ // WHEN the handler has processed the events.
+ List<NetworkEvent> events = fillHandlerWithFullBatchOfEvents(startingId);
+
+ // THEN the events are in a batch.
+ assertTrue("Batch not at the returned token.",
+ events != null && events.size() == MAX_EVENTS_PER_BATCH);
+ // THEN event ids are monotonically increasing.
+ long expectedId = startingId;
+ for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) {
+ assertEquals("At index " + i + ", the event has the wrong id.", expectedId,
+ events.get(i).getId());
+ expectedId++;
+ }
+ }
+
+ public void testNetworkEventId_wrapsAround() throws Exception {
+ // GIVEN the handler has almost processed Long.MAX_VALUE events.
+ int gap = 5;
+ long startingId = Long.MAX_VALUE - gap;
+
+ // WHEN the handler has processed the events.
+ List<NetworkEvent> events = fillHandlerWithFullBatchOfEvents(startingId);
+
+ // THEN the events are in a batch.
+ assertTrue("Batch not at the returned token.",
+ events != null && events.size() == MAX_EVENTS_PER_BATCH);
+ // THEN event ids are monotonically increasing.
+ long expectedId = startingId;
+ for (int i = 0; i < gap; i++) {
+ assertEquals("At index " + i + ", the event has the wrong id.", expectedId,
+ events.get(i).getId());
+ expectedId++;
+ }
+ // THEN event ids are reset when the id reaches the maximum possible value.
+ assertEquals("Event was not assigned the maximum id value.", Long.MAX_VALUE,
+ events.get(gap).getId());
+ assertEquals("Event id was not reset.", 0, events.get(gap + 1).getId());
+ // THEN event ids are monotonically increasing.
+ expectedId = 0;
+ for (int i = gap + 1; i < MAX_EVENTS_PER_BATCH; i++) {
+ assertEquals("At index " + i + ", the event has the wrong id.", expectedId,
+ events.get(i).getId());
+ expectedId++;
+ }
+ }
+
+ private List<NetworkEvent> fillHandlerWithFullBatchOfEvents(long startingId) throws Exception {
+ // GIVEN a handler with events
+ NetworkLoggingHandler handler = new NetworkLoggingHandler(new TestLooper().getLooper(),
+ mDpmTestable, startingId);
+ // GIVEN network events are sent to the handler.
+ for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) {
+ ConnectEvent event = new ConnectEvent("some_ip_address", 800, "com.google.foo",
+ SystemClock.currentThreadTimeMillis());
+ Message msg = new Message();
+ msg.what = LOG_NETWORK_EVENT_MSG;
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(NetworkLoggingHandler.NETWORK_EVENT_KEY, event);
+ msg.setData(bundle);
+ handler.handleMessage(msg);
+ }
+
+ // WHEN the handler processes the events.
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mSpiedDpmMockContext).sendBroadcastAsUser(intentCaptor.capture(),
+ any(UserHandle.class));
+ assertEquals(intentCaptor.getValue().getAction(),
+ DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE);
+ long token = intentCaptor.getValue().getExtras().getLong(
+ DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, 0);
+ return handler.retrieveFullLogBatch(token);
+ }
/**
* Test parceling and unparceling of a ConnectEvent.
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
new file mode 100644
index 0000000..1b3fc2c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.devicepolicy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link PasswordBlacklist}.
+ *
+ * bit FrameworksServicesTests:com.android.server.devicepolicy.PasswordBlacklistTest
+ * runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
+ */
+@RunWith(AndroidJUnit4.class)
+public final class PasswordBlacklistTest {
+ private File mBlacklistFile;
+ private PasswordBlacklist mBlacklist;
+
+ @Before
+ public void setUp() throws IOException {
+ mBlacklistFile = File.createTempFile("pwdbl", null);
+ mBlacklist = new PasswordBlacklist(mBlacklistFile);
+ }
+
+ @After
+ public void tearDown() {
+ mBlacklist.delete();
+ }
+
+ @Test
+ public void matchIsExact() {
+ // Note: Case sensitivity is handled by the user of PasswordBlacklist by normalizing the
+ // values stored in and tested against it.
+ mBlacklist.savePasswordBlacklist("matchIsExact", Arrays.asList("password", "qWERty"));
+ assertTrue(mBlacklist.isPasswordBlacklisted("password"));
+ assertTrue(mBlacklist.isPasswordBlacklisted("qWERty"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("Password"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("qwert"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("letmein"));
+ }
+
+ @Test
+ public void matchIsNotRegex() {
+ mBlacklist.savePasswordBlacklist("matchIsNotRegex", Arrays.asList("a+b*"));
+ assertTrue(mBlacklist.isPasswordBlacklisted("a+b*"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("aaaa"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("abbbb"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("aaaa"));
+ }
+
+ @Test
+ public void matchFailsSafe() throws IOException {
+ try (FileOutputStream fos = new FileOutputStream(mBlacklistFile)) {
+ // Write a malformed blacklist file
+ fos.write(17);
+ }
+ assertTrue(mBlacklist.isPasswordBlacklisted("anything"));
+ assertTrue(mBlacklist.isPasswordBlacklisted("at"));
+ assertTrue(mBlacklist.isPasswordBlacklisted("ALL"));
+ }
+
+ @Test
+ public void blacklistCanBeNamed() {
+ final String name = "identifier";
+ mBlacklist.savePasswordBlacklist(name, Arrays.asList("one", "two", "three"));
+ assertEquals(mBlacklist.getName(), name);
+ }
+
+ @Test
+ public void reportsTheCorrectNumberOfEntries() {
+ mBlacklist.savePasswordBlacklist("Count Entries", Arrays.asList("1", "2", "3", "4"));
+ assertEquals(mBlacklist.getSize(), 4);
+ }
+
+ @Test
+ public void reportsBlacklistFile() {
+ assertEquals(mBlacklistFile, mBlacklist.getFile());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
new file mode 100644
index 0000000..e20f664
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.security.KeyStore;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PlatformKeyManagerTest {
+
+ private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+ private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
+ private static final int USER_ID_FIXTURE = 42;
+
+ @Mock private Context mContext;
+ @Mock private KeyStoreProxy mKeyStoreProxy;
+ @Mock private KeyguardManager mKeyguardManager;
+
+ @Captor private ArgumentCaptor<KeyStore.ProtectionParameter> mProtectionParameterCaptor;
+ @Captor private ArgumentCaptor<KeyStore.Entry> mEntryArgumentCaptor;
+
+ private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+ private File mDatabaseFile;
+
+ private PlatformKeyManager mPlatformKeyManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+ mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+ mPlatformKeyManager = new PlatformKeyManager(
+ USER_ID_FIXTURE, mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
+
+ when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
+ when(mContext.getSystemServiceName(any())).thenReturn("test");
+ when(mKeyguardManager.isDeviceSecure(USER_ID_FIXTURE)).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() {
+ mRecoverableKeyStoreDb.close();
+ mDatabaseFile.delete();
+ }
+
+ @Test
+ public void init_createsEncryptKeyWithCorrectAlias() throws Exception {
+ mPlatformKeyManager.init();
+
+ verify(mKeyStoreProxy).setEntry(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ any(),
+ any());
+ }
+
+ @Test
+ public void init_createsEncryptKeyWithCorrectPurposes() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertEquals(KeyProperties.PURPOSE_ENCRYPT, getEncryptKeyProtection().getPurposes());
+ }
+
+ @Test
+ public void init_createsEncryptKeyWithCorrectPaddings() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertArrayEquals(
+ new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
+ getEncryptKeyProtection().getEncryptionPaddings());
+ }
+
+ @Test
+ public void init_createsEncryptKeyWithCorrectBlockModes() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertArrayEquals(
+ new String[] { KeyProperties.BLOCK_MODE_GCM },
+ getEncryptKeyProtection().getBlockModes());
+ }
+
+ @Test
+ public void init_createsEncryptKeyWithoutAuthenticationRequired() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertFalse(getEncryptKeyProtection().isUserAuthenticationRequired());
+ }
+
+ @Test
+ public void init_createsDecryptKeyWithCorrectAlias() throws Exception {
+ mPlatformKeyManager.init();
+
+ verify(mKeyStoreProxy).setEntry(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ any(),
+ any());
+ }
+
+ @Test
+ public void init_createsDecryptKeyWithCorrectPurposes() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertEquals(KeyProperties.PURPOSE_DECRYPT, getDecryptKeyProtection().getPurposes());
+ }
+
+ @Test
+ public void init_createsDecryptKeyWithCorrectPaddings() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertArrayEquals(
+ new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
+ getDecryptKeyProtection().getEncryptionPaddings());
+ }
+
+ @Test
+ public void init_createsDecryptKeyWithCorrectBlockModes() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertArrayEquals(
+ new String[] { KeyProperties.BLOCK_MODE_GCM },
+ getDecryptKeyProtection().getBlockModes());
+ }
+
+ @Test
+ public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
+ }
+
+ @Test
+ public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertEquals(
+ USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS,
+ getDecryptKeyProtection().getUserAuthenticationValidityDurationSeconds());
+ }
+
+ @Test
+ public void init_createsDecryptKeyBoundToTheUsersAuthentication() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertEquals(
+ USER_ID_FIXTURE,
+ getDecryptKeyProtection().getBoundToSpecificSecureUserId());
+ }
+
+ @Test
+ public void init_createsBothKeysWithSameMaterial() throws Exception {
+ mPlatformKeyManager.init();
+
+ verify(mKeyStoreProxy, times(2)).setEntry(any(), mEntryArgumentCaptor.capture(), any());
+ List<KeyStore.Entry> entries = mEntryArgumentCaptor.getAllValues();
+ assertArrayEquals(
+ ((KeyStore.SecretKeyEntry) entries.get(0)).getSecretKey().getEncoded(),
+ ((KeyStore.SecretKeyEntry) entries.get(1)).getSecretKey().getEncoded());
+ }
+
+ @Test
+ public void init_setsGenerationIdTo1() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertEquals(1, mPlatformKeyManager.getGenerationId());
+ }
+
+ @Test
+ public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
+ mPlatformKeyManager.getDecryptKey();
+
+ verify(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ any());
+ }
+
+ @Test
+ public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
+ mPlatformKeyManager.getEncryptKey();
+
+ verify(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ any());
+ }
+
+ @Test
+ public void regenerate_incrementsTheGenerationId() throws Exception {
+ mPlatformKeyManager.init();
+
+ mPlatformKeyManager.regenerate();
+
+ assertEquals(2, mPlatformKeyManager.getGenerationId());
+ }
+
+ @Test
+ public void regenerate_generatesANewEncryptKeyWithTheCorrectAlias() throws Exception {
+ mPlatformKeyManager.init();
+
+ mPlatformKeyManager.regenerate();
+
+ verify(mKeyStoreProxy).setEntry(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ any(),
+ any());
+ }
+
+ @Test
+ public void regenerate_generatesANewDecryptKeyWithTheCorrectAlias() throws Exception {
+ mPlatformKeyManager.init();
+
+ mPlatformKeyManager.regenerate();
+
+ verify(mKeyStoreProxy).setEntry(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ any(),
+ any());
+ }
+
+ private KeyProtection getEncryptKeyProtection() throws Exception {
+ verify(mKeyStoreProxy).setEntry(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ any(),
+ mProtectionParameterCaptor.capture());
+ return (KeyProtection) mProtectionParameterCaptor.getValue();
+ }
+
+ private KeyProtection getDecryptKeyProtection() throws Exception {
+ verify(mKeyStoreProxy).setEntry(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ any(),
+ mProtectionParameterCaptor.capture());
+ return (KeyProtection) mProtectionParameterCaptor.getValue();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
index 298a988..3012931 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
@@ -16,61 +16,71 @@
package com.android.server.locksettings.recoverablekeystore;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
+import com.google.common.collect.ImmutableMap;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
import java.security.KeyStore;
+import java.util.Arrays;
+import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RecoverableKeyGeneratorTest {
+ private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+ private static final int TEST_GENERATION_ID = 3;
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String KEY_ALGORITHM = "AES";
+ private static final String SUPPORTED_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+ private static final String UNSUPPORTED_CIPHER_ALGORITHM = "AES/CTR/NoPadding";
private static final String TEST_ALIAS = "karlin";
private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyGeneratorTestWrappingKey";
+ private static final int KEYSTORE_UID_SELF = -1;
+ private static final int GCM_TAG_LENGTH_BITS = 128;
+ private static final int GCM_NONCE_LENGTH_BYTES = 12;
- @Mock
- RecoverableKeyStorage mRecoverableKeyStorage;
-
- @Captor ArgumentCaptor<KeyProtection> mKeyProtectionArgumentCaptor;
-
- private AndroidKeyStoreSecretKey mPlatformKey;
- private SecretKey mKeyHandle;
+ private PlatformEncryptionKey mPlatformKey;
+ private PlatformDecryptionKey mDecryptKey;
+ private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+ private File mDatabaseFile;
private RecoverableKeyGenerator mRecoverableKeyGenerator;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mPlatformKey = generateAndroidKeyStoreKey();
- mKeyHandle = generateKey();
- mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(
- mPlatformKey, mRecoverableKeyStorage);
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+ mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
- when(mRecoverableKeyStorage.loadFromAndroidKeyStore(any())).thenReturn(mKeyHandle);
+ AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+ mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, platformKey);
+ mDecryptKey = new PlatformDecryptionKey(TEST_GENERATION_ID, platformKey);
+ mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mRecoverableKeyStoreDb);
}
@After
@@ -78,67 +88,69 @@
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
keyStore.load(/*param=*/ null);
keyStore.deleteEntry(WRAPPING_KEY_ALIAS);
+
+ mRecoverableKeyStoreDb.close();
+ mDatabaseFile.delete();
}
@Test
public void generateAndStoreKey_setsKeyInKeyStore() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
- verify(mRecoverableKeyStorage, times(1))
- .importIntoAndroidKeyStore(eq(TEST_ALIAS), any(), any());
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ assertTrue(keyStore.containsAlias(TEST_ALIAS));
}
@Test
- public void generateAndStoreKey_storesKeyEnabledForEncryptDecrypt() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ public void generateAndStoreKey_storesKeyEnabledForAesGcmNoPaddingEncryptDecrypt()
+ throws Exception {
+ mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
- KeyProtection keyProtection = getKeyProtectionUsed();
- assertEquals(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
- keyProtection.getPurposes());
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+ Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
+ Arrays.fill(nonce, (byte) 0);
+ cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
}
@Test
- public void generateAndStoreKey_storesKeyEnabledForGCM() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ public void generateAndStoreKey_storesKeyDisabledForOtherModes() throws Exception {
+ mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
- KeyProtection keyProtection = getKeyProtectionUsed();
- assertArrayEquals(new String[] { KeyProperties.BLOCK_MODE_GCM },
- keyProtection.getBlockModes());
- }
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+ Cipher cipher = Cipher.getInstance(UNSUPPORTED_CIPHER_ALGORITHM);
- @Test
- public void generateAndStoreKey_storesKeyEnabledForNoPadding() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
- KeyProtection keyProtection = getKeyProtectionUsed();
- assertArrayEquals(new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
- keyProtection.getEncryptionPaddings());
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ fail("Should not be able to use key for " + UNSUPPORTED_CIPHER_ALGORITHM);
+ } catch (InvalidKeyException e) {
+ // expected
+ }
}
@Test
public void generateAndStoreKey_storesWrappedKey() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
- verify(mRecoverableKeyStorage, times(1)).persistToDisk(eq(TEST_ALIAS), any());
- }
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+ WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
+ SecretKey unwrappedKey = WrappedKey
+ .unwrapKeys(mDecryptKey, ImmutableMap.of(TEST_ALIAS, wrappedKey))
+ .get(TEST_ALIAS);
- @Test
- public void generateAndStoreKey_returnsKeyHandle() throws Exception {
- SecretKey secretKey = mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
- assertEquals(mKeyHandle, secretKey);
- }
-
- private KeyProtection getKeyProtectionUsed() throws Exception {
- verify(mRecoverableKeyStorage, times(1)).importIntoAndroidKeyStore(
- any(), any(), mKeyProtectionArgumentCaptor.capture());
- return mKeyProtectionArgumentCaptor.getValue();
- }
-
- private SecretKey generateKey() throws Exception {
- KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
- keyGenerator.init(/*keySize=*/ 256);
- return keyGenerator.generateKey();
+ // key and unwrappedKey should be equivalent. let's check!
+ byte[] plaintext = getUtf8Bytes("dtianpos");
+ Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] encrypted = cipher.doFinal(plaintext);
+ byte[] iv = cipher.getIV();
+ cipher.init(Cipher.DECRYPT_MODE, unwrappedKey, new GCMParameterSpec(128, iv));
+ byte[] decrypted = cipher.doFinal(encrypted);
+ assertArrayEquals(decrypted, plaintext);
}
private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
@@ -152,4 +164,8 @@
.build());
return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
}
+
+ private static byte[] getUtf8Bytes(String s) {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java
deleted file mode 100644
index fb4e75e..0000000
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.KeyStoreException;
-import java.util.Random;
-
-import javax.crypto.Cipher;
-import javax.crypto.KeyGenerator;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RecoverableKeyStorageImplTest {
- private static final String KEY_ALGORITHM = "AES";
- private static final int GCM_TAG_LENGTH_BYTES = 16;
- private static final int BITS_PER_BYTE = 8;
- private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
- private static final int GCM_NONCE_LENGTH_BYTES = 12;
- private static final String TEST_KEY_ALIAS = "RecoverableKeyStorageImplTestKey";
- private static final int KEYSTORE_UID_SELF = -1;
-
- private RecoverableKeyStorageImpl mRecoverableKeyStorage;
-
- @Before
- public void setUp() throws Exception {
- mRecoverableKeyStorage = RecoverableKeyStorageImpl.newInstance(
- /*userId=*/ KEYSTORE_UID_SELF);
- }
-
- @After
- public void tearDown() {
- try {
- mRecoverableKeyStorage.removeFromAndroidKeyStore(TEST_KEY_ALIAS);
- } catch (KeyStoreException e) {
- // Do nothing.
- }
- }
-
- @Test
- public void loadFromAndroidKeyStore_loadsAKeyThatWasImported() throws Exception {
- SecretKey key = generateKey();
- mRecoverableKeyStorage.importIntoAndroidKeyStore(
- TEST_KEY_ALIAS,
- key,
- getKeyProperties());
-
- assertKeysAreEquivalent(
- key, mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS));
- }
-
- @Test
- public void importIntoAndroidKeyStore_importsWithKeyProperties() throws Exception {
- mRecoverableKeyStorage.importIntoAndroidKeyStore(
- TEST_KEY_ALIAS,
- generateKey(),
- getKeyProperties());
-
- SecretKey key = mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS);
-
- Mac mac = Mac.getInstance("HmacSHA256");
- try {
- // Fails because missing PURPOSE_SIGN or PURPOSE_VERIFY
- mac.init(key);
- fail("Was able to initialize Mac with an ENCRYPT/DECRYPT-only key.");
- } catch (InvalidKeyException e) {
- // expect exception
- }
- }
-
- @Test
- public void removeFromAndroidKeyStore_removesAnEntry() throws Exception {
- mRecoverableKeyStorage.importIntoAndroidKeyStore(
- TEST_KEY_ALIAS,
- generateKey(),
- getKeyProperties());
-
- mRecoverableKeyStorage.removeFromAndroidKeyStore(TEST_KEY_ALIAS);
-
- assertNull(mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS));
- }
-
- private static KeyProtection getKeyProperties() {
- return new KeyProtection.Builder(
- KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
- .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .build();
- }
-
- /**
- * Asserts that {@code b} key can decrypt data encrypted with {@code a} key. Otherwise throws.
- */
- private static void assertKeysAreEquivalent(SecretKey a, SecretKey b) throws Exception {
- byte[] plaintext = "doge".getBytes(StandardCharsets.UTF_8);
- byte[] nonce = generateGcmNonce();
-
- Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
- cipher.init(Cipher.ENCRYPT_MODE, a, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
- byte[] encrypted = cipher.doFinal(plaintext);
-
- cipher.init(Cipher.DECRYPT_MODE, b, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
- byte[] decrypted = cipher.doFinal(encrypted);
-
- assertArrayEquals(decrypted, plaintext);
- }
-
- /**
- * Returns a new random GCM nonce.
- */
- private static byte[] generateGcmNonce() {
- Random random = new Random();
- byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
- random.nextBytes(nonce);
- return nonce;
- }
-
- private static SecretKey generateKey() throws Exception {
- KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
- keyGenerator.init(/*keySize=*/ 256);
- return keyGenerator.generateKey();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
new file mode 100644
index 0000000..72b69f0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.ECPrivateKeySpec;
+import javax.crypto.AEADBadTagException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SecureBoxTest {
+
+ private static final int EC_PUBLIC_KEY_LEN_BYTES = 65;
+ private static final int NUM_TEST_ITERATIONS = 100;
+ private static final int VERSION_LEN_BYTES = 2;
+
+ // The following fixtures were produced by the C implementation of SecureBox v2. We use these to
+ // cross-verify the two implementations.
+ private static final byte[] VAULT_PARAMS =
+ new byte[] {
+ (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98,
+ (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4, (byte) 0x94, (byte) 0xfe,
+ (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07, (byte) 0x8d, (byte) 0x01,
+ (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61,
+ (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+ (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f,
+ (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d,
+ (byte) 0x91, (byte) 0x55, (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a,
+ (byte) 0x8b, (byte) 0x32, (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2,
+ (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21,
+ (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa, (byte) 0x31,
+ (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00
+ };
+ private static final byte[] VAULT_CHALLENGE = getBytes("Not a real vault challenge");
+ private static final byte[] THM_KF_HASH = getBytes("12345678901234567890123456789012");
+ private static final byte[] ENCRYPTED_RECOVERY_KEY =
+ new byte[] {
+ (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0xe3, (byte) 0xa8, (byte) 0xd0,
+ (byte) 0x32, (byte) 0x3c, (byte) 0xc7, (byte) 0xe5, (byte) 0xe8, (byte) 0xc1,
+ (byte) 0x73, (byte) 0x4c, (byte) 0x75, (byte) 0x20, (byte) 0x2e, (byte) 0xb7,
+ (byte) 0xba, (byte) 0xef, (byte) 0x3e, (byte) 0x3e, (byte) 0xa6, (byte) 0x93,
+ (byte) 0xe9, (byte) 0xde, (byte) 0xa7, (byte) 0x00, (byte) 0x09, (byte) 0xba,
+ (byte) 0xa8, (byte) 0x9c, (byte) 0xac, (byte) 0x72, (byte) 0xff, (byte) 0xf6,
+ (byte) 0x84, (byte) 0x16, (byte) 0xb0, (byte) 0xff, (byte) 0x47, (byte) 0x98,
+ (byte) 0x53, (byte) 0xc4, (byte) 0xa3, (byte) 0x4a, (byte) 0x54, (byte) 0x21,
+ (byte) 0x8e, (byte) 0x00, (byte) 0x4b, (byte) 0xfa, (byte) 0xce, (byte) 0xe3,
+ (byte) 0x79, (byte) 0x8e, (byte) 0x20, (byte) 0x7c, (byte) 0x9b, (byte) 0xc4,
+ (byte) 0x7c, (byte) 0xd5, (byte) 0x33, (byte) 0x70, (byte) 0x96, (byte) 0xdc,
+ (byte) 0xa0, (byte) 0x1f, (byte) 0x6e, (byte) 0xbb, (byte) 0x5d, (byte) 0x0c,
+ (byte) 0x64, (byte) 0x5f, (byte) 0xed, (byte) 0xbf, (byte) 0x79, (byte) 0x8a,
+ (byte) 0x0e, (byte) 0xd6, (byte) 0x4b, (byte) 0x93, (byte) 0xc9, (byte) 0xcd,
+ (byte) 0x25, (byte) 0x06, (byte) 0x73, (byte) 0x5e, (byte) 0xdb, (byte) 0xac,
+ (byte) 0xa8, (byte) 0xeb, (byte) 0x6e, (byte) 0x26, (byte) 0x77, (byte) 0x56,
+ (byte) 0xd1, (byte) 0x23, (byte) 0x48, (byte) 0xb6, (byte) 0x6a, (byte) 0x15,
+ (byte) 0xd4, (byte) 0x3e, (byte) 0x38, (byte) 0x7d, (byte) 0x6f, (byte) 0x6f,
+ (byte) 0x7c, (byte) 0x0b, (byte) 0x93, (byte) 0x4e, (byte) 0xb3, (byte) 0x21,
+ (byte) 0x44, (byte) 0x86, (byte) 0xf3, (byte) 0x2e
+ };
+ private static final byte[] KEY_CLAIMANT = getBytes("asdfasdfasdfasdf");
+ private static final byte[] RECOVERY_CLAIM =
+ new byte[] {
+ (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0x16, (byte) 0x75, (byte) 0x5b,
+ (byte) 0xa2, (byte) 0xdc, (byte) 0x2b, (byte) 0x58, (byte) 0xb9, (byte) 0x66,
+ (byte) 0xcb, (byte) 0x6f, (byte) 0xb1, (byte) 0xc1, (byte) 0xb0, (byte) 0x1d,
+ (byte) 0x82, (byte) 0x29, (byte) 0x97, (byte) 0xec, (byte) 0x65, (byte) 0x5e,
+ (byte) 0xef, (byte) 0x14, (byte) 0xc7, (byte) 0xf0, (byte) 0xf1, (byte) 0x83,
+ (byte) 0x15, (byte) 0x0b, (byte) 0xcb, (byte) 0x33, (byte) 0x2d, (byte) 0x05,
+ (byte) 0x20, (byte) 0xdc, (byte) 0xc7, (byte) 0x0d, (byte) 0xc8, (byte) 0xc0,
+ (byte) 0xc9, (byte) 0xa8, (byte) 0x67, (byte) 0xc8, (byte) 0x16, (byte) 0xfe,
+ (byte) 0xfb, (byte) 0xb0, (byte) 0x28, (byte) 0x8e, (byte) 0x4f, (byte) 0xd5,
+ (byte) 0x31, (byte) 0xa7, (byte) 0x94, (byte) 0x33, (byte) 0x23, (byte) 0x15,
+ (byte) 0x04, (byte) 0xbf, (byte) 0x13, (byte) 0x6a, (byte) 0x28, (byte) 0x8f,
+ (byte) 0xa6, (byte) 0xfc, (byte) 0x01, (byte) 0xd5, (byte) 0x69, (byte) 0x3d,
+ (byte) 0x96, (byte) 0x0c, (byte) 0x37, (byte) 0xb4, (byte) 0x1e, (byte) 0x13,
+ (byte) 0x40, (byte) 0xcc, (byte) 0x44, (byte) 0x19, (byte) 0xf2, (byte) 0xdb,
+ (byte) 0x49, (byte) 0x80, (byte) 0x9f, (byte) 0xef, (byte) 0xee, (byte) 0x41,
+ (byte) 0xe6, (byte) 0x3f, (byte) 0xa8, (byte) 0xea, (byte) 0x89, (byte) 0xfe,
+ (byte) 0x56, (byte) 0x20, (byte) 0xba, (byte) 0x90, (byte) 0x9a, (byte) 0xba,
+ (byte) 0x0e, (byte) 0x30, (byte) 0xa7, (byte) 0x2b, (byte) 0x0a, (byte) 0x12,
+ (byte) 0x0b, (byte) 0x03, (byte) 0xd1, (byte) 0x0c, (byte) 0x8e, (byte) 0x82,
+ (byte) 0x03, (byte) 0xa1, (byte) 0x7f, (byte) 0xc8, (byte) 0xd0, (byte) 0xa9,
+ (byte) 0x86, (byte) 0x55, (byte) 0x63, (byte) 0xdc, (byte) 0x70, (byte) 0x34,
+ (byte) 0x21, (byte) 0x2a, (byte) 0x41, (byte) 0x3f, (byte) 0xbb, (byte) 0x82,
+ (byte) 0x82, (byte) 0xf9, (byte) 0x2b, (byte) 0xd2, (byte) 0x33, (byte) 0x03,
+ (byte) 0x50, (byte) 0xd2, (byte) 0x27, (byte) 0xeb, (byte) 0x1a
+ };
+
+ private static final byte[] TEST_SHARED_SECRET = getBytes("TEST_SHARED_SECRET");
+ private static final byte[] TEST_HEADER = getBytes("TEST_HEADER");
+ private static final byte[] TEST_PAYLOAD = getBytes("TEST_PAYLOAD");
+
+ private static final PublicKey THM_PUBLIC_KEY;
+ private static final PrivateKey THM_PRIVATE_KEY;
+
+ static {
+ try {
+ THM_PUBLIC_KEY =
+ SecureBox.decodePublicKey(
+ new byte[] {
+ (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18,
+ (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4,
+ (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c,
+ (byte) 0x07, (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a,
+ (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61, (byte) 0xd0,
+ (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+ (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0,
+ (byte) 0x3f, (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79,
+ (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55, (byte) 0xb0,
+ (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32,
+ (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc,
+ (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21,
+ (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa
+ });
+ THM_PRIVATE_KEY =
+ decodePrivateKey(
+ new byte[] {
+ (byte) 0x70, (byte) 0x01, (byte) 0xc7, (byte) 0x87, (byte) 0x32,
+ (byte) 0x2f, (byte) 0x1c, (byte) 0x9a, (byte) 0x6e, (byte) 0xb1,
+ (byte) 0x91, (byte) 0xca, (byte) 0x4e, (byte) 0xb5, (byte) 0x44,
+ (byte) 0xba, (byte) 0xc8, (byte) 0x68, (byte) 0xc6, (byte) 0x0a,
+ (byte) 0x76, (byte) 0xcb, (byte) 0xd3, (byte) 0x63, (byte) 0x67,
+ (byte) 0x7c, (byte) 0xb0, (byte) 0x11, (byte) 0x82, (byte) 0x65,
+ (byte) 0x77, (byte) 0x01
+ });
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Test
+ public void genKeyPair_alwaysReturnsANewKeyPair() throws Exception {
+ KeyPair keyPair1 = SecureBox.genKeyPair();
+ KeyPair keyPair2 = SecureBox.genKeyPair();
+ assertThat(keyPair1).isNotEqualTo(keyPair2);
+ }
+
+ @Test
+ public void decryptRecoveryClaim() throws Exception {
+ byte[] claimContent =
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ /*sharedSecret=*/ null,
+ SecureBox.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
+ RECOVERY_CLAIM);
+ assertThat(claimContent).isEqualTo(SecureBox.concat(THM_KF_HASH, KEY_CLAIMANT));
+ }
+
+ @Test
+ public void decryptRecoveryKey_doesNotThrowForValidAuthenticationTag() throws Exception {
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ THM_KF_HASH,
+ SecureBox.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
+ ENCRYPTED_RECOVERY_KEY);
+ }
+
+ @Test
+ public void encryptThenDecrypt() throws Exception {
+ byte[] state = TEST_PAYLOAD;
+ // Iterate multiple times to amplify any errors
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ state = SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
+ }
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ state = SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
+ }
+ assertThat(state).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullPublicPrivateKeys() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ /*theirPublicKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ byte[] decrypted =
+ SecureBox.decrypt(
+ /*ourPrivateKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, encrypted);
+ assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullSharedSecret() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ THM_PUBLIC_KEY, /*sharedSecret=*/ null, TEST_HEADER, TEST_PAYLOAD);
+ byte[] decrypted =
+ SecureBox.decrypt(THM_PRIVATE_KEY, /*sharedSecret=*/ null, TEST_HEADER, encrypted);
+ assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullHeader() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ THM_PUBLIC_KEY, TEST_SHARED_SECRET, /*header=*/ null, TEST_PAYLOAD);
+ byte[] decrypted =
+ SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, /*header=*/ null, encrypted);
+ assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullPayload() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, /*payload=*/ null);
+ byte[] decrypted =
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ TEST_SHARED_SECRET,
+ TEST_HEADER,
+ /*encryptedPayload=*/ encrypted);
+ assertThat(decrypted.length).isEqualTo(0);
+ }
+
+ @Test
+ public void encrypt_nullPublicKeyAndSharedSecret() throws Exception {
+ IllegalArgumentException expected =
+ expectThrows(
+ IllegalArgumentException.class,
+ () ->
+ SecureBox.encrypt(
+ /*theirPublicKey=*/ null,
+ /*sharedSecret=*/ null,
+ TEST_HEADER,
+ TEST_PAYLOAD));
+ assertThat(expected.getMessage()).contains("public key and shared secret");
+ }
+
+ @Test
+ public void decrypt_nullPrivateKeyAndSharedSecret() throws Exception {
+ IllegalArgumentException expected =
+ expectThrows(
+ IllegalArgumentException.class,
+ () ->
+ SecureBox.decrypt(
+ /*ourPrivateKey=*/ null,
+ /*sharedSecret=*/ null,
+ TEST_HEADER,
+ TEST_PAYLOAD));
+ assertThat(expected.getMessage()).contains("private key and shared secret");
+ }
+
+ @Test
+ public void decrypt_nullEncryptedPayload() throws Exception {
+ IllegalArgumentException expected =
+ expectThrows(
+ IllegalArgumentException.class,
+ () ->
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ TEST_SHARED_SECRET,
+ TEST_HEADER,
+ /*encryptedPayload=*/ null));
+ assertThat(expected.getMessage()).contains("payload");
+ }
+
+ @Test
+ public void decrypt_badAuthenticationTag() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ encrypted[encrypted.length - 1] ^= (byte) 1;
+
+ assertThrows(
+ AEADBadTagException.class,
+ () ->
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
+ }
+
+ @Test
+ public void encrypt_invalidPublicKey() throws Exception {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ PublicKey publicKey = keyGen.genKeyPair().getPublic();
+
+ assertThrows(
+ InvalidKeyException.class,
+ () -> SecureBox.encrypt(publicKey, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD));
+ }
+
+ @Test
+ public void decrypt_invalidPrivateKey() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ PrivateKey privateKey = keyGen.genKeyPair().getPrivate();
+
+ assertThrows(
+ InvalidKeyException.class,
+ () -> SecureBox.decrypt(privateKey, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
+ }
+
+ @Test
+ public void decrypt_publicKeyOutsideCurve() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ // Flip the least significant bit of the encoded public key
+ encrypted[VERSION_LEN_BYTES + EC_PUBLIC_KEY_LEN_BYTES - 1] ^= (byte) 1;
+
+ InvalidKeyException expected =
+ expectThrows(
+ InvalidKeyException.class,
+ () ->
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ TEST_SHARED_SECRET,
+ TEST_HEADER,
+ encrypted));
+ assertThat(expected.getMessage()).contains("expected curve");
+ }
+
+ @Test
+ public void encodeThenDecodePublicKey() throws Exception {
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ PublicKey originalKey = SecureBox.genKeyPair().getPublic();
+ byte[] encodedKey = SecureBox.encodePublicKey(originalKey);
+ PublicKey decodedKey = SecureBox.decodePublicKey(encodedKey);
+ assertThat(originalKey).isEqualTo(decodedKey);
+ }
+ }
+
+ private static byte[] getBytes(String str) {
+ return str.getBytes(StandardCharsets.UTF_8);
+ }
+
+ private static PrivateKey decodePrivateKey(byte[] keyBytes) throws Exception {
+ assertThat(keyBytes.length).isEqualTo(32);
+ BigInteger priv = new BigInteger(/*signum=*/ 1, keyBytes);
+ KeyFactory keyFactory = KeyFactory.getInstance("EC");
+ return keyFactory.generatePrivate(new ECPrivateKeySpec(priv, SecureBox.EC_PARAM_SPEC));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
index 8371fe5..56122a7 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
@@ -61,7 +61,8 @@
@Test
public void fromSecretKey_createsWrappedKeyThatCanBeUnwrapped() throws Exception {
- SecretKey wrappingKey = generateAndroidKeyStoreKey();
+ PlatformEncryptionKey wrappingKey = new PlatformEncryptionKey(
+ GENERATION_ID, generateAndroidKeyStoreKey());
SecretKey rawKey = generateKey();
WrappedKey wrappedKey = WrappedKey.fromSecretKey(wrappingKey, rawKey);
@@ -69,7 +70,7 @@
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(
Cipher.UNWRAP_MODE,
- wrappingKey,
+ wrappingKey.getKey(),
new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce()));
SecretKey unwrappedKey = (SecretKey) cipher.unwrap(
wrappedKey.getKeyMaterial(), KEY_ALGORITHM, Cipher.SECRET_KEY);
@@ -77,15 +78,28 @@
}
@Test
+ public void fromSecretKey_returnsAKeyWithTheGenerationIdOfTheWrappingKey() throws Exception {
+ PlatformEncryptionKey wrappingKey = new PlatformEncryptionKey(
+ GENERATION_ID, generateAndroidKeyStoreKey());
+ SecretKey rawKey = generateKey();
+
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(wrappingKey, rawKey);
+
+ assertEquals(GENERATION_ID, wrappedKey.getPlatformKeyGenerationId());
+ }
+
+ @Test
public void decryptWrappedKeys_decryptsWrappedKeys() throws Exception {
String alias = "karlin";
- PlatformDecryptionKey platformKey = generatePlatformDecryptionKey();
+ AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
SecretKey appKey = generateKey();
- WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey.getKey(), appKey);
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(
+ new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey);
HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
keysByAlias.put(alias, wrappedKey);
- Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(platformKey, keysByAlias);
+ Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(
+ new PlatformDecryptionKey(GENERATION_ID, platformKey), keysByAlias);
assertEquals(1, unwrappedKeys.size());
assertTrue(unwrappedKeys.containsKey(alias));
@@ -95,26 +109,32 @@
@Test
public void decryptWrappedKeys_doesNotDieIfSomeKeysAreUnwrappable() throws Exception {
String alias = "karlin";
+ AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
SecretKey appKey = generateKey();
- WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), appKey);
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(
+ new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey);
HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
keysByAlias.put(alias, wrappedKey);
Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(
- generatePlatformDecryptionKey(), keysByAlias);
+ new PlatformDecryptionKey(GENERATION_ID, generateAndroidKeyStoreKey()),
+ keysByAlias);
assertEquals(0, unwrappedKeys.size());
}
@Test
public void decryptWrappedKeys_throwsIfPlatformKeyGenerationIdDoesNotMatch() throws Exception {
- WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), generateKey());
+ AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(
+ new PlatformEncryptionKey(GENERATION_ID, platformKey), generateKey());
HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
keysByAlias.put("benji", wrappedKey);
try {
WrappedKey.unwrapKeys(
- generatePlatformDecryptionKey(/*generationId=*/ 2), keysByAlias);
+ new PlatformDecryptionKey(/*generationId=*/ 2, platformKey),
+ keysByAlias);
fail("Should have thrown.");
} catch (BadPlatformKeyException e) {
assertEquals(
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 5cb88dd..d5ad959 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -30,7 +30,6 @@
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-
import com.android.server.locksettings.recoverablekeystore.WrappedKey;
import java.io.File;
@@ -63,19 +62,46 @@
int userId = 12;
String alias = "test";
WrappedKey oldWrappedKey = new WrappedKey(
- getUtf8Bytes("nonce1"), getUtf8Bytes("keymaterial1"));
+ getUtf8Bytes("nonce1"),
+ getUtf8Bytes("keymaterial1"),
+ /*platformKeyGenerationId=*/ 1);
mRecoverableKeyStoreDb.insertKey(
- userId, alias, oldWrappedKey, /*generationId=*/ 1);
+ userId, alias, oldWrappedKey);
byte[] nonce = getUtf8Bytes("nonce2");
byte[] keyMaterial = getUtf8Bytes("keymaterial2");
- WrappedKey newWrappedKey = new WrappedKey(nonce, keyMaterial);
+ WrappedKey newWrappedKey = new WrappedKey(
+ nonce, keyMaterial, /*platformKeyGenerationId=*/2);
mRecoverableKeyStoreDb.insertKey(
- userId, alias, newWrappedKey, /*generationId=*/ 2);
+ userId, alias, newWrappedKey);
WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias);
assertArrayEquals(nonce, retrievedKey.getNonce());
assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
+ assertEquals(2, retrievedKey.getPlatformKeyGenerationId());
+ }
+
+ @Test
+ public void insertKey_allowsTwoUidsToHaveSameAlias() {
+ String alias = "pcoulton";
+ WrappedKey key1 = new WrappedKey(
+ getUtf8Bytes("nonce1"),
+ getUtf8Bytes("key1"),
+ /*platformKeyGenerationId=*/ 1);
+ WrappedKey key2 = new WrappedKey(
+ getUtf8Bytes("nonce2"),
+ getUtf8Bytes("key2"),
+ /*platformKeyGenerationId=*/ 1);
+
+ mRecoverableKeyStoreDb.insertKey(/*uid=*/ 1, alias, key1);
+ mRecoverableKeyStoreDb.insertKey(/*uid=*/ 2, alias, key2);
+
+ assertArrayEquals(
+ getUtf8Bytes("nonce1"),
+ mRecoverableKeyStoreDb.getKey(1, alias).getNonce());
+ assertArrayEquals(
+ getUtf8Bytes("nonce2"),
+ mRecoverableKeyStoreDb.getKey(2, alias).getNonce());
}
@Test
@@ -93,13 +119,14 @@
String alias = "test";
byte[] nonce = getUtf8Bytes("nonce");
byte[] keyMaterial = getUtf8Bytes("keymaterial");
- WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial);
- mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey, generationId);
+ WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId);
+ mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey);
WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias);
assertArrayEquals(nonce, retrievedKey.getNonce());
assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
+ assertEquals(generationId, retrievedKey.getPlatformKeyGenerationId());
}
@Test
@@ -109,8 +136,8 @@
String alias = "test";
byte[] nonce = getUtf8Bytes("nonce");
byte[] keyMaterial = getUtf8Bytes("keymaterial");
- WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial);
- mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey, generationId);
+ WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId);
+ mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey);
Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, generationId);
@@ -119,15 +146,18 @@
WrappedKey retrievedKey = keys.get(alias);
assertArrayEquals(nonce, retrievedKey.getNonce());
assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
+ assertEquals(generationId, retrievedKey.getPlatformKeyGenerationId());
}
@Test
public void getAllKeys_doesNotReturnKeysWithBadGenerationId() {
int userId = 12;
WrappedKey wrappedKey = new WrappedKey(
- getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial"));
+ getUtf8Bytes("nonce"),
+ getUtf8Bytes("keymaterial"),
+ /*platformKeyGenerationId=*/ 5);
mRecoverableKeyStoreDb.insertKey(
- userId, /*alias=*/ "test", wrappedKey, /*generationId=*/ 5);
+ userId, /*alias=*/ "test", wrappedKey);
Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
userId, /*generationId=*/ 7);
@@ -139,9 +169,9 @@
public void getAllKeys_doesNotReturnKeysWithBadUserId() {
int generationId = 12;
WrappedKey wrappedKey = new WrappedKey(
- getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial"));
+ getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial"), generationId);
mRecoverableKeyStoreDb.insertKey(
- /*userId=*/ 1, /*alias=*/ "test", wrappedKey, generationId);
+ /*userId=*/ 1, /*alias=*/ "test", wrappedKey);
Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
/*userId=*/ 2, generationId);
@@ -149,6 +179,29 @@
assertTrue(keys.isEmpty());
}
+ @Test
+ public void getPlatformKeyGenerationId_returnsGenerationId() {
+ int userId = 42;
+ int generationId = 24;
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, generationId);
+
+ assertEquals(generationId, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId));
+ }
+
+ @Test
+ public void getPlatformKeyGenerationId_returnsMinusOneIfNoEntry() {
+ assertEquals(-1, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(42));
+ }
+
+ @Test
+ public void setPlatformKeyGenerationId_replacesOldEntry() {
+ int userId = 42;
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 1);
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 2);
+
+ assertEquals(2, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId));
+ }
+
private static byte[] getUtf8Bytes(String s) {
return s.getBytes(StandardCharsets.UTF_8);
}
diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
index 7f48b8e..0c35fcb 100644
--- a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import com.android.frameworks.servicestests.R;
import com.android.servicestests.aidl.ICmdReceiverService;
@@ -42,6 +41,7 @@
import android.util.Log;
import org.junit.AfterClass;
+import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -81,7 +81,7 @@
private static final long NETWORK_CHECK_TIMEOUT_MS = 4000; // 4 sec
- private static final long SCREEN_ON_DELAY_MS = 500; // 0.5 sec
+ private static final long SCREEN_ON_DELAY_MS = 1000; // 1 sec
private static final long BIND_SERVICE_TIMEOUT_SEC = 4;
@@ -348,13 +348,17 @@
}
private String executeCommand(String cmd) throws IOException {
- final String result = mUiDevice.executeShellCommand(cmd).trim();
+ final String result = executeSilentCommand(cmd);
Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
return result;
}
+ private static String executeSilentCommand(String cmd) throws IOException {
+ return mUiDevice.executeShellCommand(cmd).trim();
+ }
+
private void assertDelayedCommandResult(String cmd, String expectedResult,
- int maxTries, int napTimeMs) throws IOException {
+ int maxTries, int napTimeMs) throws Exception {
String result = "";
for (int i = 1; i <= maxTries; ++i) {
result = executeCommand(cmd);
@@ -394,6 +398,23 @@
}
}
+ private static void fail(String msg) throws Exception {
+ dumpOnFailure();
+ Assert.fail(msg);
+ }
+
+ private static void dumpOnFailure() throws Exception {
+ Log.d(TAG, ">>> Begin network_management dump");
+ Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG,
+ TAG, executeSilentCommand("dumpsys network_management"), null);
+ Log.d(TAG, "<<< End network_management dump");
+
+ Log.d(TAG, ">>> Begin netpolicy dump");
+ Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG,
+ TAG, executeSilentCommand("dumpsys netpolicy"), null);
+ Log.d(TAG, "<<< End netpolicy dump");
+ }
+
private void finishActivity() throws Exception {
mCmdReceiverService.finishActivity();
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 337fd50..0959df2 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -451,9 +451,9 @@
private DisplayCutout createDisplayCutoutFromRect(int left, int top, int right, int bottom) {
return DisplayCutout.fromBoundingPolygon(Arrays.asList(
new Point(left, top),
- new Point (left, bottom),
- new Point (right, bottom),
- new Point (left, bottom)
+ new Point(left, bottom),
+ new Point(right, bottom),
+ new Point(right, top)
));
}
}
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java
index b26cc7fd..f8d1d03 100644
--- a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java
+++ b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java
@@ -77,6 +77,7 @@
Log.i(TAG, "onStop called");
if (finishCommandReceiver != null) {
unregisterReceiver(finishCommandReceiver);
+ finishCommandReceiver = null;
}
super.onStop();
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 07c860b..cdce448 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -495,8 +495,8 @@
flushToDiskLocked();
pw.println("Flushed stats to disk");
return;
- } else {
- // Anything else is a pkg to filter
+ } else if (arg != null && !arg.startsWith("-")) {
+ // Anything else that doesn't start with '-' is a pkg to filter
pkg = arg;
break;
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index c0685f9..44f5551 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -67,6 +67,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
@@ -389,11 +390,13 @@
}
public void switchUser(int userHandle) {
- synchronized (this) {
- mCurUser = userHandle;
- mCurUserUnlocked = false;
- switchImplementationIfNeededLocked(false);
- }
+ FgThread.getHandler().post(() -> {
+ synchronized (this) {
+ mCurUser = userHandle;
+ mCurUserUnlocked = false;
+ switchImplementationIfNeededLocked(false);
+ }
+ });
}
void switchImplementationIfNeeded(boolean force) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index dedce17..6a47d05 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1717,6 +1717,13 @@
public static final String KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL =
"spn_display_rule_use_roaming_from_service_state_bool";
+ /**
+ * Determines whether any carrier has been identified and its specific config has been applied,
+ * default to false.
+ * @hide
+ */
+ public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -2002,6 +2009,7 @@
sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false);
+ sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
}
/**
@@ -2047,6 +2055,33 @@
}
/**
+ * Determines whether a configuration {@link PersistableBundle} obtained from
+ * {@link #getConfig()} or {@link #getConfigForSubId(int)} corresponds to an identified carrier.
+ * <p>
+ * When an app receives the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED}
+ * broadcast which informs it that the carrier configuration has changed, it is possible
+ * that another reload of the carrier configuration has begun since the intent was sent.
+ * In this case, the carrier configuration the app fetches (e.g. via {@link #getConfig()})
+ * may not represent the configuration for the current carrier. It should be noted that it
+ * does not necessarily mean the configuration belongs to current carrier when this function
+ * return true because it may belong to another previous identified carrier. Users should
+ * always call {@link #getConfig()} or {@link #getConfigForSubId(int)} after receiving the
+ * broadcast {@link #ACTION_CARRIER_CONFIG_CHANGED}.
+ * </p>
+ * <p>
+ * After using {@link #getConfig()} or {@link #getConfigForSubId(int)} an app should always
+ * use this method to confirm whether any carrier specific configuration has been applied.
+ * </p>
+ *
+ * @param bundle the configuration bundle to be checked.
+ * @return boolean true if any carrier specific configuration bundle has been applied, false
+ * otherwise or the bundle is null.
+ */
+ public static boolean isConfigForIdentifiedCarrier(PersistableBundle bundle) {
+ return bundle != null && bundle.getBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL);
+ }
+
+ /**
* Calling this method triggers telephony services to fetch the current carrier configuration.
* <p>
* Normally this does not need to be called because the platform reloads config on its own.
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index 710eff0..a6dbc06 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -19,6 +19,7 @@
import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
import android.annotation.StringDef;
+import android.app.PendingIntent;
import android.content.res.Resources;
import android.os.Binder;
import android.text.TextUtils;
@@ -92,11 +93,13 @@
/**
* Indicates a 3GPP format SMS message.
+ * @see SmsManager#injectSmsPdu(byte[], String, PendingIntent)
*/
public static final String FORMAT_3GPP = "3gpp";
/**
* Indicates a 3GPP2 format SMS message.
+ * @see SmsManager#injectSmsPdu(byte[], String, PendingIntent)
*/
public static final String FORMAT_3GPP2 = "3gpp2";
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 28ae10b..9e77992 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1176,12 +1176,14 @@
}
/**
- * Returns the NAI. Return null if NAI is not available.
- *
+ * Returns the Network Access Identifier (NAI). Return null if NAI is not available.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
- /** {@hide}*/
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public String getNai() {
- return getNai(getSlotIndex());
+ return getNaiBySubscriberId(getSubId());
}
/**
@@ -1192,11 +1194,18 @@
/** {@hide}*/
public String getNai(int slotIndex) {
int[] subId = SubscriptionManager.getSubId(slotIndex);
+ if (subId == null) {
+ return null;
+ }
+ return getNaiBySubscriberId(subId[0]);
+ }
+
+ private String getNaiBySubscriberId(int subId) {
try {
IPhoneSubInfo info = getSubscriberInfo();
if (info == null)
return null;
- String nai = info.getNaiForSubscriber(subId[0], mContext.getOpPackageName());
+ String nai = info.getNaiForSubscriber(subId, mContext.getOpPackageName());
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Rlog.v(TAG, "Nai = " + nai);
}
@@ -5747,39 +5756,38 @@
* @param enable Whether to enable mobile data.
*
* @see #hasCarrierPrivileges
+ * @deprecated use {@link #setUserMobileDataEnabled(boolean)} instead.
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void setDataEnabled(boolean enable) {
- setDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable);
+ setUserMobileDataEnabled(enable);
}
- /** @hide */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void setDataEnabled(int subId, boolean enable) {
- try {
- Log.d(TAG, "setDataEnabled: enabled=" + enable);
- ITelephony telephony = getITelephony();
- if (telephony != null)
- telephony.setDataEnabled(subId, enable);
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#setDataEnabled", e);
- }
- }
-
-
/**
- * @deprecated use {@link #isDataEnabled()} instead.
+ * @hide
+ * @deprecated use {@link #setUserMobileDataEnabled(boolean)} instead.
+ */
+ @SystemApi
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setDataEnabled(int subId, boolean enable) {
+ setUserMobileDataEnabled(subId, enable);
+ }
+
+ /**
+ * @deprecated use {@link #isUserMobileDataEnabled()} instead.
* @hide
*/
@SystemApi
@Deprecated
public boolean getDataEnabled() {
- return isDataEnabled();
+ return isUserMobileDataEnabled();
}
/**
- * Returns whether mobile data is enabled or not.
+ * Returns whether mobile data is enabled or not per user setting. There are other factors
+ * that could disable mobile data, but they are not considered here.
*
* If this object has been created with {@link #createForSubscriptionId}, applies to the given
* subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
@@ -5796,28 +5804,21 @@
* @return true if mobile data is enabled.
*
* @see #hasCarrierPrivileges
+ * @deprecated use {@link #isUserMobileDataEnabled()} instead.
*/
- @SuppressWarnings("deprecation")
+ @Deprecated
public boolean isDataEnabled() {
- return getDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
+ return isUserMobileDataEnabled();
}
/**
- * @deprecated use {@link #isDataEnabled(int)} instead.
+ * @deprecated use {@link #isUserMobileDataEnabled()} instead.
* @hide
*/
+ @Deprecated
@SystemApi
public boolean getDataEnabled(int subId) {
- boolean retVal = false;
- try {
- ITelephony telephony = getITelephony();
- if (telephony != null)
- retVal = telephony.getDataEnabled(subId);
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#getDataEnabled", e);
- } catch (NullPointerException e) {
- }
- return retVal;
+ return isUserMobileDataEnabled(subId);
}
/** @hide */
@@ -7021,4 +7022,101 @@
}
return null;
}
+
+ /**
+ * Turns mobile data on or off.
+ * If the {@link TelephonyManager} object has been created with
+ * {@link #createForSubscriptionId}, this API applies to the given subId.
+ * Otherwise, it applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the
+ * calling app has carrier privileges.
+ *
+ * @param enable Whether to enable mobile data.
+ *
+ * @see #hasCarrierPrivileges
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setUserMobileDataEnabled(boolean enable) {
+ setUserMobileDataEnabled(
+ getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable);
+ }
+
+ /**
+ * Returns whether mobile data is enabled or not per user setting. There are other factors
+ * that could disable mobile data, but they are not considered here.
+ *
+ * If this object has been created with {@link #createForSubscriptionId}, applies to the given
+ * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
+ * <p>Requires one of the following permissions:
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
+ * calling app has carrier privileges.
+ *
+ * <p>Note that this does not take into account any data restrictions that may be present on the
+ * calling app. Such restrictions may be inspected with
+ * {@link ConnectivityManager#getRestrictBackgroundStatus}.
+ *
+ * @return true if mobile data is enabled.
+ *
+ * @see #hasCarrierPrivileges
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.ACCESS_NETWORK_STATE,
+ android.Manifest.permission.MODIFY_PHONE_STATE
+ })
+ public boolean isUserMobileDataEnabled() {
+ return isUserMobileDataEnabled(
+ getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
+ }
+
+ /**
+ * @hide
+ * Unlike isUserMobileDataEnabled, this API also evaluates carrierDataEnabled,
+ * policyDataEnabled etc to give a final decision.
+ */
+ public boolean isMobileDataEnabled() {
+ boolean retVal = false;
+ try {
+ int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId());
+ ITelephony telephony = getITelephony();
+ if (telephony != null)
+ retVal = telephony.isDataEnabled(subId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isDataEnabled", e);
+ } catch (NullPointerException e) {
+ }
+ return retVal;
+ }
+
+ /**
+ * Utility class of {@link #isUserMobileDataEnabled()};
+ */
+ private boolean isUserMobileDataEnabled(int subId) {
+ boolean retVal = false;
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null)
+ retVal = telephony.isUserDataEnabled(subId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isUserDataEnabled", e);
+ } catch (NullPointerException e) {
+ }
+ return retVal;
+ }
+
+ /** Utility method of {@link #setUserMobileDataEnabled(boolean)} */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ private void setUserMobileDataEnabled(int subId, boolean enable) {
+ try {
+ Log.d(TAG, "setUserMobileDataEnabled: enabled=" + enable);
+ ITelephony telephony = getITelephony();
+ if (telephony != null)
+ telephony.setUserDataEnabled(subId, enable);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#setUserDataEnabled", e);
+ }
+ }
}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 2507cfee..973df31 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -168,12 +168,10 @@
public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon";
private final Context mContext;
- private final IEuiccController mController;
/** @hide */
public EuiccManager(Context context) {
mContext = context;
- mController = IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
}
/**
@@ -189,7 +187,7 @@
public boolean isEnabled() {
// In the future, this may reach out to IEuiccController (if non-null) to check any dynamic
// restrictions.
- return mController != null;
+ return getIEuiccController() != null;
}
/**
@@ -206,7 +204,7 @@
return null;
}
try {
- return mController.getEid();
+ return getIEuiccController().getEid();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -232,7 +230,7 @@
return;
}
try {
- mController.downloadSubscription(subscription, switchAfterDownload,
+ getIEuiccController().downloadSubscription(subscription, switchAfterDownload,
mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -296,7 +294,7 @@
return;
}
try {
- mController.continueOperation(resolutionIntent, resolutionExtras);
+ getIEuiccController().continueOperation(resolutionIntent, resolutionExtras);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -328,7 +326,7 @@
return;
}
try {
- mController.getDownloadableSubscriptionMetadata(
+ getIEuiccController().getDownloadableSubscriptionMetadata(
subscription, mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -358,7 +356,7 @@
return;
}
try {
- mController.getDefaultDownloadableSubscriptionList(
+ getIEuiccController().getDefaultDownloadableSubscriptionList(
mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -377,7 +375,7 @@
return null;
}
try {
- return mController.getEuiccInfo();
+ return getIEuiccController().getEuiccInfo();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -402,7 +400,7 @@
return;
}
try {
- mController.deleteSubscription(
+ getIEuiccController().deleteSubscription(
subscriptionId, mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -429,7 +427,7 @@
return;
}
try {
- mController.switchToSubscription(
+ getIEuiccController().switchToSubscription(
subscriptionId, mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -455,7 +453,8 @@
return;
}
try {
- mController.updateSubscriptionNickname(subscriptionId, nickname, callbackIntent);
+ getIEuiccController().updateSubscriptionNickname(
+ subscriptionId, nickname, callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -477,7 +476,7 @@
return;
}
try {
- mController.eraseSubscriptions(callbackIntent);
+ getIEuiccController().eraseSubscriptions(callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -507,7 +506,7 @@
return;
}
try {
- mController.retainSubscriptionsForFactoryReset(callbackIntent);
+ getIEuiccController().retainSubscriptionsForFactoryReset(callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -520,4 +519,8 @@
// Caller canceled the callback; do nothing.
}
}
+
+ private static IEuiccController getIEuiccController() {
+ return IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index a9f8f45..8ea53a5 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -870,14 +870,33 @@
*
* @param enable true to turn on, else false
*/
- void setDataEnabled(int subId, boolean enable);
+ void setUserDataEnabled(int subId, boolean enable);
+
+ /**
+ * Get the user enabled state of Mobile Data.
+ *
+ * TODO: remove and use isUserDataEnabled.
+ * This can't be removed now because some vendor codes
+ * calls through ITelephony directly while they should
+ * use TelephonyManager.
+ *
+ * @return true on enabled
+ */
+ boolean getDataEnabled(int subId);
/**
* Get the user enabled state of Mobile Data.
*
* @return true on enabled
*/
- boolean getDataEnabled(int subId);
+ boolean isUserDataEnabled(int subId);
+
+ /**
+ * Get the overall enabled state of Mobile Data.
+ *
+ * @return true on enabled
+ */
+ boolean isDataEnabled(int subId);
/**
* Get P-CSCF address from PCO after data connection is established or modified.
diff --git a/test-mock/Android.mk b/test-mock/Android.mk
index a761a07..7926a77 100644
--- a/test-mock/Android.mk
+++ b/test-mock/Android.mk
@@ -16,7 +16,12 @@
LOCAL_PATH:= $(call my-dir)
-android_test_mock_source_files := $(call all-java-files-under, src/android/test/mock)
+# Includes the main framework source to ensure that doclava has access to the
+# visibility information for the base classes of the mock classes. Without it
+# otherwise hidden methods could be visible.
+android_test_mock_source_files := \
+ $(call all-java-files-under, src/android/test/mock) \
+ $(call all-java-files-under, ../core/java) \
# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
@@ -67,6 +72,8 @@
LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_gen_stamp)
android_test_mock_gen_stamp :=
+LOCAL_SDK_VERSION := current
+
include $(BUILD_STATIC_JAVA_LIBRARY)
# Archive a copy of the classes.jar in SDK build.
@@ -104,4 +111,91 @@
@echo Copying removed.txt
$(hide) $(ACP) $(ANDROID_TEST_MOCK_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_MOCK_REMOVED_API_FILE)
+# Generate the stub source files for android.test.mock.stubs-system
+# =================================================================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(android_test_mock_source_files)
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src/android/test/mock
+
+ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/api.txt
+ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/removed.txt
+
+ANDROID_TEST_MOCK_SYSTEM_API_FILE := $(LOCAL_PATH)/api/android-test-mock-system-current.txt
+ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE := $(LOCAL_PATH)/api/android-test-mock-system-removed.txt
+
+LOCAL_DROIDDOC_OPTIONS:= \
+ -stubpackages android.test.mock \
+ -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/src \
+ -nodocs \
+ -showAnnotation android.annotation.SystemApi \
+ -api $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) \
+ -removedApi $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE) \
+
+LOCAL_UNINSTALLABLE_MODULE := true
+LOCAL_MODULE := android-test-mock-system-api-stubs-gen
+
+include $(BUILD_DROIDDOC)
+
+# Remember the target that will trigger the code generation.
+android_test_mock_system_gen_stamp := $(full_target)
+
+# Add some additional dependencies
+$(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE): $(full_target)
+$(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE): $(full_target)
+
+# Build the android.test.mock.stubs-system library
+# ================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android.test.mock.stubs-system
+
+LOCAL_SOURCE_FILES_ALL_GENERATED := true
+
+# Make sure to run droiddoc first to generate the stub source files.
+LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_system_gen_stamp)
+android_test_mock_system_gen_stamp :=
+
+LOCAL_SDK_VERSION := system_current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Archive a copy of the classes.jar in SDK build.
+$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.mock.stubs_system.jar)
+
+# Check that the android.test.mock.stubs-system library has not changed
+# =====================================================================
+
+# Check that the API we're building hasn't changed from the not-yet-released
+# SDK version.
+$(eval $(call check-api, \
+ check-android-test-mock-system-api-current, \
+ $(ANDROID_TEST_MOCK_SYSTEM_API_FILE), \
+ $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE), \
+ $(ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE), \
+ $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE), \
+ -error 2 -error 3 -error 4 -error 5 -error 6 \
+ -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
+ -error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 \
+ -error 25 -error 26 -error 27, \
+ cat $(LOCAL_PATH)/api/apicheck_msg_android_test_mock-system.txt, \
+ check-android-test-mock-system-api, \
+ $(call doc-timestamp-for,android-test-mock-system-api-stubs-gen) \
+ ))
+
+.PHONY: check-android-test-mock-system-api
+checkapi: check-android-test-mock-system-api
+
+.PHONY: update-android-test-mock-system-api
+update-api: update-android-test-mock-system-api
+
+update-android-test-mock-system-api: $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) | $(ACP)
+ @echo Copying current.txt
+ $(hide) $(ACP) $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) $(ANDROID_TEST_MOCK_SYSTEM_API_FILE)
+ @echo Copying removed.txt
+ $(hide) $(ACP) $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE)
+
endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
+
diff --git a/test-mock/api/android-test-mock-current.txt b/test-mock/api/android-test-mock-current.txt
index 3aa350b..48a1f80 100644
--- a/test-mock/api/android-test-mock-current.txt
+++ b/test-mock/api/android-test-mock-current.txt
@@ -10,7 +10,6 @@
ctor public MockContentProvider(android.content.Context, java.lang.String, java.lang.String, android.content.pm.PathPermission[]);
method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>);
method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
- method public final android.content.IContentProvider getIContentProvider();
method public java.lang.String getType(android.net.Uri);
method public android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public boolean onCreate();
@@ -22,37 +21,26 @@
public class MockContentResolver extends android.content.ContentResolver {
ctor public MockContentResolver();
ctor public MockContentResolver(android.content.Context);
- method protected android.content.IContentProvider acquireProvider(android.content.Context, java.lang.String);
- method protected android.content.IContentProvider acquireUnstableProvider(android.content.Context, java.lang.String);
method public void addProvider(java.lang.String, android.content.ContentProvider);
- method public boolean releaseProvider(android.content.IContentProvider);
- method public boolean releaseUnstableProvider(android.content.IContentProvider);
- method public void unstableProviderDied(android.content.IContentProvider);
}
public class MockContext extends android.content.Context {
ctor public MockContext();
method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
- method public boolean canLoadUnsafeResources();
method public int checkCallingOrSelfPermission(java.lang.String);
method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
method public int checkCallingPermission(java.lang.String);
method public int checkCallingUriPermission(android.net.Uri, int);
method public int checkPermission(java.lang.String, int, int);
- method public int checkPermission(java.lang.String, int, int, android.os.IBinder);
method public int checkSelfPermission(java.lang.String);
method public int checkUriPermission(android.net.Uri, int, int, int);
- method public int checkUriPermission(android.net.Uri, int, int, int, android.os.IBinder);
method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
method public void clearWallpaper();
- method public android.content.Context createApplicationContext(android.content.pm.ApplicationInfo, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.Context createConfigurationContext(android.content.res.Configuration);
method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.Context createCredentialProtectedStorageContext();
method public android.content.Context createDeviceProtectedStorageContext();
method public android.content.Context createDisplayContext(android.view.Display);
method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.Context createPackageContextAsUser(java.lang.String, int, android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.lang.String[] databaseList();
method public boolean deleteDatabase(java.lang.String);
method public boolean deleteFile(java.lang.String);
@@ -68,7 +56,6 @@
method public android.content.Context getApplicationContext();
method public android.content.pm.ApplicationInfo getApplicationInfo();
method public android.content.res.AssetManager getAssets();
- method public java.lang.String getBasePackageName();
method public java.io.File getCacheDir();
method public java.lang.ClassLoader getClassLoader();
method public java.io.File getCodeCacheDir();
@@ -76,8 +63,6 @@
method public java.io.File getDataDir();
method public java.io.File getDatabasePath(java.lang.String);
method public java.io.File getDir(java.lang.String, int);
- method public android.view.Display getDisplay();
- method public android.view.DisplayAdjustments getDisplayAdjustments(int);
method public java.io.File getExternalCacheDir();
method public java.io.File[] getExternalCacheDirs();
method public java.io.File getExternalFilesDir(java.lang.String);
@@ -89,25 +74,19 @@
method public java.io.File getNoBackupFilesDir();
method public java.io.File getObbDir();
method public java.io.File[] getObbDirs();
- method public java.lang.String getOpPackageName();
method public java.lang.String getPackageCodePath();
method public android.content.pm.PackageManager getPackageManager();
method public java.lang.String getPackageName();
method public java.lang.String getPackageResourcePath();
- method public java.io.File getPreloadsFileCache();
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.io.File getSharedPreferencesPath(java.lang.String);
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();
- method public int getUserId();
method public android.graphics.drawable.Drawable getWallpaper();
method public int getWallpaperDesiredMinimumHeight();
method public int getWallpaperDesiredMinimumWidth();
method public void grantUriPermission(java.lang.String, android.net.Uri, int);
- method public boolean isCredentialProtectedStorage();
method public boolean isDeviceProtectedStorage();
method public boolean moveDatabaseFrom(android.content.Context, java.lang.String);
method public boolean moveSharedPreferencesFrom(android.content.Context, java.lang.String);
@@ -120,31 +99,19 @@
method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, int);
method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler);
method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, int);
- method public android.content.Intent registerReceiverAsUser(android.content.BroadcastReceiver, android.os.UserHandle, android.content.IntentFilter, java.lang.String, android.os.Handler);
- method public void reloadSharedPreferences();
method public void removeStickyBroadcast(android.content.Intent);
method public void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
method public void revokeUriPermission(android.net.Uri, int);
method public void revokeUriPermission(java.lang.String, android.net.Uri, int);
method public void sendBroadcast(android.content.Intent);
method public void sendBroadcast(android.content.Intent, java.lang.String);
- method public void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
- method public void sendBroadcast(android.content.Intent, java.lang.String, int);
method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String);
- method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
- method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int);
- method public void sendBroadcastMultiplePermissions(android.content.Intent, java.lang.String[]);
method public void sendOrderedBroadcast(android.content.Intent, java.lang.String);
method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
- method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
- method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
- method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
- method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void sendStickyBroadcast(android.content.Intent);
method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
- method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.os.Bundle);
method public void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void setTheme(int);
@@ -155,17 +122,13 @@
method public void startActivity(android.content.Intent);
method public void startActivity(android.content.Intent, android.os.Bundle);
method public android.content.ComponentName startForegroundService(android.content.Intent);
- method public android.content.ComponentName startForegroundServiceAsUser(android.content.Intent, android.os.UserHandle);
method public boolean startInstrumentation(android.content.ComponentName, java.lang.String, android.os.Bundle);
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public android.content.ComponentName startService(android.content.Intent);
- method public android.content.ComponentName startServiceAsUser(android.content.Intent, android.os.UserHandle);
method public boolean stopService(android.content.Intent);
- method public boolean stopServiceAsUser(android.content.Intent, android.os.UserHandle);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
- method public void updateDisplay(int);
}
public deprecated class MockCursor implements android.database.Cursor {
@@ -221,8 +184,6 @@
public deprecated class MockPackageManager extends android.content.pm.PackageManager {
ctor public MockPackageManager();
- method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
- method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public void addPackageToPreferred(java.lang.String);
method public boolean addPermission(android.content.pm.PermissionInfo);
method public boolean addPermissionAsync(android.content.pm.PermissionInfo);
@@ -232,19 +193,10 @@
method public int checkPermission(java.lang.String, java.lang.String);
method public int checkSignatures(java.lang.String, java.lang.String);
method public int checkSignatures(int, int);
- method public void clearApplicationUserData(java.lang.String, android.content.pm.IPackageDataObserver);
- method public void clearCrossProfileIntentFilters(int);
method public void clearInstantAppCookie();
method public void clearPackagePreferredActivities(java.lang.String);
method public java.lang.String[] currentToCanonicalPackageNames(java.lang.String[]);
- method public void deleteApplicationCacheFiles(java.lang.String, android.content.pm.IPackageDataObserver);
- method public void deleteApplicationCacheFilesAsUser(java.lang.String, int, android.content.pm.IPackageDataObserver);
- method public void deletePackage(java.lang.String, android.content.pm.IPackageDeleteObserver, int);
- method public void deletePackageAsUser(java.lang.String, android.content.pm.IPackageDeleteObserver, int, int);
method public void extendVerificationTimeout(int, int, long);
- method public void flushPackageRestrictionsAsUser(int);
- method public void freeStorage(java.lang.String, long, android.content.IntentSender);
- method public void freeStorageAndNotify(java.lang.String, long, android.content.pm.IPackageDataObserver);
method public android.graphics.drawable.Drawable getActivityBanner(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.graphics.drawable.Drawable getActivityBanner(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.graphics.drawable.Drawable getActivityIcon(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -257,148 +209,75 @@
method public android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationBanner(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public int getApplicationEnabledSetting(java.lang.String);
- method public boolean getApplicationHiddenSettingAsUser(java.lang.String, android.os.UserHandle);
method public android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationIcon(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.ApplicationInfo getApplicationInfoAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.lang.CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.ChangedPackages getChangedPackages(int);
method public int getComponentEnabledSetting(android.content.ComponentName);
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
- method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public android.content.ComponentName getHomeActivities(java.util.List<android.content.pm.ResolveInfo>);
- method public int getInstallReason(java.lang.String, android.os.UserHandle);
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
- method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
- method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
method public java.lang.String getInstallerPackageName(java.lang.String);
- method public java.lang.String getInstantAppAndroidId(java.lang.String, android.os.UserHandle);
method public byte[] getInstantAppCookie();
method public int getInstantAppCookieMaxBytes();
- method public int getInstantAppCookieMaxSize();
- method public android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
- method public android.content.ComponentName getInstantAppInstallerComponent();
- method public android.content.ComponentName getInstantAppResolverSettingsComponent();
- method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
- method public int getIntentVerificationStatusAsUser(java.lang.String, int);
- method public android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
- method public int getMoveStatus(int);
method public java.lang.String getNameForUid(int);
- method public java.lang.String[] getNamesForUids(int[]);
- method public java.util.List<android.os.storage.VolumeInfo> getPackageCandidateVolumes(android.content.pm.ApplicationInfo);
- method public android.os.storage.VolumeInfo getPackageCurrentVolume(android.content.pm.ApplicationInfo);
method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.PackageInfo getPackageInfoAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInstaller getPackageInstaller();
- method public void getPackageSizeInfoAsUser(java.lang.String, int, android.content.pm.IPackageStatsObserver);
method public int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public int getPackageUidAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public int getPackageUidAsUser(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.lang.String[] getPackagesForUid(int);
method public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int);
- method public java.lang.String getPermissionControllerPackageName();
- method public int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
method public android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PermissionInfo getPermissionInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String);
method public java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
- method public java.util.List<android.os.storage.VolumeInfo> getPrimaryStorageCandidateVolumes();
- method public android.os.storage.VolumeInfo getPrimaryStorageCurrentVolume();
method public android.content.pm.ProviderInfo getProviderInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.ActivityInfo getReceiverInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.res.Resources getResourcesForActivity(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.res.Resources getResourcesForApplicationAsUser(java.lang.String, int);
method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public java.lang.String getServicesSystemSharedLibraryPackageName();
method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
- method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibrariesAsUser(int, int);
- method public java.lang.String getSharedSystemSharedLibraryPackageName();
- method public android.content.pm.KeySet getSigningKeySet(java.lang.String);
method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public java.lang.String[] getSystemSharedLibraryNames();
method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public int getUidForSharedUser(java.lang.String);
- method public android.graphics.drawable.Drawable getUserBadgeForDensity(android.os.UserHandle, int);
- method public android.graphics.drawable.Drawable getUserBadgeForDensityNoBackground(android.os.UserHandle, int);
method public android.graphics.drawable.Drawable getUserBadgedDrawableForDensity(android.graphics.drawable.Drawable, android.os.UserHandle, android.graphics.Rect, int);
method public android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle);
method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
- method public android.content.pm.VerifierDeviceIdentity getVerifierDeviceIdentity();
method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public boolean hasSystemFeature(java.lang.String);
method public boolean hasSystemFeature(java.lang.String, int);
- method public int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public int installExistingPackageAsUser(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public void installPackage(android.net.Uri, android.app.PackageInstallObserver, int, java.lang.String);
method public boolean isInstantApp();
method public boolean isInstantApp(java.lang.String);
- method public boolean isPackageAvailable(java.lang.String);
- method public boolean isPackageSuspendedForUser(java.lang.String, int);
- method public boolean isPermissionReviewModeEnabled();
method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public boolean isSafeMode();
- method public boolean isSignedBy(java.lang.String, android.content.pm.KeySet);
- method public boolean isSignedByExactly(java.lang.String, android.content.pm.KeySet);
- method public boolean isUpgrade();
- method public android.graphics.drawable.Drawable loadItemIcon(android.content.pm.PackageItemInfo, android.content.pm.ApplicationInfo);
- method public android.graphics.drawable.Drawable loadUnbadgedItemIcon(android.content.pm.PackageItemInfo, android.content.pm.ApplicationInfo);
- method public int movePackage(java.lang.String, android.os.storage.VolumeInfo);
- method public int movePrimaryStorage(android.os.storage.VolumeInfo);
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
- method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, int);
method public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
method public java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(android.content.Intent, int);
- method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(android.content.Intent, int, int);
method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(android.content.ComponentName, android.content.Intent[], android.content.Intent, int);
method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int);
- method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(android.content.Intent, int, int);
method public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int);
- method public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(android.content.Intent, int, int);
method public java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
- method public void registerMoveCallback(android.content.pm.PackageManager.MoveCallback, android.os.Handler);
- method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public void removePackageFromPreferred(java.lang.String);
method public void removePermission(java.lang.String);
- method public void replacePreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
method public android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int);
- method public android.content.pm.ResolveInfo resolveActivityAsUser(android.content.Intent, int, int);
method public android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
- method public android.content.pm.ProviderInfo resolveContentProviderAsUser(java.lang.String, int, int);
method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
- method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public void setApplicationCategoryHint(java.lang.String, int);
method public void setApplicationEnabledSetting(java.lang.String, int, int);
- method public boolean setApplicationHiddenSettingAsUser(java.lang.String, boolean, android.os.UserHandle);
method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
- method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
method public void setInstallerPackageName(java.lang.String, java.lang.String);
- method public boolean setInstantAppCookie(byte[]);
- method public java.lang.String[] setPackagesSuspendedAsUser(java.lang.String[], boolean, int);
- method public void setUpdateAvailable(java.lang.String, boolean);
- method public boolean shouldShowRequestPermissionRationale(java.lang.String);
- method public void unregisterMoveCallback(android.content.pm.PackageManager.MoveCallback);
method public void updateInstantAppCookie(byte[]);
- method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
- method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
- method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
method public void verifyPendingInstall(int, int);
}
diff --git a/test-mock/api/android-test-mock-removed.txt b/test-mock/api/android-test-mock-removed.txt
index 5b358cf..bd109a8 100644
--- a/test-mock/api/android-test-mock-removed.txt
+++ b/test-mock/api/android-test-mock-removed.txt
@@ -8,6 +8,7 @@
public deprecated class MockPackageManager extends android.content.pm.PackageManager {
method public deprecated java.lang.String getDefaultBrowserPackageName(int);
method public deprecated boolean setDefaultBrowserPackageName(java.lang.String, int);
+ method public boolean setInstantAppCookie(byte[]);
}
}
diff --git a/test-mock/api/android-test-mock-system-current.txt b/test-mock/api/android-test-mock-system-current.txt
new file mode 100644
index 0000000..20401a5
--- /dev/null
+++ b/test-mock/api/android-test-mock-system-current.txt
@@ -0,0 +1,38 @@
+package android.test.mock {
+
+ public class MockContext extends android.content.Context {
+ method public android.content.Context createCredentialProtectedStorageContext();
+ method public java.io.File getPreloadsFileCache();
+ method public boolean isCredentialProtectedStorage();
+ method public void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
+ method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
+ method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
+ }
+
+ public deprecated class MockPackageManager extends android.content.pm.PackageManager {
+ method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+ method public java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
+ method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
+ method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+ method public android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
+ method public android.content.ComponentName getInstantAppInstallerComponent();
+ method public android.content.ComponentName getInstantAppResolverSettingsComponent();
+ method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
+ method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
+ method public int getIntentVerificationStatusAsUser(java.lang.String, int);
+ method public int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
+ method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
+ method public int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
+ method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+ method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
+ method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
+ method public void setUpdateAvailable(java.lang.String, boolean);
+ method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
+ method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
+ method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
+ }
+
+}
+
diff --git a/test-mock/api/android-test-mock-system-removed.txt b/test-mock/api/android-test-mock-system-removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test-mock/api/android-test-mock-system-removed.txt
diff --git a/test-mock/api/apicheck_msg_android_test_mock-system.txt b/test-mock/api/apicheck_msg_android_test_mock-system.txt
new file mode 100644
index 0000000..3a97117
--- /dev/null
+++ b/test-mock/api/apicheck_msg_android_test_mock-system.txt
@@ -0,0 +1,17 @@
+
+******************************
+You have tried to change the API from what has been previously approved.
+
+To make these errors go away, you have two choices:
+ 1) You can add "@hide" javadoc comments to the methods, etc. listed in the
+ errors above.
+
+ 2) You can update android-test-mock-current.txt by executing the following command:
+ make update-android-test-mock-system-api
+
+ To submit the revised android-test-mock-system-current.txt to the main Android repository,
+ you will need approval.
+******************************
+
+
+
diff --git a/tests/AppLaunch/Android.mk b/tests/AppLaunch/Android.mk
index 09739e5..917293f 100644
--- a/tests/AppLaunch/Android.mk
+++ b/tests/AppLaunch/Android.mk
@@ -13,6 +13,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
# Use the following include to make our test apk.
diff --git a/tests/Internal/Android.mk b/tests/Internal/Android.mk
index 73181ec..b69e3e4 100644
--- a/tests/Internal/Android.mk
+++ b/tests/Internal/Android.mk
@@ -14,6 +14,7 @@
android-support-test \
mockito-target-minus-junit4
+LOCAL_JAVA_RESOURCE_DIRS := res
LOCAL_CERTIFICATE := platform
LOCAL_PACKAGE_NAME := InternalTests
diff --git a/tests/Internal/AndroidManifest.xml b/tests/Internal/AndroidManifest.xml
index a2c95fb..e5a5694 100644
--- a/tests/Internal/AndroidManifest.xml
+++ b/tests/Internal/AndroidManifest.xml
@@ -18,8 +18,24 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.internal.tests">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.BIND_WALLPAPER" />
<application>
<uses-library android:name="android.test.runner" />
+
+ <service android:name="stub.DummyWallpaperService"
+ android:enabled="true"
+ android:directBootAware="true"
+ android:label="Dummy wallpaper"
+ android:permission="android.permission.BIND_WALLPAPER">
+
+ <intent-filter>
+ <action android:name="android.service.wallpaper.WallpaperService" />
+ </intent-filter>
+
+ <!-- Link to XML that defines the wallpaper info. -->
+ <meta-data android:name="android.service.wallpaper"
+ android:resource="@xml/livewallpaper" />
+ </service>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/Internal/res/xml/livewallpaper.xml b/tests/Internal/res/xml/livewallpaper.xml
new file mode 100644
index 0000000..dbb0e47
--- /dev/null
+++ b/tests/Internal/res/xml/livewallpaper.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<wallpaper
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ androidprv:supportsAmbientMode="true"/>
\ No newline at end of file
diff --git a/tests/Internal/src/android/app/WallpaperInfoTest.java b/tests/Internal/src/android/app/WallpaperInfoTest.java
new file mode 100644
index 0000000..9d26270
--- /dev/null
+++ b/tests/Internal/src/android/app/WallpaperInfoTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Parcel;
+import android.service.wallpaper.WallpaperService;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Tests for hidden WallpaperInfo methods.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WallpaperInfoTest {
+
+ @Test
+ public void testSupportsAmbientMode() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+
+ Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+ intent.setPackage("com.android.internal.tests");
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> result = pm.queryIntentServices(intent, PackageManager.GET_META_DATA);
+ assertEquals(1, result.size());
+ ResolveInfo info = result.get(0);
+ WallpaperInfo wallpaperInfo = new WallpaperInfo(context, info);
+
+ // Defined as true in the XML
+ assertTrue("supportsAmbientMode should be true, as defined in the XML.",
+ wallpaperInfo.getSupportsAmbientMode());
+ Parcel parcel = Parcel.obtain();
+ wallpaperInfo.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+ WallpaperInfo fromParcel = WallpaperInfo.CREATOR.createFromParcel(parcel);
+ assertTrue("supportsAmbientMode should have been restored from parcelable",
+ fromParcel.getSupportsAmbientMode());
+ parcel.recycle();
+ }
+}
+
diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
new file mode 100644
index 0000000..f7ce2c7
--- /dev/null
+++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.service.wallpaper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class WallpaperServiceTest {
+
+ @Test
+ public void testDeliversAmbientModeChanged() {
+ int[] ambientModeChangedCount = {0};
+ WallpaperService service = new WallpaperService() {
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine() {
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode) {
+ ambientModeChangedCount[0]++;
+ }
+ };
+ }
+ };
+ WallpaperService.Engine engine = service.onCreateEngine();
+ engine.setCreated(true);
+
+ engine.doAmbientModeChanged(false);
+ assertFalse("ambient mode should be false", engine.isInAmbientMode());
+ assertEquals("onAmbientModeChanged should have been called",
+ ambientModeChangedCount[0], 1);
+
+ engine.doAmbientModeChanged(true);
+ assertTrue("ambient mode should be false", engine.isInAmbientMode());
+ assertEquals("onAmbientModeChanged should have been called",
+ ambientModeChangedCount[0], 2);
+ }
+
+}
diff --git a/tests/Internal/src/stub/DummyWallpaperService.java b/tests/Internal/src/stub/DummyWallpaperService.java
new file mode 100644
index 0000000..084c036
--- /dev/null
+++ b/tests/Internal/src/stub/DummyWallpaperService.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package stub;
+
+import android.service.wallpaper.WallpaperService;
+
+/**
+ * Dummy wallpaper service only for test purposes, won't draw anything.
+ */
+public class DummyWallpaperService extends WallpaperService {
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+}
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index 8683c12..5d1e10e 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -27,6 +27,7 @@
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -40,10 +41,14 @@
import android.net.IpSecUdpEncapResponse;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.system.ErrnoException;
import android.system.Os;
+import android.system.StructStat;
+
+import dalvik.system.SocketTagger;
import java.io.FileDescriptor;
import java.net.InetAddress;
@@ -56,6 +61,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
/** Unit tests for {@link IpSecService}. */
@SmallTest
@@ -411,4 +417,84 @@
mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
}
}
+
+ @Test
+ public void testUidFdtagger() throws Exception {
+ SocketTagger actualSocketTagger = SocketTagger.get();
+
+ try {
+ FileDescriptor sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ // Has to be done after socket creation because BlockGuardOS calls tag on new sockets
+ SocketTagger mockSocketTagger = mock(SocketTagger.class);
+ SocketTagger.set(mockSocketTagger);
+
+ mIpSecService.mUidFdTagger.tag(sockFd, Process.LAST_APPLICATION_UID);
+ verify(mockSocketTagger).tag(eq(sockFd));
+ } finally {
+ SocketTagger.set(actualSocketTagger);
+ }
+ }
+
+ /**
+ * Checks if two file descriptors point to the same file.
+ *
+ * <p>According to stat.h documentation, the correct way to check for equivalent or duplicated
+ * file descriptors is to check their inode and device. These two entries uniquely identify any
+ * file.
+ */
+ private boolean fileDescriptorsEqual(FileDescriptor fd1, FileDescriptor fd2) {
+ try {
+ StructStat fd1Stat = Os.fstat(fd1);
+ StructStat fd2Stat = Os.fstat(fd2);
+
+ return fd1Stat.st_ino == fd2Stat.st_ino && fd1Stat.st_dev == fd2Stat.st_dev;
+ } catch (ErrnoException e) {
+ return false;
+ }
+ }
+
+ @Test
+ public void testOpenUdpEncapSocketTagsSocket() throws Exception {
+ IpSecService.UidFdTagger mockTagger = mock(IpSecService.UidFdTagger.class);
+ IpSecService testIpSecService =
+ new IpSecService(mMockContext, mMockIpSecSrvConfig, mockTagger);
+
+ IpSecUdpEncapResponse udpEncapResp =
+ testIpSecService.openUdpEncapsulationSocket(0, new Binder());
+ assertNotNull(udpEncapResp);
+ assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+
+ FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
+ ArgumentMatcher<FileDescriptor> fdMatcher =
+ (argFd) -> {
+ return fileDescriptorsEqual(sockFd, argFd);
+ };
+ verify(mockTagger).tag(argThat(fdMatcher), eq(Os.getuid()));
+
+ testIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+ udpEncapResp.fileDescriptor.close();
+ }
+
+ @Test
+ public void testOpenUdpEncapsulationSocketCallsSetEncapSocketOwner() throws Exception {
+ IpSecUdpEncapResponse udpEncapResp =
+ mIpSecService.openUdpEncapsulationSocket(0, new Binder());
+
+ FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
+ ArgumentMatcher<FileDescriptor> fdMatcher = (arg) -> {
+ try {
+ StructStat sockStat = Os.fstat(sockFd);
+ StructStat argStat = Os.fstat(arg);
+
+ return sockStat.st_ino == argStat.st_ino
+ && sockStat.st_dev == argStat.st_dev;
+ } catch (ErrnoException e) {
+ return false;
+ }
+ };
+
+ verify(mMockNetd).ipSecSetEncapSocketOwner(argThat(fdMatcher), eq(Os.getuid()));
+ mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+ }
}
diff --git a/tests/utils/testutils/Android.mk b/tests/utils/testutils/Android.mk
index 543c652..a76616f 100644
--- a/tests/utils/testutils/Android.mk
+++ b/tests/utils/testutils/Android.mk
@@ -24,9 +24,12 @@
LOCAL_SRC_FILES := $(call all-java-files-under,java)
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
- mockito-target-minus-junit4
+ android-support-test
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+ android.test.base \
+ android.test.mock \
+ mockito-target-minus-junit4 \
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 0f6fb50..5831875 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -294,36 +294,42 @@
printer->Print("/");
printer->Print(entry->name);
- switch (entry->symbol_status.state) {
- case SymbolState::kPublic:
+ switch (entry->visibility.level) {
+ case Visibility::Level::kPublic:
printer->Print(" PUBLIC");
break;
- case SymbolState::kPrivate:
+ case Visibility::Level::kPrivate:
printer->Print(" _PRIVATE_");
break;
- case SymbolState::kUndefined:
+ case Visibility::Level::kUndefined:
// Print nothing.
break;
}
+ if (entry->overlayable) {
+ printer->Print(" OVERLAYABLE");
+ }
+
printer->Println();
- printer->Indent();
- for (const auto& value : entry->values) {
- printer->Print("(");
- printer->Print(value->config.to_string());
- printer->Print(") ");
- value->value->Accept(&headline_printer);
- if (options.show_sources && !value->value->GetSource().path.empty()) {
- printer->Print(" src=");
- printer->Print(value->value->GetSource().to_string());
- }
- printer->Println();
+ if (options.show_values) {
printer->Indent();
- value->value->Accept(&body_printer);
+ for (const auto& value : entry->values) {
+ printer->Print("(");
+ printer->Print(value->config.to_string());
+ printer->Print(") ");
+ value->value->Accept(&headline_printer);
+ if (options.show_sources && !value->value->GetSource().path.empty()) {
+ printer->Print(" src=");
+ printer->Print(value->value->GetSource().to_string());
+ }
+ printer->Println();
+ printer->Indent();
+ value->value->Accept(&body_printer);
+ printer->Undent();
+ }
printer->Undent();
}
- printer->Undent();
}
printer->Undent();
}
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index 3c1ee4c..6209a04 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -29,6 +29,7 @@
struct DebugPrintTableOptions {
bool show_sources = false;
+ bool show_values = true;
};
struct Debug {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 4cc60a8..24b28dd 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -30,7 +30,7 @@
#include "util/Util.h"
#include "xml/XmlPullParser.h"
-using android::StringPiece;
+using ::android::StringPiece;
namespace aapt {
@@ -90,9 +90,12 @@
ConfigDescription config;
std::string product;
Source source;
+
ResourceId id;
- Maybe<SymbolState> symbol_state;
+ Visibility::Level visibility_level = Visibility::Level::kUndefined;
bool allow_new = false;
+ bool overlayable = false;
+
std::string comment;
std::unique_ptr<Value> value;
std::list<ParsedResource> child_resources;
@@ -106,24 +109,41 @@
res->comment = trimmed_comment.to_string();
}
- if (res->symbol_state) {
- Symbol symbol;
- symbol.state = res->symbol_state.value();
- symbol.source = res->source;
- symbol.comment = res->comment;
- symbol.allow_new = res->allow_new;
- if (!table->SetSymbolState(res->name, res->id, symbol, diag)) {
+ if (res->visibility_level != Visibility::Level::kUndefined) {
+ Visibility visibility;
+ visibility.level = res->visibility_level;
+ visibility.source = res->source;
+ visibility.comment = res->comment;
+ if (!table->SetVisibilityWithId(res->name, visibility, res->id, diag)) {
return false;
}
}
- if (res->value) {
+ if (res->allow_new) {
+ AllowNew allow_new;
+ allow_new.source = res->source;
+ allow_new.comment = res->comment;
+ if (!table->SetAllowNew(res->name, allow_new, diag)) {
+ return false;
+ }
+ }
+
+ if (res->overlayable) {
+ Overlayable overlayable;
+ overlayable.source = res->source;
+ overlayable.comment = res->comment;
+ if (!table->SetOverlayable(res->name, overlayable, diag)) {
+ return false;
+ }
+ }
+
+ if (res->value != nullptr) {
// Attach the comment, source and config to the value.
res->value->SetComment(std::move(res->comment));
res->value->SetSource(std::move(res->source));
- if (!table->AddResource(res->name, res->id, res->config, res->product, std::move(res->value),
- diag)) {
+ if (!table->AddResourceWithId(res->name, res->id, res->config, res->product,
+ std::move(res->value), diag)) {
return false;
}
}
@@ -601,8 +621,7 @@
// Process the raw value.
std::unique_ptr<Item> processed_item =
- ResourceUtils::TryParseItemForAttribute(raw_value, type_mask,
- on_create_reference);
+ ResourceUtils::TryParseItemForAttribute(raw_value, type_mask, on_create_reference);
if (processed_item) {
// Fix up the reference.
if (Reference* ref = ValueCast<Reference>(processed_item.get())) {
@@ -689,8 +708,7 @@
return true;
}
-bool ResourceParser::ParsePublic(xml::XmlPullParser* parser,
- ParsedResource* out_resource) {
+bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) {
if (out_resource->config != ConfigDescription::DefaultConfig()) {
diag_->Warn(DiagMessage(out_resource->source)
<< "ignoring configuration '" << out_resource->config << "' for <public> tag");
@@ -728,7 +746,7 @@
out_resource->value = util::make_unique<Id>();
}
- out_resource->symbol_state = SymbolState::kPublic;
+ out_resource->visibility_level = Visibility::Level::kPublic;
return true;
}
@@ -818,7 +836,7 @@
child_resource.id = next_id;
child_resource.comment = std::move(comment);
child_resource.source = item_source;
- child_resource.symbol_state = SymbolState::kPublic;
+ child_resource.visibility_level = Visibility::Level::kPublic;
out_resource->child_resources.push_back(std::move(child_resource));
next_id.id += 1;
@@ -864,7 +882,7 @@
return false;
}
- out_resource->symbol_state = SymbolState::kPrivate;
+ out_resource->visibility_level = Visibility::Level::kPrivate;
return true;
}
@@ -920,8 +938,12 @@
continue;
}
- // TODO(b/64980941): Mark the symbol as overlayable and allow marking which entity can overlay
- // the resource (system/app).
+ ParsedResource child_resource;
+ child_resource.name.type = *type;
+ child_resource.name.entry = maybe_name.value().to_string();
+ child_resource.source = item_source;
+ child_resource.overlayable = true;
+ out_resource->child_resources.push_back(std::move(child_resource));
xml::XmlPullParser::SkipCurrentElement(parser);
} else if (!ShouldIgnoreElement(element_namespace, element_name)) {
@@ -932,10 +954,9 @@
return !error;
}
-bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser,
- ParsedResource* out_resource) {
+bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) {
if (ParseSymbolImpl(parser, out_resource)) {
- out_resource->symbol_state = SymbolState::kUndefined;
+ out_resource->visibility_level = Visibility::Level::kUndefined;
out_resource->allow_new = true;
return true;
}
@@ -1395,9 +1416,8 @@
ParsedResource* out_resource) {
out_resource->name.type = ResourceType::kStyleable;
- // Declare-styleable is kPrivate by default, because it technically only
- // exists in R.java.
- out_resource->symbol_state = SymbolState::kPublic;
+ // Declare-styleable is kPrivate by default, because it technically only exists in R.java.
+ out_resource->visibility_level = Visibility::Level::kPublic;
// Declare-styleable only ends up in default config;
if (out_resource->config != ConfigDescription::DefaultConfig()) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 9a5cd3e..618c8ed 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -29,8 +29,8 @@
using ::aapt::io::StringInputStream;
using ::aapt::test::StrValueEq;
using ::aapt::test::ValueEq;
-using ::android::ResTable_map;
using ::android::Res_value;
+using ::android::ResTable_map;
using ::android::StringPiece;
using ::testing::Eq;
using ::testing::IsEmpty;
@@ -38,6 +38,7 @@
using ::testing::NotNull;
using ::testing::Pointee;
using ::testing::SizeIs;
+using ::testing::StrEq;
namespace aapt {
@@ -482,7 +483,7 @@
Maybe<ResourceTable::SearchResult> result =
table_.FindResource(test::ParseNameOrDie("styleable/foo"));
ASSERT_TRUE(result);
- EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));
+ EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
Attribute* attr = test::GetValue<Attribute>(&table_, "attr/bar");
ASSERT_THAT(attr, NotNull());
@@ -718,6 +719,26 @@
EXPECT_THAT(actual_id, Eq(ResourceId(0x01010041)));
}
+TEST_F(ResourceParserTest, StrongestSymbolVisibilityWins) {
+ std::string input = R"(
+ <!-- private -->
+ <java-symbol type="string" name="foo" />
+ <!-- public -->
+ <public type="string" name="foo" id="0x01020000" />
+ <!-- private2 -->
+ <java-symbol type="string" name="foo" />)";
+ ASSERT_TRUE(TestParse(input));
+
+ Maybe<ResourceTable::SearchResult> result =
+ table_.FindResource(test::ParseNameOrDie("string/foo"));
+ ASSERT_TRUE(result);
+
+ ResourceEntry* entry = result.value().entry;
+ ASSERT_THAT(entry, NotNull());
+ EXPECT_THAT(entry->visibility.level, Eq(Visibility::Level::kPublic));
+ EXPECT_THAT(entry->visibility.comment, StrEq("public"));
+}
+
TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) {
ASSERT_TRUE(TestParse(R"(<item type="layout" name="foo">@layout/bar</item>)"));
ASSERT_FALSE(TestParse(R"(<item type="layout" name="bar">"this is a string"</item>)"));
@@ -731,8 +752,8 @@
ASSERT_TRUE(result);
const ResourceEntry* entry = result.value().entry;
ASSERT_THAT(entry, NotNull());
- EXPECT_THAT(entry->symbol_status.state, Eq(SymbolState::kUndefined));
- EXPECT_TRUE(entry->symbol_status.allow_new);
+ EXPECT_THAT(entry->visibility.level, Eq(Visibility::Level::kUndefined));
+ EXPECT_TRUE(entry->allow_new);
}
TEST_F(ResourceParserTest, ParseItemElementWithFormat) {
@@ -833,6 +854,22 @@
<item type="string" name="bar" />
</overlayable>)";
ASSERT_TRUE(TestParse(input));
+
+ Maybe<ResourceTable::SearchResult> search_result =
+ table_.FindResource(test::ParseNameOrDie("string/bar"));
+ ASSERT_TRUE(search_result);
+ ASSERT_THAT(search_result.value().entry, NotNull());
+ EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
+ EXPECT_TRUE(search_result.value().entry->overlayable);
+}
+
+TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
+ std::string input = R"(
+ <overlayable>
+ <item type="string" name="foo" />
+ <item type="string" name="foo" />
+ </overlayable>)";
+ EXPECT_FALSE(TestParse(input));
}
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 0304e21..3172892 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -22,6 +22,7 @@
#include <tuple>
#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
#include "androidfw/ResourceTypes.h"
#include "ConfigDescription.h"
@@ -33,6 +34,7 @@
using ::aapt::text::IsValidResourceEntryName;
using ::android::StringPiece;
+using ::android::base::StringPrintf;
namespace aapt {
@@ -45,7 +47,7 @@
return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
}
-ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) {
+ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) const {
const auto last = packages.end();
auto iter = std::lower_bound(packages.begin(), last, name,
less_than_struct_with_name<ResourceTablePackage>);
@@ -55,7 +57,7 @@
return nullptr;
}
-ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) {
+ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) const {
for (auto& package : packages) {
if (package->id && package->id.value() == id) {
return package.get();
@@ -206,30 +208,23 @@
return results;
}
-/**
- * The default handler for collisions.
- *
- * Typically, a weak value will be overridden by a strong value. An existing
- * weak
- * value will not be overridden by an incoming weak value.
- *
- * There are some exceptions:
- *
- * Attributes: There are two types of Attribute values: USE and DECL.
- *
- * USE is anywhere an Attribute is declared without a format, and in a place
- * that would
- * be legal to declare if the Attribute already existed. This is typically in a
- * <declare-styleable> tag. Attributes defined in a <declare-styleable> are also
- * weak.
- *
- * DECL is an absolute declaration of an Attribute and specifies an explicit
- * format.
- *
- * A DECL will override a USE without error. Two DECLs must match in their
- * format for there to be
- * no error.
- */
+// The default handler for collisions.
+//
+// Typically, a weak value will be overridden by a strong value. An existing weak
+// value will not be overridden by an incoming weak value.
+//
+// There are some exceptions:
+//
+// Attributes: There are two types of Attribute values: USE and DECL.
+//
+// USE is anywhere an Attribute is declared without a format, and in a place that would
+// be legal to declare if the Attribute already existed. This is typically in a
+// <declare-styleable> tag. Attributes defined in a <declare-styleable> are also weak.
+//
+// DECL is an absolute declaration of an Attribute and specifies an explicit format.
+//
+// A DECL will override a USE without error. Two DECLs must match in their format for there to be
+// no error.
ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* existing,
Value* incoming) {
Attribute* existing_attr = ValueCast<Attribute>(existing);
@@ -287,14 +282,14 @@
return CollisionResult::kConflict;
}
-static StringPiece ValidateName(const StringPiece& name) {
+static StringPiece ResourceNameValidator(const StringPiece& name) {
if (!IsValidResourceEntryName(name)) {
return name;
}
return {};
}
-static StringPiece SkipValidateName(const StringPiece& /*name*/) {
+static StringPiece SkipNameValidator(const StringPiece& /*name*/) {
return {};
}
@@ -303,17 +298,14 @@
const StringPiece& product,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
- return AddResourceImpl(name, {}, config, product, std::move(value), ValidateName,
+ return AddResourceImpl(name, {}, config, product, std::move(value), ResourceNameValidator,
ResolveValueCollision, diag);
}
-bool ResourceTable::AddResource(const ResourceNameRef& name,
- const ResourceId& res_id,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag) {
- return AddResourceImpl(name, res_id, config, product, std::move(value), ValidateName,
+bool ResourceTable::AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id,
+ const ConfigDescription& config, const StringPiece& product,
+ std::unique_ptr<Value> value, IDiagnostics* diag) {
+ return AddResourceImpl(name, res_id, config, product, std::move(value), ResourceNameValidator,
ResolveValueCollision, diag);
}
@@ -322,14 +314,14 @@
const Source& source,
const StringPiece& path,
IDiagnostics* diag) {
- return AddFileReferenceImpl(name, config, source, path, nullptr, ValidateName, diag);
+ return AddFileReferenceImpl(name, config, source, path, nullptr, ResourceNameValidator, diag);
}
-bool ResourceTable::AddFileReferenceAllowMangled(
- const ResourceNameRef& name, const ConfigDescription& config,
- const Source& source, const StringPiece& path, io::IFile* file,
- IDiagnostics* diag) {
- return AddFileReferenceImpl(name, config, source, path, file, SkipValidateName, diag);
+bool ResourceTable::AddFileReferenceMangled(const ResourceNameRef& name,
+ const ConfigDescription& config, const Source& source,
+ const StringPiece& path, io::IFile* file,
+ IDiagnostics* diag) {
+ return AddFileReferenceImpl(name, config, source, path, file, SkipNameValidator, diag);
}
bool ResourceTable::AddFileReferenceImpl(const ResourceNameRef& name,
@@ -344,88 +336,85 @@
name_validator, ResolveValueCollision, diag);
}
-bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag) {
- return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), SkipValidateName,
+bool ResourceTable::AddResourceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+ const StringPiece& product, std::unique_ptr<Value> value,
+ IDiagnostics* diag) {
+ return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), SkipNameValidator,
ResolveValueCollision, diag);
}
-bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
- const ResourceId& id,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag) {
- return AddResourceImpl(name, id, config, product, std::move(value), SkipValidateName,
+bool ResourceTable::AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value, IDiagnostics* diag) {
+ return AddResourceImpl(name, id, config, product, std::move(value), SkipNameValidator,
ResolveValueCollision, diag);
}
+bool ResourceTable::ValidateName(NameValidator name_validator, const ResourceNameRef& name,
+ const Source& source, IDiagnostics* diag) {
+ const StringPiece bad_char = name_validator(name.entry);
+ if (!bad_char.empty()) {
+ diag->Error(DiagMessage(source) << "resource '" << name << "' has invalid entry name '"
+ << name.entry << "'. Invalid character '" << bad_char << "'");
+ return false;
+ }
+ return true;
+}
+
bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
const ConfigDescription& config, const StringPiece& product,
std::unique_ptr<Value> value, NameValidator name_validator,
- const CollisionResolverFunc& conflictResolver,
+ const CollisionResolverFunc& conflict_resolver,
IDiagnostics* diag) {
CHECK(value != nullptr);
CHECK(diag != nullptr);
- const StringPiece bad_char = name_validator(name.entry);
- if (!bad_char.empty()) {
- diag->Error(DiagMessage(value->GetSource()) << "resource '" << name
- << "' has invalid entry name '" << name.entry
- << "'. Invalid character '" << bad_char << "'");
-
+ const Source& source = value->GetSource();
+ if (!ValidateName(name_validator, name, source, diag)) {
return false;
}
ResourceTablePackage* package = FindOrCreatePackage(name.package);
if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
- diag->Error(DiagMessage(value->GetSource())
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but package '" << package->name << "' already has ID "
- << std::hex << (int)package->id.value() << std::dec);
+ diag->Error(DiagMessage(source) << "trying to add resource '" << name << "' with ID " << res_id
+ << " but package '" << package->name << "' already has ID "
+ << StringPrintf("%02x", package->id.value()));
return false;
}
ResourceTableType* type = package->FindOrCreateType(name.type);
if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) {
- diag->Error(DiagMessage(value->GetSource())
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but type '" << type->type << "' already has ID "
- << std::hex << (int)type->id.value() << std::dec);
+ diag->Error(DiagMessage(source)
+ << "trying to add resource '" << name << "' with ID " << res_id << " but type '"
+ << type->type << "' already has ID " << StringPrintf("%02x", type->id.value()));
return false;
}
ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) {
- diag->Error(DiagMessage(value->GetSource())
+ diag->Error(DiagMessage(source)
<< "trying to add resource '" << name << "' with ID " << res_id
<< " but resource already has ID "
- << ResourceId(package->id.value(), type->id.value(),
- entry->id.value()));
+ << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
return false;
}
ResourceConfigValue* config_value = entry->FindOrCreateValue(config, product);
- if (!config_value->value) {
+ if (config_value->value == nullptr) {
// Resource does not exist, add it now.
config_value->value = std::move(value);
-
} else {
- switch (conflictResolver(config_value->value.get(), value.get())) {
+ switch (conflict_resolver(config_value->value.get(), value.get())) {
case CollisionResult::kTakeNew:
// Take the incoming value.
config_value->value = std::move(value);
break;
case CollisionResult::kConflict:
- diag->Error(DiagMessage(value->GetSource())
- << "duplicate value for resource '" << name << "' "
- << "with config '" << config << "'");
- diag->Error(DiagMessage(config_value->value->GetSource())
- << "resource previously defined here");
+ diag->Error(DiagMessage(source) << "duplicate value for resource '" << name << "' "
+ << "with config '" << config << "'");
+ diag->Error(DiagMessage(source) << "resource previously defined here");
return false;
case CollisionResult::kKeepOriginal:
@@ -441,52 +430,56 @@
return true;
}
-bool ResourceTable::SetSymbolState(const ResourceNameRef& name, const ResourceId& res_id,
- const Symbol& symbol, IDiagnostics* diag) {
- return SetSymbolStateImpl(name, res_id, symbol, ValidateName, diag);
+bool ResourceTable::SetVisibility(const ResourceNameRef& name, const Visibility& visibility,
+ IDiagnostics* diag) {
+ return SetVisibilityImpl(name, visibility, ResourceId{}, ResourceNameValidator, diag);
}
-bool ResourceTable::SetSymbolStateAllowMangled(const ResourceNameRef& name,
- const ResourceId& res_id,
- const Symbol& symbol,
- IDiagnostics* diag) {
- return SetSymbolStateImpl(name, res_id, symbol, SkipValidateName, diag);
+bool ResourceTable::SetVisibilityMangled(const ResourceNameRef& name, const Visibility& visibility,
+ IDiagnostics* diag) {
+ return SetVisibilityImpl(name, visibility, ResourceId{}, SkipNameValidator, diag);
}
-bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
- const Symbol& symbol, NameValidator name_validator,
- IDiagnostics* diag) {
+bool ResourceTable::SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility,
+ const ResourceId& res_id, IDiagnostics* diag) {
+ return SetVisibilityImpl(name, visibility, res_id, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetVisibilityWithIdMangled(const ResourceNameRef& name,
+ const Visibility& visibility,
+ const ResourceId& res_id, IDiagnostics* diag) {
+ return SetVisibilityImpl(name, visibility, res_id, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility,
+ const ResourceId& res_id, NameValidator name_validator,
+ IDiagnostics* diag) {
CHECK(diag != nullptr);
- const StringPiece bad_char = name_validator(name.entry);
- if (!bad_char.empty()) {
- diag->Error(DiagMessage(symbol.source) << "resource '" << name << "' has invalid entry name '"
- << name.entry << "'. Invalid character '" << bad_char
- << "'");
+ const Source& source = visibility.source;
+ if (!ValidateName(name_validator, name, source, diag)) {
return false;
}
ResourceTablePackage* package = FindOrCreatePackage(name.package);
if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
- diag->Error(DiagMessage(symbol.source)
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but package '" << package->name << "' already has ID "
- << std::hex << (int)package->id.value() << std::dec);
+ diag->Error(DiagMessage(source) << "trying to add resource '" << name << "' with ID " << res_id
+ << " but package '" << package->name << "' already has ID "
+ << StringPrintf("%02x", package->id.value()));
return false;
}
ResourceTableType* type = package->FindOrCreateType(name.type);
if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) {
- diag->Error(DiagMessage(symbol.source)
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but type '" << type->type << "' already has ID "
- << std::hex << (int)type->id.value() << std::dec);
+ diag->Error(DiagMessage(source)
+ << "trying to add resource '" << name << "' with ID " << res_id << " but type '"
+ << type->type << "' already has ID " << StringPrintf("%02x", type->id.value()));
return false;
}
ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) {
- diag->Error(DiagMessage(symbol.source)
+ diag->Error(DiagMessage(source)
<< "trying to add resource '" << name << "' with ID " << res_id
<< " but resource already has ID "
<< ResourceId(package->id.value(), type->id.value(), entry->id.value()));
@@ -499,48 +492,96 @@
entry->id = res_id.entry_id();
}
- // Only mark the type state as public, it doesn't care about being private.
- if (symbol.state == SymbolState::kPublic) {
- type->symbol_status.state = SymbolState::kPublic;
+ // Only mark the type visibility level as public, it doesn't care about being private.
+ if (visibility.level == Visibility::Level::kPublic) {
+ type->visibility_level = Visibility::Level::kPublic;
}
- if (symbol.allow_new) {
- // This symbol can be added as a new resource when merging (if it belongs to an overlay).
- entry->symbol_status.allow_new = true;
- }
-
- if (symbol.state == SymbolState::kUndefined &&
- entry->symbol_status.state != SymbolState::kUndefined) {
+ if (visibility.level == Visibility::Level::kUndefined &&
+ entry->visibility.level != Visibility::Level::kUndefined) {
// We can't undefine a symbol (remove its visibility). Ignore.
return true;
}
- if (symbol.state == SymbolState::kPrivate &&
- entry->symbol_status.state == SymbolState::kPublic) {
+ if (visibility.level < entry->visibility.level) {
// We can't downgrade public to private. Ignore.
return true;
}
// This symbol definition takes precedence, replace.
- entry->symbol_status.state = symbol.state;
- entry->symbol_status.source = symbol.source;
- entry->symbol_status.comment = symbol.comment;
+ entry->visibility = visibility;
return true;
}
-Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) {
+bool ResourceTable::SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new,
+ IDiagnostics* diag) {
+ return SetAllowNewImpl(name, allow_new, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new,
+ IDiagnostics* diag) {
+ return SetAllowNewImpl(name, allow_new, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
+ NameValidator name_validator, IDiagnostics* diag) {
+ CHECK(diag != nullptr);
+
+ if (!ValidateName(name_validator, name, allow_new.source, diag)) {
+ return false;
+ }
+
+ ResourceTablePackage* package = FindOrCreatePackage(name.package);
+ ResourceTableType* type = package->FindOrCreateType(name.type);
+ ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+ entry->allow_new = allow_new;
+ return true;
+}
+
+bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+ IDiagnostics* diag) {
+ return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetOverlayableMangled(const ResourceNameRef& name,
+ const Overlayable& overlayable, IDiagnostics* diag) {
+ return SetOverlayableImpl(name, overlayable, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
+ NameValidator name_validator, IDiagnostics* diag) {
+ CHECK(diag != nullptr);
+
+ if (!ValidateName(name_validator, name, overlayable.source, diag)) {
+ return false;
+ }
+
+ ResourceTablePackage* package = FindOrCreatePackage(name.package);
+ ResourceTableType* type = package->FindOrCreateType(name.type);
+ ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+ if (entry->overlayable) {
+ diag->Error(DiagMessage(overlayable.source)
+ << "duplicate overlayable declaration for resource '" << name << "'");
+ diag->Error(DiagMessage(entry->overlayable.value().source) << "previous declaration here");
+ return false;
+ }
+ entry->overlayable = overlayable;
+ return true;
+}
+
+Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) const {
ResourceTablePackage* package = FindPackage(name.package);
- if (!package) {
+ if (package == nullptr) {
return {};
}
ResourceTableType* type = package->FindType(name.type);
- if (!type) {
+ if (type == nullptr) {
return {};
}
ResourceEntry* entry = type->FindEntry(name.entry);
- if (!entry) {
+ if (entry == nullptr) {
return {};
}
return SearchResult{package, type, entry};
@@ -552,23 +593,20 @@
ResourceTablePackage* new_pkg = new_table->CreatePackage(pkg->name, pkg->id);
for (const auto& type : pkg->types) {
ResourceTableType* new_type = new_pkg->FindOrCreateType(type->type);
- if (!new_type->id) {
- new_type->id = type->id;
- new_type->symbol_status = type->symbol_status;
- }
+ new_type->id = type->id;
+ new_type->visibility_level = type->visibility_level;
for (const auto& entry : type->entries) {
ResourceEntry* new_entry = new_type->FindOrCreateEntry(entry->name);
- if (!new_entry->id) {
- new_entry->id = entry->id;
- new_entry->symbol_status = entry->symbol_status;
- }
+ new_entry->id = entry->id;
+ new_entry->visibility = entry->visibility;
+ new_entry->allow_new = entry->allow_new;
+ new_entry->overlayable = entry->overlayable;
for (const auto& config_value : entry->values) {
ResourceConfigValue* new_value =
new_entry->FindOrCreateValue(config_value->config, config_value->product);
- Value* value = config_value->value->Clone(&new_table->string_pool);
- new_value->value = std::unique_ptr<Value>(value);
+ new_value->value.reset(config_value->value->Clone(&new_table->string_pool));
}
}
}
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index d5db67e..eaa2d0b 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -38,40 +38,40 @@
namespace aapt {
-enum class SymbolState {
- kUndefined,
- kPrivate,
- kPublic,
+// The Public status of a resource.
+struct Visibility {
+ enum class Level {
+ kUndefined,
+ kPrivate,
+ kPublic,
+ };
+
+ Level level = Level::kUndefined;
+ Source source;
+ std::string comment;
};
-/**
- * The Public status of a resource.
- */
-struct Symbol {
- SymbolState state = SymbolState::kUndefined;
+// Represents <add-resource> in an overlay.
+struct AllowNew {
Source source;
+ std::string comment;
+};
- // Whether this entry (originating from an overlay) can be added as a new resource.
- bool allow_new = false;
-
+// The policy dictating whether an entry is overlayable at runtime by RROs.
+struct Overlayable {
+ Source source;
std::string comment;
};
class ResourceConfigValue {
public:
- /**
- * The configuration for which this value is defined.
- */
+ // The configuration for which this value is defined.
const ConfigDescription config;
- /**
- * The product for which this value is defined.
- */
+ // The product for which this value is defined.
const std::string product;
- /**
- * The actual Value.
- */
+ // The actual Value.
std::unique_ptr<Value> value;
ResourceConfigValue(const ConfigDescription& config, const android::StringPiece& product)
@@ -87,27 +87,21 @@
*/
class ResourceEntry {
public:
- /**
- * The name of the resource. Immutable, as
- * this determines the order of this resource
- * when doing lookups.
- */
+ // The name of the resource. Immutable, as this determines the order of this resource
+ // when doing lookups.
const std::string name;
- /**
- * The entry ID for this resource.
- */
+ // The entry ID for this resource (the EEEE in 0xPPTTEEEE).
Maybe<uint16_t> id;
- /**
- * Whether this resource is public (and must maintain the same entry ID across
- * builds).
- */
- Symbol symbol_status;
+ // Whether this resource is public (and must maintain the same entry ID across builds).
+ Visibility visibility;
- /**
- * The resource's values for each configuration.
- */
+ Maybe<AllowNew> allow_new;
+
+ Maybe<Overlayable> overlayable;
+
+ // The resource's values for each configuration.
std::vector<std::unique_ptr<ResourceConfigValue>> values;
explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {}
@@ -125,31 +119,19 @@
DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
};
-/**
- * Represents a resource type, which holds entries defined
- * for this type.
- */
+// Represents a resource type (eg. string, drawable, layout, etc.) containing resource entries.
class ResourceTableType {
public:
- /**
- * The logical type of resource (string, drawable, layout, etc.).
- */
+ // The logical type of resource (string, drawable, layout, etc.).
const ResourceType type;
- /**
- * The type ID for this resource.
- */
+ // The type ID for this resource (the TT in 0xPPTTEEEE).
Maybe<uint8_t> id;
- /**
- * Whether this type is public (and must maintain the same
- * type ID across builds).
- */
- Symbol symbol_status;
+ // Whether this type is public (and must maintain the same type ID across builds).
+ Visibility::Level visibility_level = Visibility::Level::kUndefined;
- /**
- * List of resources for this type.
- */
+ // List of resources for this type.
std::vector<std::unique_ptr<ResourceEntry>> entries;
explicit ResourceTableType(const ResourceType type) : type(type) {}
@@ -163,9 +145,11 @@
class ResourceTablePackage {
public:
- Maybe<uint8_t> id;
std::string name;
+ // The package ID (the PP in 0xPPTTEEEE).
+ Maybe<uint8_t> id;
+
std::vector<std::unique_ptr<ResourceTableType>> types;
ResourceTablePackage() = default;
@@ -176,10 +160,7 @@
DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
};
-/**
- * The container and index for all resources defined for an app. This gets
- * flattened into a binary resource table (resources.arsc).
- */
+// The container and index for all resources defined for an app.
class ResourceTable {
public:
ResourceTable() = default;
@@ -188,47 +169,51 @@
using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
- /**
- * When a collision of resources occurs, this method decides which value to
- * keep.
- */
+ // When a collision of resources occurs, this method decides which value to keep.
static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);
bool AddResource(const ResourceNameRef& name, const ConfigDescription& config,
const android::StringPiece& product, std::unique_ptr<Value> value,
IDiagnostics* diag);
- bool AddResource(const ResourceNameRef& name, const ResourceId& res_id,
- const ConfigDescription& config, const android::StringPiece& product,
- std::unique_ptr<Value> value, IDiagnostics* diag);
+ bool AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id,
+ const ConfigDescription& config, const android::StringPiece& product,
+ std::unique_ptr<Value> value, IDiagnostics* diag);
bool AddFileReference(const ResourceNameRef& name, const ConfigDescription& config,
const Source& source, const android::StringPiece& path, IDiagnostics* diag);
- bool AddFileReferenceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
- const Source& source, const android::StringPiece& path,
- io::IFile* file, IDiagnostics* diag);
+ bool AddFileReferenceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+ const Source& source, const android::StringPiece& path,
+ io::IFile* file, IDiagnostics* diag);
- /**
- * Same as AddResource, but doesn't verify the validity of the name. This is
- * used
- * when loading resources from an existing binary resource table that may have
- * mangled
- * names.
- */
- bool AddResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
- const android::StringPiece& product, std::unique_ptr<Value> value,
- IDiagnostics* diag);
+ // Same as AddResource, but doesn't verify the validity of the name. This is used
+ // when loading resources from an existing binary resource table that may have mangled names.
+ bool AddResourceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::StringPiece& product, std::unique_ptr<Value> value,
+ IDiagnostics* diag);
- bool AddResourceAllowMangled(const ResourceNameRef& name, const ResourceId& id,
- const ConfigDescription& config, const android::StringPiece& product,
- std::unique_ptr<Value> value, IDiagnostics* diag);
+ bool AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id,
+ const ConfigDescription& config,
+ const android::StringPiece& product, std::unique_ptr<Value> value,
+ IDiagnostics* diag);
- bool SetSymbolState(const ResourceNameRef& name, const ResourceId& res_id,
- const Symbol& symbol, IDiagnostics* diag);
+ bool SetVisibility(const ResourceNameRef& name, const Visibility& visibility, IDiagnostics* diag);
+ bool SetVisibilityMangled(const ResourceNameRef& name, const Visibility& visibility,
+ IDiagnostics* diag);
+ bool SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility,
+ const ResourceId& res_id, IDiagnostics* diag);
+ bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility,
+ const ResourceId& res_id, IDiagnostics* diag);
- bool SetSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId& res_id,
- const Symbol& symbol, IDiagnostics* diag);
+ bool SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+ IDiagnostics* diag);
+ bool SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
+ IDiagnostics* diag);
+
+ bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag);
+ bool SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new,
+ IDiagnostics* diag);
struct SearchResult {
ResourceTablePackage* package;
@@ -236,40 +221,28 @@
ResourceEntry* entry;
};
- Maybe<SearchResult> FindResource(const ResourceNameRef& name);
+ Maybe<SearchResult> FindResource(const ResourceNameRef& name) const;
- /**
- * Returns the package struct with the given name, or nullptr if such a
- * package does not
- * exist. The empty string is a valid package and typically is used to
- * represent the
- * 'current' package before it is known to the ResourceTable.
- */
- ResourceTablePackage* FindPackage(const android::StringPiece& name);
+ // Returns the package struct with the given name, or nullptr if such a package does not
+ // exist. The empty string is a valid package and typically is used to represent the
+ // 'current' package before it is known to the ResourceTable.
+ ResourceTablePackage* FindPackage(const android::StringPiece& name) const;
- ResourceTablePackage* FindPackageById(uint8_t id);
+ ResourceTablePackage* FindPackageById(uint8_t id) const;
ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {});
std::unique_ptr<ResourceTable> Clone() const;
- /**
- * The string pool used by this resource table. Values that reference strings
- * must use
- * this pool to create their strings.
- *
- * NOTE: `string_pool` must come before `packages` so that it is destroyed
- * after.
- * When `string_pool` references are destroyed (as they will be when
- * `packages`
- * is destroyed), they decrement a refCount, which would cause invalid
- * memory access if the pool was already destroyed.
- */
+ // The string pool used by this resource table. Values that reference strings must use
+ // this pool to create their strings.
+ // NOTE: `string_pool` must come before `packages` so that it is destroyed after.
+ // When `string_pool` references are destroyed (as they will be when `packages` is destroyed),
+ // they decrement a refCount, which would cause invalid memory access if the pool was already
+ // destroyed.
StringPool string_pool;
- /**
- * The list of packages in this table, sorted alphabetically by package name.
- */
+ // The list of packages in this table, sorted alphabetically by package name.
std::vector<std::unique_ptr<ResourceTablePackage>> packages;
// Set of dynamic packages that this table may reference. Their package names get encoded
@@ -284,6 +257,9 @@
ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
+ bool ValidateName(NameValidator validator, const ResourceNameRef& name, const Source& source,
+ IDiagnostics* diag);
+
bool AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
const ConfigDescription& config, const android::StringPiece& product,
std::unique_ptr<Value> value, NameValidator name_validator,
@@ -293,8 +269,19 @@
const Source& source, const android::StringPiece& path, io::IFile* file,
NameValidator name_validator, IDiagnostics* diag);
+ bool SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility,
+ const ResourceId& res_id, NameValidator name_validator,
+ IDiagnostics* diag);
+
+ bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
+ NameValidator name_validator, IDiagnostics* diag);
+
+ bool SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
+ NameValidator name_validator, IDiagnostics* diag);
+
bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
- const Symbol& symbol, NameValidator name_validator, IDiagnostics* diag);
+ const Visibility& symbol, NameValidator name_validator,
+ IDiagnostics* diag);
DISALLOW_COPY_AND_ASSIGN(ResourceTable);
};
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 2a3c131..eb75f94 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -24,7 +24,10 @@
#include <ostream>
#include <string>
+using ::android::StringPiece;
+using ::testing::Eq;
using ::testing::NotNull;
+using ::testing::StrEq;
namespace aapt {
@@ -45,7 +48,7 @@
TEST(ResourceTableTest, AddResourceWithWeirdNameWhenAddingMangledResources) {
ResourceTable table;
- EXPECT_TRUE(table.AddResourceAllowMangled(
+ EXPECT_TRUE(table.AddResourceMangled(
test::ParseNameOrDie("android:id/heythere "), ConfigDescription{}, "",
test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(), test::GetDiagnostics()));
}
@@ -141,4 +144,104 @@
EXPECT_EQ(std::string("tablet"), values[1]->product);
}
+static StringPiece LevelToString(Visibility::Level level) {
+ switch (level) {
+ case Visibility::Level::kPrivate:
+ return "private";
+ case Visibility::Level::kPublic:
+ return "private";
+ default:
+ return "undefined";
+ }
+}
+
+static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& table,
+ const ResourceNameRef& name,
+ Visibility::Level level,
+ const StringPiece& comment) {
+ Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
+ if (!result) {
+ return ::testing::AssertionFailure() << "no resource '" << name << "' found in table";
+ }
+
+ const Visibility& visibility = result.value().entry->visibility;
+ if (visibility.level != level) {
+ return ::testing::AssertionFailure() << "expected visibility " << LevelToString(level)
+ << " but got " << LevelToString(visibility.level);
+ }
+
+ if (visibility.comment != comment) {
+ return ::testing::AssertionFailure() << "expected visibility comment '" << comment
+ << "' but got '" << visibility.comment << "'";
+ }
+ return ::testing::AssertionSuccess();
+}
+
+TEST(ResourceTableTest, SetVisibility) {
+ using Level = Visibility::Level;
+
+ ResourceTable table;
+ const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+ Visibility visibility;
+ visibility.level = Visibility::Level::kPrivate;
+ visibility.comment = "private";
+ ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
+
+ visibility.level = Visibility::Level::kUndefined;
+ visibility.comment = "undefined";
+ ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
+
+ visibility.level = Visibility::Level::kPublic;
+ visibility.comment = "public";
+ ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
+
+ visibility.level = Visibility::Level::kPrivate;
+ visibility.comment = "private";
+ ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
+}
+
+TEST(ResourceTableTest, SetAllowNew) {
+ ResourceTable table;
+ const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+ AllowNew allow_new;
+ Maybe<ResourceTable::SearchResult> result;
+
+ allow_new.comment = "first";
+ ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics()));
+ result = table.FindResource(name);
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result.value().entry->allow_new);
+ ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("first"));
+
+ allow_new.comment = "second";
+ ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics()));
+ result = table.FindResource(name);
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result.value().entry->allow_new);
+ ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("second"));
+}
+
+TEST(ResourceTableTest, SetOverlayable) {
+ ResourceTable table;
+ const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+ Overlayable overlayable;
+
+ overlayable.comment = "first";
+ ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
+ Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result.value().entry->overlayable);
+ ASSERT_THAT(result.value().entry->overlayable.value().comment, StrEq("first"));
+
+ overlayable.comment = "second";
+ ASSERT_FALSE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 7e7c86d..8552195 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -93,11 +93,10 @@
repeated Entry entry = 3;
}
-// The status of a symbol/entry. This contains information like visibility (public/private),
-// comments, and whether the entry can be overridden.
-message SymbolStatus {
+// The Visibility of a symbol/entry (public, private, undefined).
+message Visibility {
// The visibility of the resource outside of its package.
- enum Visibility {
+ enum Level {
// No visibility was explicitly specified. This is typically treated as private.
// The distinction is important when two separate R.java files are generated: a public and
// private one. An unknown visibility, in this case, would cause the resource to be omitted
@@ -115,17 +114,32 @@
PUBLIC = 2;
}
- Visibility visibility = 1;
+ Level level = 1;
// The path at which this entry's visibility was defined (eg. public.xml).
Source source = 2;
// The comment associated with the <public> tag.
string comment = 3;
+}
- // Whether the symbol can be merged into another resource table without there being an existing
- // definition to override. Used for overlays and set to true when <add-resource> is specified.
- bool allow_new = 4;
+// Whether a resource comes from a compile-time overlay and is explicitly allowed to not overlay an
+// existing resource.
+message AllowNew {
+ // Where this was defined in source.
+ Source source = 1;
+
+ // Any comment associated with the declaration.
+ string comment = 2;
+}
+
+// Whether a resource is overlayable by runtime resource overlays (RRO).
+message Overlayable {
+ // Where this declaration was defined in source.
+ Source source = 1;
+
+ // Any comment associated with the declaration.
+ string comment = 2;
}
// An entry ID in the range [0x0000, 0xffff].
@@ -147,12 +161,19 @@
// form package:type/entry.
string name = 2;
- // The symbol status of this entry, which includes visibility information.
- SymbolStatus symbol_status = 3;
+ // The visibility of this entry (public, private, undefined).
+ Visibility visibility = 3;
+
+ // Whether this resource, when originating from a compile-time overlay, is allowed to NOT overlay
+ // any existing resources.
+ AllowNew allow_new = 4;
+
+ // Whether this resource can be overlaid by a runtime resource overlay (RRO).
+ Overlayable overlayable = 5;
// The set of values defined for this entry, each corresponding to a different
// configuration/variant.
- repeated ConfigValue config_value = 4;
+ repeated ConfigValue config_value = 6;
}
// A Configuration/Value pair.
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 83512b9..7c1e96e 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -49,6 +49,7 @@
#include "xml/XmlPullParser.h"
using ::aapt::io::FileInputStream;
+using ::aapt::text::Printer;
using ::android::StringPiece;
using ::android::base::SystemErrorCodeToString;
using ::google::protobuf::io::CopyingOutputStreamAdaptor;
@@ -112,6 +113,7 @@
struct CompileOptions {
std::string output_path;
Maybe<std::string> res_dir;
+ Maybe<std::string> generate_text_symbols_path;
bool pseudolocalize = false;
bool no_png_crunch = false;
bool legacy_mode = false;
@@ -261,6 +263,58 @@
context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
return false;
}
+
+ if (options.generate_text_symbols_path) {
+ io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+
+ if (fout_text.HadError()) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "failed writing to'"
+ << options.generate_text_symbols_path.value()
+ << "': " << fout_text.GetError());
+ return false;
+ }
+
+ Printer r_txt_printer(&fout_text);
+ for (const auto& package : table.packages) {
+ for (const auto& type : package->types) {
+ for (const auto& entry : type->entries) {
+ // Check access modifiers.
+ switch(entry->visibility.level) {
+ case Visibility::Level::kUndefined :
+ r_txt_printer.Print("default ");
+ break;
+ case Visibility::Level::kPublic :
+ r_txt_printer.Print("public ");
+ break;
+ case Visibility::Level::kPrivate :
+ r_txt_printer.Print("private ");
+ }
+
+ if (type->type != ResourceType::kStyleable) {
+ r_txt_printer.Print("int ");
+ r_txt_printer.Print(to_string(type->type));
+ r_txt_printer.Print(" ");
+ r_txt_printer.Println(entry->name);
+ } else {
+ r_txt_printer.Print("int[] styleable ");
+ r_txt_printer.Println(entry->name);
+
+ if (!entry->values.empty()) {
+ auto styleable = static_cast<const Styleable*>(entry->values.front()->value.get());
+ for (const auto& attr : styleable->entries) {
+ r_txt_printer.Print("default int styleable ");
+ r_txt_printer.Print(entry->name);
+ r_txt_printer.Print("_");
+ r_txt_printer.Println(attr.name.value().entry);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
return true;
}
@@ -402,6 +456,31 @@
context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data");
return false;
}
+
+ if (options.generate_text_symbols_path) {
+ io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+
+ if (fout_text.HadError()) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "failed writing to'"
+ << options.generate_text_symbols_path.value()
+ << "': " << fout_text.GetError());
+ return false;
+ }
+
+ Printer r_txt_printer(&fout_text);
+ for (const auto res : xmlres->file.exported_symbols) {
+ r_txt_printer.Print("default int id ");
+ r_txt_printer.Println(res.name.entry);
+ }
+
+ // And print ourselves.
+ r_txt_printer.Print("default int ");
+ r_txt_printer.Print(path_data.resource_dir);
+ r_txt_printer.Print(" ");
+ r_txt_printer.Println(path_data.name);
+ }
+
return true;
}
@@ -609,6 +688,10 @@
Flags()
.RequiredFlag("-o", "Output path", &options.output_path)
.OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir)
+ .OptionalFlag("--output-text-symbols",
+ "Generates a text file containing the resource symbols in the\n"
+ "specified file",
+ &options.generate_text_symbols_path)
.OptionalSwitch("--pseudo-localize",
"Generate resources for pseudo-locales "
"(en-XA and ar-XB)",
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index fc1f1d6..12113ed 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -75,14 +75,14 @@
std::cerr << source << ": " << message << "\n";
}
-static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a, const Symbol& symbol_b) {
- return symbol_a.state != symbol_b.state;
+static bool IsSymbolVisibilityDifferent(const Visibility& vis_a, const Visibility& vis_b) {
+ return vis_a.level != vis_b.level;
}
template <typename Id>
-static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a, const Symbol& symbol_b,
- const Maybe<Id>& id_b) {
- if (symbol_a.state == SymbolState::kPublic || symbol_b.state == SymbolState::kPublic) {
+static bool IsIdDiff(const Visibility::Level& level_a, const Maybe<Id>& id_a,
+ const Visibility::Level& level_b, const Maybe<Id>& id_b) {
+ if (level_a == Visibility::Level::kPublic || level_b == Visibility::Level::kPublic) {
return id_a != id_b;
}
return false;
@@ -157,17 +157,17 @@
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else {
- if (IsSymbolVisibilityDifferent(entry_a->symbol_status, entry_b->symbol_status)) {
+ if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
<< " has different visibility (";
- if (entry_b->symbol_status.state == SymbolState::kPublic) {
+ if (entry_b->visibility.level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
}
str_stream << " vs ";
- if (entry_a->symbol_status.state == SymbolState::kPublic) {
+ if (entry_a->visibility.level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
@@ -175,7 +175,7 @@
str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
- } else if (IsIdDiff(entry_a->symbol_status, entry_a->id, entry_b->symbol_status,
+ } else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level,
entry_b->id)) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
@@ -225,16 +225,16 @@
EmitDiffLine(apk_a->GetSource(), str_stream.str());
diff = true;
} else {
- if (IsSymbolVisibilityDifferent(type_a->symbol_status, type_b->symbol_status)) {
+ if (type_a->visibility_level != type_b->visibility_level) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type << " has different visibility (";
- if (type_b->symbol_status.state == SymbolState::kPublic) {
+ if (type_b->visibility_level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
}
str_stream << " vs ";
- if (type_a->symbol_status.state == SymbolState::kPublic) {
+ if (type_a->visibility_level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
@@ -242,7 +242,8 @@
str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
- } else if (IsIdDiff(type_a->symbol_status, type_a->id, type_b->symbol_status, type_b->id)) {
+ } else if (IsIdDiff(type_a->visibility_level, type_a->id, type_b->visibility_level,
+ type_b->id)) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type << " has different public ID (";
if (type_b->id) {
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index bc7f5a8..3d2fb55 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -69,15 +69,13 @@
printer->Println(StringPrintf("Data: offset=%" PRIi64 " length=%zd", offset, len));
}
-static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
+static bool TryDumpFile(IAaptContext* context, const std::string& file_path,
+ const DebugPrintTableOptions& print_options) {
// Use a smaller buffer so that there is less latency for dumping to stdout.
constexpr size_t kStdOutBufferSize = 1024u;
io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
Printer printer(&fout);
- DebugPrintTableOptions print_options;
- print_options.show_sources = true;
-
std::string err;
std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err);
if (zip) {
@@ -244,7 +242,12 @@
// Entry point for dump command.
int Dump(const std::vector<StringPiece>& args) {
bool verbose = false;
- Flags flags = Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose);
+ bool no_values = false;
+ Flags flags = Flags()
+ .OptionalSwitch("--no-values",
+ "Suppresses output of values when displaying resource tables.",
+ &no_values)
+ .OptionalSwitch("-v", "increase verbosity of output", &verbose);
if (!flags.Parse("aapt2 dump", args, &std::cerr)) {
return 1;
}
@@ -252,8 +255,11 @@
DumpContext context;
context.SetVerbose(verbose);
+ DebugPrintTableOptions dump_table_options;
+ dump_table_options.show_sources = true;
+ dump_table_options.show_values = !no_values;
for (const std::string& arg : flags.GetArgs()) {
- if (!TryDumpFile(&context, arg)) {
+ if (!TryDumpFile(&context, arg, dump_table_options)) {
return 1;
}
}
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index d782de5..72e07dc 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -631,9 +631,9 @@
dst_path =
ResourceUtils::BuildResourceFileName(doc->file, context_->GetNameMangler());
- bool result = table->AddFileReferenceAllowMangled(doc->file.name, doc->file.config,
- doc->file.source, dst_path, nullptr,
- context_->GetDiagnostics());
+ bool result =
+ table->AddFileReferenceMangled(doc->file.name, doc->file.config, doc->file.source,
+ dst_path, nullptr, context_->GetDiagnostics());
if (!result) {
return false;
}
@@ -1343,9 +1343,9 @@
std::unique_ptr<Id> id = util::make_unique<Id>();
id->SetSource(source.WithLine(exported_symbol.line));
- bool result = final_table_.AddResourceAllowMangled(
- res_name, ConfigDescription::DefaultConfig(), std::string(), std::move(id),
- context_->GetDiagnostics());
+ bool result =
+ final_table_.AddResourceMangled(res_name, ConfigDescription::DefaultConfig(),
+ std::string(), std::move(id), context_->GetDiagnostics());
if (!result) {
return false;
}
@@ -2121,6 +2121,9 @@
&options.manifest_fixer_options.rename_instrumentation_target_package)
.OptionalFlagList("-0", "File extensions not to compress.",
&options.extensions_to_not_compress)
+ .OptionalSwitch("--warn-manifest-validation",
+ "Treat manifest validation errors as warnings.",
+ &options.manifest_fixer_options.warn_validation)
.OptionalFlagList("--split",
"Split resources matching a set of configs out to a Split APK.\n"
"Syntax: path/to/output.apk:<config>[,<config>[...]].\n"
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index d8bb999..9c76119 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -377,44 +377,10 @@
}
const std::string& apk_path = flags.GetArgs()[0];
- std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
- if (!apk) {
- return 1;
- }
context.SetVerbose(verbose);
IDiagnostics* diag = context.GetDiagnostics();
- if (target_densities) {
- // Parse the target screen densities.
- for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
- Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
- if (!target_density) {
- return 1;
- }
- options.table_splitter_options.preferred_densities.push_back(target_density.value());
- }
- }
-
- std::unique_ptr<IConfigFilter> filter;
- if (!configs.empty()) {
- filter = ParseConfigFilterParameters(configs, diag);
- if (filter == nullptr) {
- return 1;
- }
- options.table_splitter_options.config_filter = filter.get();
- }
-
- // Parse the split parameters.
- for (const std::string& split_arg : split_args) {
- options.split_paths.emplace_back();
- options.split_constraints.emplace_back();
- if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
- &options.split_constraints.back())) {
- return 1;
- }
- }
-
if (config_path) {
std::string& path = config_path.value();
Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
@@ -456,6 +422,41 @@
return 1;
}
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
+ if (!apk) {
+ return 1;
+ }
+
+ if (target_densities) {
+ // Parse the target screen densities.
+ for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
+ Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
+ if (!target_density) {
+ return 1;
+ }
+ options.table_splitter_options.preferred_densities.push_back(target_density.value());
+ }
+ }
+
+ std::unique_ptr<IConfigFilter> filter;
+ if (!configs.empty()) {
+ filter = ParseConfigFilterParameters(configs, diag);
+ if (filter == nullptr) {
+ return 1;
+ }
+ options.table_splitter_options.config_filter = filter.get();
+ }
+
+ // Parse the split parameters.
+ for (const std::string& split_arg : split_args) {
+ options.split_paths.emplace_back();
+ options.split_constraints.emplace_back();
+ if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
+ &options.split_constraints.back())) {
+ return 1;
+ }
+ }
+
if (options.table_flattener_options.collapse_key_stringpool) {
if (whitelist_path) {
std::string& path = whitelist_path.value();
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index ebc523f..eabeb47 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -49,13 +49,15 @@
using ::aapt::configuration::ConfiguredArtifact;
using ::aapt::configuration::DeviceFeature;
using ::aapt::configuration::Entry;
+using ::aapt::configuration::ExtractConfiguration;
using ::aapt::configuration::GlTexture;
using ::aapt::configuration::Group;
using ::aapt::configuration::Locale;
+using ::aapt::configuration::OrderedEntry;
using ::aapt::configuration::OutputArtifact;
using ::aapt::configuration::PostProcessingConfiguration;
using ::aapt::configuration::handler::AbiGroupTagHandler;
-using ::aapt::configuration::handler::AndroidSdkGroupTagHandler;
+using ::aapt::configuration::handler::AndroidSdkTagHandler;
using ::aapt::configuration::handler::ArtifactFormatTagHandler;
using ::aapt::configuration::handler::ArtifactTagHandler;
using ::aapt::configuration::handler::DeviceFeatureGroupTagHandler;
@@ -130,7 +132,7 @@
return false;
}
- for (const T& item : group->second) {
+ for (const T& item : group->second.entry) {
target->push_back(item);
}
return true;
@@ -188,61 +190,6 @@
};
}
-/** Returns the binary reprasentation of the XML configuration. */
-Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
- IDiagnostics* diag) {
- StringInputStream in(contents);
- std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source("config.xml"));
- if (!doc) {
- return {};
- }
-
- // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
- Element* root = doc->root.get();
- if (root == nullptr) {
- diag->Error(DiagMessage() << "Could not find the root element in the XML document");
- return {};
- }
-
- std::string& xml_ns = root->namespace_uri;
- if (!xml_ns.empty()) {
- if (xml_ns != kAaptXmlNs) {
- diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
- return {};
- }
-
- xml_ns.clear();
- NamespaceVisitor visitor;
- root->Accept(&visitor);
- }
-
- XmlActionExecutor executor;
- XmlNodeAction& root_action = executor["post-process"];
- XmlNodeAction& artifacts_action = root_action["artifacts"];
- XmlNodeAction& groups_action = root_action["groups"];
-
- PostProcessingConfiguration config;
-
- // Parse the artifact elements.
- artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
- artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
-
- // Parse the different configuration groups.
- groups_action["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
- groups_action["screen-density-group"].Action(Bind(&config, ScreenDensityGroupTagHandler));
- groups_action["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
- groups_action["android-sdk-group"].Action(Bind(&config, AndroidSdkGroupTagHandler));
- groups_action["gl-texture-group"].Action(Bind(&config, GlTextureGroupTagHandler));
- groups_action["device-feature-group"].Action(Bind(&config, DeviceFeatureGroupTagHandler));
-
- if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
- diag->Error(DiagMessage() << "Could not process XML document");
- return {};
- }
-
- return {config};
-}
-
/** Converts a ConfiguredArtifact into an OutputArtifact. */
Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact,
const std::string& apk_name,
@@ -302,11 +249,11 @@
has_errors = true;
}
- if (artifact.android_sdk_group) {
- auto entry = config.android_sdk_groups.find(artifact.android_sdk_group.value());
- if (entry == config.android_sdk_groups.end()) {
+ if (artifact.android_sdk) {
+ auto entry = config.android_sdks.find(artifact.android_sdk.value());
+ if (entry == config.android_sdks.end()) {
src_diag.Error(DiagMessage() << "Could not lookup required Android SDK version: "
- << artifact.android_sdk_group.value());
+ << artifact.android_sdk.value());
has_errors = true;
} else {
output_artifact.android_sdk = {entry->second};
@@ -323,6 +270,64 @@
namespace configuration {
+/** Returns the binary reprasentation of the XML configuration. */
+Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
+ const std::string& config_path,
+ IDiagnostics* diag) {
+ StringInputStream in(contents);
+ std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source(config_path));
+ if (!doc) {
+ return {};
+ }
+
+ // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
+ Element* root = doc->root.get();
+ if (root == nullptr) {
+ diag->Error(DiagMessage() << "Could not find the root element in the XML document");
+ return {};
+ }
+
+ std::string& xml_ns = root->namespace_uri;
+ if (!xml_ns.empty()) {
+ if (xml_ns != kAaptXmlNs) {
+ diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
+ return {};
+ }
+
+ xml_ns.clear();
+ NamespaceVisitor visitor;
+ root->Accept(&visitor);
+ }
+
+ XmlActionExecutor executor;
+ XmlNodeAction& root_action = executor["post-process"];
+ XmlNodeAction& artifacts_action = root_action["artifacts"];
+
+ PostProcessingConfiguration config;
+
+ // Parse the artifact elements.
+ artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
+ artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
+
+ // Parse the different configuration groups.
+ root_action["abi-groups"]["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
+ root_action["screen-density-groups"]["screen-density-group"].Action(
+ Bind(&config, ScreenDensityGroupTagHandler));
+ root_action["locale-groups"]["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
+ root_action["android-sdks"]["android-sdk"].Action(Bind(&config, AndroidSdkTagHandler));
+ root_action["gl-texture-groups"]["gl-texture-group"].Action(
+ Bind(&config, GlTextureGroupTagHandler));
+ root_action["device-feature-groups"]["device-feature-group"].Action(
+ Bind(&config, DeviceFeatureGroupTagHandler));
+
+ if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
+ diag->Error(DiagMessage() << "Could not process XML document");
+ return {};
+ }
+
+ return {config};
+}
+
const StringPiece& AbiToString(Abi abi) {
return kAbiToStringMap.at(static_cast<size_t>(abi));
}
@@ -383,7 +388,7 @@
return {};
}
- if (!ReplacePlaceholder("${sdk}", android_sdk_group, &result, diag)) {
+ if (!ReplacePlaceholder("${sdk}", android_sdk, &result, diag)) {
return {};
}
@@ -414,47 +419,37 @@
if (!ReadFileToString(path, &contents, true)) {
return {};
}
- return ConfigurationParser(contents);
+ return ConfigurationParser(contents, path);
}
-ConfigurationParser::ConfigurationParser(std::string contents)
- : contents_(std::move(contents)),
- diag_(&noop_) {
+ConfigurationParser::ConfigurationParser(std::string contents, const std::string& config_path)
+ : contents_(std::move(contents)), config_path_(config_path), diag_(&noop_) {
}
Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse(
const android::StringPiece& apk_path) {
- Maybe<PostProcessingConfiguration> maybe_config = ExtractConfiguration(contents_, diag_);
+ Maybe<PostProcessingConfiguration> maybe_config =
+ ExtractConfiguration(contents_, config_path_, diag_);
if (!maybe_config) {
return {};
}
- const PostProcessingConfiguration& config = maybe_config.value();
-
- // TODO: Automatically arrange artifacts so that they match Play Store multi-APK requirements.
- // see: https://developer.android.com/google/play/publishing/multiple-apks.html
- //
- // For now, make sure the version codes are unique.
- std::vector<ConfiguredArtifact> artifacts = config.artifacts;
- std::sort(artifacts.begin(), artifacts.end());
- if (std::adjacent_find(artifacts.begin(), artifacts.end()) != artifacts.end()) {
- diag_->Error(DiagMessage() << "Configuration has duplicate versions");
- return {};
- }
-
- const std::string& apk_name = file::GetFilename(apk_path).to_string();
- const StringPiece ext = file::GetExtension(apk_name);
- const std::string base_name = apk_name.substr(0, apk_name.size() - ext.size());
// Convert from a parsed configuration to a list of artifacts for processing.
+ const std::string& apk_name = file::GetFilename(apk_path).to_string();
std::vector<OutputArtifact> output_artifacts;
bool has_errors = false;
- for (const ConfiguredArtifact& artifact : artifacts) {
+ PostProcessingConfiguration& config = maybe_config.value();
+ config.SortArtifacts();
+
+ int version = 1;
+ for (const ConfiguredArtifact& artifact : config.artifacts) {
Maybe<OutputArtifact> output_artifact = ToOutputArtifact(artifact, apk_name, config, diag_);
if (!output_artifact) {
// Defer return an error condition so that all errors are reported.
has_errors = true;
} else {
+ output_artifact.value().version = version++;
output_artifacts.push_back(std::move(output_artifact.value()));
}
}
@@ -470,24 +465,18 @@
bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_element,
IDiagnostics* diag) {
- // This will be incremented later so the first version will always be different to the base APK.
- int current_version = (config->artifacts.empty()) ? 0 : config->artifacts.back().version;
-
ConfiguredArtifact artifact{};
- Maybe<int> version;
for (const auto& attr : root_element->attributes) {
if (attr.name == "name") {
artifact.name = attr.value;
- } else if (attr.name == "version") {
- version = std::stoi(attr.value);
} else if (attr.name == "abi-group") {
artifact.abi_group = {attr.value};
} else if (attr.name == "screen-density-group") {
artifact.screen_density_group = {attr.value};
} else if (attr.name == "locale-group") {
artifact.locale_group = {attr.value};
- } else if (attr.name == "android-sdk-group") {
- artifact.android_sdk_group = {attr.value};
+ } else if (attr.name == "android-sdk") {
+ artifact.android_sdk = {attr.value};
} else if (attr.name == "gl-texture-group") {
artifact.gl_texture_group = {attr.value};
} else if (attr.name == "device-feature-group") {
@@ -497,9 +486,6 @@
<< attr.value);
}
}
-
- artifact.version = (version) ? version.value() : current_version + 1;
-
config->artifacts.push_back(artifact);
return true;
};
@@ -523,9 +509,19 @@
return false;
}
- auto& group = config->abi_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->abi_groups);
bool valid = true;
+ // Special case for empty abi-group tag. Label will be used as the ABI.
+ if (root_element->GetChildElements().empty()) {
+ auto abi = kStringToAbiMap.find(label);
+ if (abi == kStringToAbiMap.end()) {
+ return false;
+ }
+ group.push_back(abi->second);
+ return true;
+ }
+
for (auto* child : root_element->GetChildElements()) {
if (child->name != "abi") {
diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name);
@@ -534,7 +530,13 @@
for (auto& node : child->children) {
xml::Text* t;
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
- group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
+ auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string());
+ if (abi != kStringToAbiMap.end()) {
+ group.push_back(abi->second);
+ } else {
+ diag->Error(DiagMessage() << "Could not parse ABI value: " << t->text);
+ valid = false;
+ }
break;
}
}
@@ -551,9 +553,28 @@
return false;
}
- auto& group = config->screen_density_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->screen_density_groups);
bool valid = true;
+ // Special case for empty screen-density-group tag. Label will be used as the screen density.
+ if (root_element->GetChildElements().empty()) {
+ ConfigDescription config_descriptor;
+ bool parsed = ConfigDescription::Parse(label, &config_descriptor);
+ if (parsed &&
+ (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+ android::ResTable_config::CONFIG_DENSITY)) {
+ // Copy the density with the minimum SDK version stripped out.
+ group.push_back(config_descriptor.CopyWithoutSdkVersion());
+ } else {
+ diag->Error(DiagMessage()
+ << "Could not parse config descriptor for empty screen-density-group: "
+ << label);
+ valid = false;
+ }
+
+ return valid;
+ }
+
for (auto* child : root_element->GetChildElements()) {
if (child->name != "screen-density") {
diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
@@ -592,9 +613,28 @@
return false;
}
- auto& group = config->locale_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->locale_groups);
bool valid = true;
+ // Special case to auto insert a locale for an empty group. Label will be used for locale.
+ if (root_element->GetChildElements().empty()) {
+ ConfigDescription config_descriptor;
+ bool parsed = ConfigDescription::Parse(label, &config_descriptor);
+ if (parsed &&
+ (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+ android::ResTable_config::CONFIG_LOCALE)) {
+ // Copy the locale with the minimum SDK version stripped out.
+ group.push_back(config_descriptor.CopyWithoutSdkVersion());
+ } else {
+ diag->Error(DiagMessage()
+ << "Could not parse config descriptor for empty screen-density-group: "
+ << label);
+ valid = false;
+ }
+
+ return valid;
+ }
+
for (auto* child : root_element->GetChildElements()) {
if (child->name != "locale") {
diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
@@ -626,61 +666,58 @@
return valid;
};
-bool AndroidSdkGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
- IDiagnostics* diag) {
- std::string label = GetLabel(root_element, diag);
- if (label.empty()) {
- return false;
- }
-
+bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_element,
+ IDiagnostics* diag) {
+ AndroidSdk entry = AndroidSdk::ForMinSdk(-1);
bool valid = true;
- bool found = false;
+ for (const auto& attr : root_element->attributes) {
+ bool valid_attr = false;
+ if (attr.name == "label") {
+ entry.label = attr.value;
+ valid_attr = true;
+ } else if (attr.name == "minSdkVersion") {
+ Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+ if (version) {
+ valid_attr = true;
+ entry.min_sdk_version = version.value();
+ }
+ } else if (attr.name == "targetSdkVersion") {
+ Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+ if (version) {
+ valid_attr = true;
+ entry.target_sdk_version = version;
+ }
+ } else if (attr.name == "maxSdkVersion") {
+ Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+ if (version) {
+ valid_attr = true;
+ entry.max_sdk_version = version;
+ }
+ }
- for (auto* child : root_element->GetChildElements()) {
- if (child->name != "android-sdk") {
- diag->Error(DiagMessage() << "Unexpected root_element in ABI group: " << child->name);
+ if (!valid_attr) {
+ diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
valid = false;
- } else {
- AndroidSdk entry;
- for (const auto& attr : child->attributes) {
- Maybe<int>* target = nullptr;
- if (attr.name == "minSdkVersion") {
- target = &entry.min_sdk_version;
- } else if (attr.name == "targetSdkVersion") {
- target = &entry.target_sdk_version;
- } else if (attr.name == "maxSdkVersion") {
- target = &entry.max_sdk_version;
- } else {
- diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
- continue;
- }
-
- *target = ResourceUtils::ParseSdkVersion(attr.value);
- if (!*target) {
- diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
- valid = false;
- }
- }
-
- // TODO: Fill in the manifest details when they are finalised.
- for (auto node : child->GetChildElements()) {
- if (node->name == "manifest") {
- if (entry.manifest) {
- diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
- continue;
- }
- entry.manifest = {AndroidManifest()};
- }
- }
-
- config->android_sdk_groups[label] = entry;
- if (found) {
- valid = false;
- }
- found = true;
}
}
+ if (entry.min_sdk_version == -1) {
+ diag->Error(DiagMessage() << "android-sdk is missing minSdkVersion attribute");
+ valid = false;
+ }
+
+ // TODO: Fill in the manifest details when they are finalised.
+ for (auto node : root_element->GetChildElements()) {
+ if (node->name == "manifest") {
+ if (entry.manifest) {
+ diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
+ continue;
+ }
+ entry.manifest = {AndroidManifest()};
+ }
+ }
+
+ config->android_sdks[entry.label] = entry;
return valid;
};
@@ -691,7 +728,7 @@
return false;
}
- auto& group = config->gl_texture_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->gl_texture_groups);
bool valid = true;
GlTexture result;
@@ -734,7 +771,7 @@
return false;
}
- auto& group = config->device_feature_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->device_feature_groups);
bool valid = true;
for (auto* child : root_element->GetChildElements()) {
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index ca58910..7f1d445 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -71,7 +71,8 @@
};
struct AndroidSdk {
- Maybe<int> min_sdk_version;
+ std::string label;
+ int min_sdk_version; // min_sdk_version is mandatory if splitting by SDK.
Maybe<int> target_sdk_version;
Maybe<int> max_sdk_version;
Maybe<AndroidManifest> manifest;
@@ -113,15 +114,19 @@
Maybe<AndroidSdk> android_sdk;
std::vector<DeviceFeature> features;
std::vector<GlTexture> textures;
+
+ inline int GetMinSdk(int default_value = -1) const {
+ if (!android_sdk) {
+ return default_value;
+ }
+ return android_sdk.value().min_sdk_version;
+ }
};
} // namespace configuration
// Forward declaration of classes used in the API.
struct IDiagnostics;
-namespace xml {
-class Element;
-}
/**
* XML configuration file parser for the split and optimize commands.
@@ -133,8 +138,8 @@
static Maybe<ConfigurationParser> ForPath(const std::string& path);
/** Returns a ConfigurationParser for the configuration in the provided file contents. */
- static ConfigurationParser ForContents(const std::string& contents) {
- ConfigurationParser parser{contents};
+ static ConfigurationParser ForContents(const std::string& contents, const std::string& path) {
+ ConfigurationParser parser{contents, path};
return parser;
}
@@ -156,7 +161,7 @@
* diagnostics context. The default diagnostics context can be overridden with a call to
* WithDiagnostics(IDiagnostics *).
*/
- explicit ConfigurationParser(std::string contents);
+ ConfigurationParser(std::string contents, const std::string& config_path);
/** Returns the current diagnostics context to any subclasses. */
IDiagnostics* diagnostics() {
@@ -166,6 +171,8 @@
private:
/** The contents of the configuration file to parse. */
const std::string contents_;
+ /** Path to the input configuration. */
+ const std::string config_path_;
/** The diagnostics context to send messages to. */
IDiagnostics* diag_;
};
diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h
index 7657ebd..a583057 100644
--- a/tools/aapt2/configuration/ConfigurationParser.internal.h
+++ b/tools/aapt2/configuration/ConfigurationParser.internal.h
@@ -17,35 +17,105 @@
#ifndef AAPT2_CONFIGURATIONPARSER_INTERNAL_H
#define AAPT2_CONFIGURATIONPARSER_INTERNAL_H
+#include "configuration/ConfigurationParser.h"
+
+#include <algorithm>
+#include <limits>
+
namespace aapt {
+
+// Forward declaration of classes used in the API.
+namespace xml {
+class Element;
+}
+
namespace configuration {
+template <typename T>
+struct OrderedEntry {
+ size_t order;
+ std::vector<T> entry;
+};
+
/** A mapping of group labels to group of configuration items. */
template <class T>
-using Group = std::unordered_map<std::string, std::vector<T>>;
+using Group = std::unordered_map<std::string, OrderedEntry<T>>;
/** A mapping of group label to a single configuration item. */
template <class T>
using Entry = std::unordered_map<std::string, T>;
+/** Retrieves an entry from the provided Group, creating a new instance if one does not exist. */
+template <typename T>
+std::vector<T>& GetOrCreateGroup(std::string label, Group<T>* group) {
+ OrderedEntry<T>& entry = (*group)[label];
+ // If this is a new entry, set the order.
+ if (entry.order == 0) {
+ entry.order = group->size();
+ }
+ return entry.entry;
+}
+
+/**
+ * A ComparisonChain is a grouping of comparisons to perform when sorting groups that have a well
+ * defined order of precedence. Comparisons are only made if none of the previous comparisons had a
+ * definite result. A comparison has a result if at least one of the items has an entry for that
+ * value and that they are not equal.
+ */
+class ComparisonChain {
+ public:
+ /**
+ * Adds a new comparison of items in a group to the chain. The new comparison is only used if we
+ * have not been able to determine the sort order with the previous comparisons.
+ */
+ template <typename T>
+ ComparisonChain& Add(const Group<T>& groups, const Maybe<std::string>& lhs,
+ const Maybe<std::string>& rhs) {
+ return Add(GetGroupOrder(groups, lhs), GetGroupOrder(groups, rhs));
+ }
+
+ /**
+ * Adds a new comparison to the chain. The new comparison is only used if we have not been able to
+ * determine the sort order with the previous comparisons.
+ */
+ ComparisonChain& Add(int lhs, int rhs) {
+ if (!has_result_) {
+ has_result_ = (lhs != rhs);
+ result_ = (lhs < rhs);
+ }
+ return *this;
+ }
+
+ /** Returns true if the left hand side should come before the right hand side. */
+ bool Compare() {
+ return result_;
+ }
+
+ private:
+ template <typename T>
+ inline size_t GetGroupOrder(const Group<T>& groups, const Maybe<std::string>& label) {
+ if (!label) {
+ return std::numeric_limits<size_t>::max();
+ }
+ return groups.at(label.value()).order;
+ }
+
+ bool has_result_ = false;
+ bool result_ = false;
+};
+
/** Output artifact configuration options. */
struct ConfiguredArtifact {
/** Name to use for output of processing foo.apk -> foo.<name>.apk. */
Maybe<std::string> name;
- /**
- * Value to add to the base Android manifest versionCode. If it is not present in the
- * configuration file, it is set to the previous artifact + 1. If the first artifact does not have
- * a value, artifacts are a 1 based index.
- */
- int version;
/** If present, uses the ABI group with this name. */
Maybe<std::string> abi_group;
/** If present, uses the screen density group with this name. */
Maybe<std::string> screen_density_group;
/** If present, uses the locale group with this name. */
Maybe<std::string> locale_group;
- /** If present, uses the Android SDK group with this name. */
- Maybe<std::string> android_sdk_group;
+ /** If present, uses the Android SDK with this name. */
+ Maybe<std::string> android_sdk;
/** If present, uses the device feature group with this name. */
Maybe<std::string> device_feature_group;
/** If present, uses the OpenGL texture group with this name. */
@@ -57,31 +127,71 @@
/** Convert an artifact name template into a name string based on configuration contents. */
Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const;
-
- bool operator<(const ConfiguredArtifact& rhs) const {
- // TODO(safarmer): Order by play store multi-APK requirements.
- return version < rhs.version;
- }
-
- bool operator==(const ConfiguredArtifact& rhs) const {
- return version == rhs.version;
- }
};
/** AAPT2 XML configuration file binary representation. */
struct PostProcessingConfiguration {
- // TODO: Support named artifacts?
std::vector<ConfiguredArtifact> artifacts;
Maybe<std::string> artifact_format;
Group<Abi> abi_groups;
Group<ConfigDescription> screen_density_groups;
Group<ConfigDescription> locale_groups;
- Entry<AndroidSdk> android_sdk_groups;
Group<DeviceFeature> device_feature_groups;
Group<GlTexture> gl_texture_groups;
+ Entry<AndroidSdk> android_sdks;
+
+ /**
+ * Sorts the configured artifacts based on the ordering of the groups in the configuration file.
+ * The only exception to this rule is Android SDK versions. Larger SDK versions will have a larger
+ * versionCode to ensure users get the correct APK when they upgrade their OS.
+ */
+ void SortArtifacts() {
+ std::sort(artifacts.begin(), artifacts.end(), *this);
+ }
+
+ /** Comparator that ensures artifacts are in the preferred order for versionCode rewriting. */
+ bool operator()(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) {
+ // Split dimensions are added in the order of precedence. Items higher in the list result in
+ // higher version codes.
+ return ComparisonChain()
+ // All splits with a minSdkVersion specified must be last to ensure the application will be
+ // updated if a user upgrades the version of Android on their device.
+ .Add(GetMinSdk(lhs), GetMinSdk(rhs))
+ // ABI version is important, especially on x86 phones where they may begin to run in ARM
+ // emulation mode on newer Android versions. This allows us to ensure that the x86 version
+ // is installed on these devices rather than ARM.
+ .Add(abi_groups, lhs.abi_group, rhs.abi_group)
+ // The rest are in arbitrary order based on estimated usage.
+ .Add(screen_density_groups, lhs.screen_density_group, rhs.screen_density_group)
+ .Add(locale_groups, lhs.locale_group, rhs.locale_group)
+ .Add(gl_texture_groups, lhs.gl_texture_group, rhs.gl_texture_group)
+ .Add(device_feature_groups, lhs.device_feature_group, rhs.device_feature_group)
+ .Compare();
+ }
+
+ private:
+ /**
+ * Returns the min_sdk_version from the provided artifact or 0 if none is present. This allows
+ * artifacts that have an Android SDK version to have a higher versionCode than those that do not.
+ */
+ inline int GetMinSdk(const ConfiguredArtifact& artifact) {
+ if (!artifact.android_sdk) {
+ return 0;
+ }
+ const auto& entry = android_sdks.find(artifact.android_sdk.value());
+ if (entry == android_sdks.end()) {
+ return 0;
+ }
+ return entry->second.min_sdk_version;
+ }
};
+/** Parses the provided XML document returning the post processing configuration. */
+Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
+ const std::string& config_path,
+ IDiagnostics* diag);
+
namespace handler {
/** Handler for <artifact> tags. */
@@ -104,9 +214,9 @@
bool LocaleGroupTagHandler(configuration::PostProcessingConfiguration* config,
xml::Element* element, IDiagnostics* diag);
-/** Handler for <android-sdk-group> tags. */
-bool AndroidSdkGroupTagHandler(configuration::PostProcessingConfiguration* config,
- xml::Element* element, IDiagnostics* diag);
+/** Handler for <android-sdk> tags. */
+bool AndroidSdkTagHandler(configuration::PostProcessingConfiguration* config, xml::Element* element,
+ IDiagnostics* diag);
/** Handler for <gl-texture-group> tags. */
bool GlTextureGroupTagHandler(configuration::PostProcessingConfiguration* config,
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index 3f356d7..0329846 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -30,11 +30,33 @@
namespace configuration {
void PrintTo(const AndroidSdk& sdk, std::ostream* os) {
- *os << "SDK: min=" << sdk.min_sdk_version.value_or_default(-1)
+ *os << "SDK: min=" << sdk.min_sdk_version
<< ", target=" << sdk.target_sdk_version.value_or_default(-1)
<< ", max=" << sdk.max_sdk_version.value_or_default(-1);
}
+bool operator==(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) {
+ return lhs.name == rhs.name && lhs.abi_group == rhs.abi_group &&
+ lhs.screen_density_group == rhs.screen_density_group &&
+ lhs.locale_group == rhs.locale_group && lhs.android_sdk == rhs.android_sdk &&
+ lhs.device_feature_group == rhs.device_feature_group &&
+ lhs.gl_texture_group == rhs.gl_texture_group;
+}
+
+std::ostream& operator<<(std::ostream& out, const Maybe<std::string>& value) {
+ PrintTo(value, &out);
+ return out;
+}
+
+void PrintTo(const ConfiguredArtifact& artifact, std::ostream* os) {
+ *os << "\n{"
+ << "\n name: " << artifact.name << "\n sdk: " << artifact.android_sdk
+ << "\n abi: " << artifact.abi_group << "\n density: " << artifact.screen_density_group
+ << "\n locale: " << artifact.locale_group
+ << "\n features: " << artifact.device_feature_group
+ << "\n textures: " << artifact.gl_texture_group << "\n}\n";
+}
+
namespace handler {
namespace {
@@ -44,6 +66,7 @@
using ::aapt::configuration::AndroidSdk;
using ::aapt::configuration::ConfiguredArtifact;
using ::aapt::configuration::DeviceFeature;
+using ::aapt::configuration::ExtractConfiguration;
using ::aapt::configuration::GlTexture;
using ::aapt::configuration::Locale;
using ::aapt::configuration::PostProcessingConfiguration;
@@ -52,11 +75,13 @@
using ::android::ResTable_config;
using ::android::base::StringPrintf;
using ::testing::ElementsAre;
+using ::testing::Eq;
using ::testing::SizeIs;
+using ::testing::StrEq;
constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?>
<post-process xmlns="http://schemas.android.com/tools/aapt">
- <groups>
+ <abi-groups>
<abi-group label="arm">
<abi>armeabi-v7a</abi>
<abi>arm64-v8a</abi>
@@ -65,6 +90,8 @@
<abi>x86</abi>
<abi>mips</abi>
</abi-group>
+ </abi-groups>
+ <screen-density-groups>
<screen-density-group label="large">
<screen-density>xhdpi</screen-density>
<screen-density>xxhdpi</screen-density>
@@ -78,6 +105,8 @@
<screen-density>xxhdpi</screen-density>
<screen-density>xxxhdpi</screen-density>
</screen-density-group>
+ </screen-density-groups>
+ <locale-groups>
<locale-group label="europe">
<locale>en</locale>
<locale>es</locale>
@@ -89,25 +118,30 @@
<locale>es-rMX</locale>
<locale>fr-rCA</locale>
</locale-group>
- <android-sdk-group label="v19">
- <android-sdk
- minSdkVersion="19"
- targetSdkVersion="24"
- maxSdkVersion="25">
- <manifest>
- <!--- manifest additions here XSLT? TODO -->
- </manifest>
- </android-sdk>
- </android-sdk-group>
+ </locale-groups>
+ <android-sdks>
+ <android-sdk
+ label="v19"
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>
+ </android-sdks>
+ <gl-texture-groups>
<gl-texture-group label="dxt1">
<gl-texture name="GL_EXT_texture_compression_dxt1">
<texture-path>assets/dxt1/*</texture-path>
</gl-texture>
</gl-texture-group>
+ </gl-texture-groups>
+ <device-feature-groups>
<device-feature-group label="low-latency">
<supports-feature>android.hardware.audio.low_latency</supports-feature>
</device-feature-group>
- </groups>
+ </device-feature-groups>
<artifacts>
<artifact-format>
${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
@@ -117,7 +151,7 @@
abi-group="arm"
screen-density-group="large"
locale-group="europe"
- android-sdk-group="v19"
+ android-sdk="v19"
gl-texture-group="dxt1"
device-feature-group="low-latency"/>
<artifact
@@ -125,7 +159,7 @@
abi-group="other"
screen-density-group="alldpi"
locale-group="north-america"
- android-sdk-group="v19"
+ android-sdk="v19"
gl-texture-group="dxt1"
device-feature-group="low-latency"/>
</artifacts>
@@ -134,7 +168,8 @@
class ConfigurationParserTest : public ConfigurationParser, public ::testing::Test {
public:
- ConfigurationParserTest() : ConfigurationParser("") {}
+ ConfigurationParserTest() : ConfigurationParser("", "config.xml") {
+ }
protected:
StdErrDiagnostics diag_;
@@ -145,8 +180,31 @@
EXPECT_FALSE(result);
}
+TEST_F(ConfigurationParserTest, ExtractConfiguration) {
+ Maybe<PostProcessingConfiguration> maybe_config =
+ ExtractConfiguration(kValidConfig, "dummy.xml", &diag_);
+
+ PostProcessingConfiguration config = maybe_config.value();
+
+ auto& arm = config.abi_groups["arm"];
+ auto& other = config.abi_groups["other"];
+ EXPECT_EQ(arm.order, 1ul);
+ EXPECT_EQ(other.order, 2ul);
+
+ auto& large = config.screen_density_groups["large"];
+ auto& alldpi = config.screen_density_groups["alldpi"];
+ EXPECT_EQ(large.order, 1ul);
+ EXPECT_EQ(alldpi.order, 2ul);
+
+ auto& north_america = config.locale_groups["north-america"];
+ auto& europe = config.locale_groups["europe"];
+ // Checked in reverse to make sure access order does not matter.
+ EXPECT_EQ(north_america.order, 2ul);
+ EXPECT_EQ(europe.order, 1ul);
+}
+
TEST_F(ConfigurationParserTest, ValidateFile) {
- auto parser = ConfigurationParser::ForContents(kValidConfig).WithDiagnostics(&diag_);
+ auto parser = ConfigurationParser::ForContents(kValidConfig, "conf.xml").WithDiagnostics(&diag_);
auto result = parser.Parse("test.apk");
ASSERT_TRUE(result);
const std::vector<OutputArtifact>& value = result.value();
@@ -154,6 +212,7 @@
const OutputArtifact& a1 = value[0];
EXPECT_EQ(a1.name, "art1.apk");
+ EXPECT_EQ(a1.version, 1);
EXPECT_THAT(a1.abis, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
EXPECT_THAT(a1.screen_densities,
ElementsAre(test::ParseConfigOrDie("xhdpi").CopyWithoutSdkVersion(),
@@ -161,12 +220,15 @@
test::ParseConfigOrDie("xxxhdpi").CopyWithoutSdkVersion()));
EXPECT_THAT(a1.locales, ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es"),
test::ParseConfigOrDie("fr"), test::ParseConfigOrDie("de")));
- EXPECT_EQ(a1.android_sdk.value().min_sdk_version.value(), 19l);
+ ASSERT_TRUE(a1.android_sdk);
+ ASSERT_TRUE(a1.android_sdk.value().min_sdk_version);
+ EXPECT_EQ(a1.android_sdk.value().min_sdk_version, 19l);
EXPECT_THAT(a1.textures, SizeIs(1ul));
EXPECT_THAT(a1.features, SizeIs(1ul));
const OutputArtifact& a2 = value[1];
EXPECT_EQ(a2.name, "art2.apk");
+ EXPECT_EQ(a2.version, 2);
EXPECT_THAT(a2.abis, ElementsAre(Abi::kX86, Abi::kMips));
EXPECT_THAT(a2.screen_densities,
ElementsAre(test::ParseConfigOrDie("ldpi").CopyWithoutSdkVersion(),
@@ -178,124 +240,138 @@
EXPECT_THAT(a2.locales,
ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es-rMX"),
test::ParseConfigOrDie("fr-rCA")));
- EXPECT_EQ(a2.android_sdk.value().min_sdk_version.value(), 19l);
+ ASSERT_TRUE(a2.android_sdk);
+ ASSERT_TRUE(a2.android_sdk.value().min_sdk_version);
+ EXPECT_EQ(a2.android_sdk.value().min_sdk_version, 19l);
EXPECT_THAT(a2.textures, SizeIs(1ul));
EXPECT_THAT(a2.features, SizeIs(1ul));
}
+TEST_F(ConfigurationParserTest, ConfiguredArtifactOrdering) {
+ // Create a base builder with the configuration groups but no artifacts to allow it to be copied.
+ test::PostProcessingConfigurationBuilder base_builder = test::PostProcessingConfigurationBuilder()
+ .AddAbiGroup("arm")
+ .AddAbiGroup("arm64")
+ .AddAndroidSdk("v23", 23)
+ .AddAndroidSdk("v19", 19);
+
+ {
+ // Test version ordering.
+ ConfiguredArtifact v23;
+ v23.android_sdk = {"v23"};
+ ConfiguredArtifact v19;
+ v19.android_sdk = {"v19"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(v23).AddArtifact(v19).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(v19));
+ EXPECT_THAT(config.artifacts[1], Eq(v23));
+ }
+
+ {
+ // Test ABI ordering.
+ ConfiguredArtifact arm;
+ arm.android_sdk = {"v19"};
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact arm64;
+ arm64.android_sdk = {"v19"};
+ arm64.abi_group = {"arm64"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(arm64).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm));
+ EXPECT_THAT(config.artifacts[1], Eq(arm64));
+ }
+
+ {
+ // Test Android SDK has precedence over ABI.
+ ConfiguredArtifact arm;
+ arm.android_sdk = {"v23"};
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact arm64;
+ arm64.android_sdk = {"v19"};
+ arm64.abi_group = {"arm64"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(arm64).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm64));
+ EXPECT_THAT(config.artifacts[1], Eq(arm));
+ }
+
+ {
+ // Test version is better than ABI.
+ ConfiguredArtifact arm;
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact v19;
+ v19.android_sdk = {"v19"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(v19).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm));
+ EXPECT_THAT(config.artifacts[1], Eq(v19));
+ }
+
+ {
+ // Test version is sorted higher than no version.
+ ConfiguredArtifact arm;
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact v19;
+ v19.abi_group = {"arm"};
+ v19.android_sdk = {"v19"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(v19).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm));
+ EXPECT_THAT(config.artifacts[1], Eq(v19));
+ }
+}
+
TEST_F(ConfigurationParserTest, InvalidNamespace) {
constexpr const char* invalid_ns = R"(<?xml version="1.0" encoding="utf-8" ?>
- <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
+ <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
- auto result = ConfigurationParser::ForContents(invalid_ns).Parse("test.apk");
+ auto result = ConfigurationParser::ForContents(invalid_ns, "config.xml").Parse("test.apk");
ASSERT_FALSE(result);
}
TEST_F(ConfigurationParserTest, ArtifactAction) {
PostProcessingConfiguration config;
- {
- const auto doc = test::BuildXmlDom(R"xml(
+ const auto doc = test::BuildXmlDom(R"xml(
<artifact
abi-group="arm"
screen-density-group="large"
locale-group="europe"
- android-sdk-group="v19"
+ android-sdk="v19"
gl-texture-group="dxt1"
device-feature-group="low-latency"/>)xml");
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc->root.get()), &diag_));
+ ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(1ul));
+ EXPECT_THAT(config.artifacts, SizeIs(1ul));
- auto& artifact = config.artifacts.back();
- EXPECT_FALSE(artifact.name); // TODO: make this fail.
- EXPECT_EQ(1, artifact.version);
- EXPECT_EQ("arm", artifact.abi_group.value());
- EXPECT_EQ("large", artifact.screen_density_group.value());
- EXPECT_EQ("europe", artifact.locale_group.value());
- EXPECT_EQ("v19", artifact.android_sdk_group.value());
- EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
- EXPECT_EQ("low-latency", artifact.device_feature_group.value());
- }
-
- {
- // Perform a second action to ensure we get 2 artifacts.
- const auto doc = test::BuildXmlDom(R"xml(
- <artifact
- abi-group="other"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>)xml");
-
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(2ul));
- EXPECT_EQ(2, config.artifacts.back().version);
- }
-
- {
- // Perform a third action with a set version code.
- const auto doc = test::BuildXmlDom(R"xml(
- <artifact
- version="5"
- abi-group="other"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>)xml");
-
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(3ul));
- EXPECT_EQ(5, config.artifacts.back().version);
- }
-
- {
- // Perform a fourth action to ensure the version code still increments.
- const auto doc = test::BuildXmlDom(R"xml(
- <artifact
- abi-group="other"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>)xml");
-
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(4ul));
- EXPECT_EQ(6, config.artifacts.back().version);
- }
-}
-
-TEST_F(ConfigurationParserTest, DuplicateArtifactVersion) {
- static constexpr const char* configuration = R"xml(<?xml version="1.0" encoding="utf-8" ?>
- <pst-process xmlns="http://schemas.android.com/tools/aapt">>
- <artifacts>
- <artifact-format>
- ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
- </artifact-format>
- <artifact
- name="art1"
- abi-group="arm"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>
- <artifact
- name="art2"
- version = "1"
- abi-group="other"
- screen-density-group="alldpi"
- locale-group="north-america"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>
- </artifacts>
- </post-process>)xml";
- auto result = ConfigurationParser::ForContents(configuration).Parse("test.apk");
- ASSERT_FALSE(result);
+ auto& artifact = config.artifacts.back();
+ EXPECT_FALSE(artifact.name); // TODO: make this fail.
+ EXPECT_EQ("arm", artifact.abi_group.value());
+ EXPECT_EQ("large", artifact.screen_density_group.value());
+ EXPECT_EQ("europe", artifact.locale_group.value());
+ EXPECT_EQ("v19", artifact.android_sdk.value());
+ EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
+ EXPECT_EQ("low-latency", artifact.device_feature_group.value());
}
TEST_F(ConfigurationParserTest, ArtifactFormatAction) {
@@ -334,10 +410,36 @@
EXPECT_THAT(config.abi_groups, SizeIs(1ul));
ASSERT_EQ(1u, config.abi_groups.count("arm"));
- auto& out = config.abi_groups["arm"];
+ auto& out = config.abi_groups["arm"].entry;
ASSERT_THAT(out, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
}
+TEST_F(ConfigurationParserTest, AbiGroupAction_EmptyGroup) {
+ static constexpr const char* xml = R"xml(<abi-group label="arm64-v8a"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ EXPECT_THAT(config.abi_groups, SizeIs(1ul));
+ ASSERT_EQ(1u, config.abi_groups.count("arm64-v8a"));
+
+ auto& out = config.abi_groups["arm64-v8a"].entry;
+ ASSERT_THAT(out, ElementsAre(Abi::kArm64V8a));
+}
+
+TEST_F(ConfigurationParserTest, AbiGroupAction_InvalidEmptyGroup) {
+ static constexpr const char* xml = R"xml(<abi-group label="arm"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_FALSE(ok);
+}
+
TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) {
static constexpr const char* xml = R"xml(
<screen-density-group label="large">
@@ -364,10 +466,39 @@
ConfigDescription xxxhdpi;
xxxhdpi.density = ResTable_config::DENSITY_XXXHIGH;
- auto& out = config.screen_density_groups["large"];
+ auto& out = config.screen_density_groups["large"].entry;
ASSERT_THAT(out, ElementsAre(xhdpi, xxhdpi, xxxhdpi));
}
+TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_EmtpyGroup) {
+ static constexpr const char* xml = R"xml(<screen-density-group label="xhdpi"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ EXPECT_THAT(config.screen_density_groups, SizeIs(1ul));
+ ASSERT_EQ(1u, config.screen_density_groups.count("xhdpi"));
+
+ ConfigDescription xhdpi;
+ xhdpi.density = ResTable_config::DENSITY_XHIGH;
+
+ auto& out = config.screen_density_groups["xhdpi"].entry;
+ ASSERT_THAT(out, ElementsAre(xhdpi));
+}
+
+TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_InvalidEmtpyGroup) {
+ static constexpr const char* xml = R"xml(<screen-density-group label="really-big-screen"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_FALSE(ok);
+}
+
TEST_F(ConfigurationParserTest, LocaleGroupAction) {
static constexpr const char* xml = R"xml(
<locale-group label="europe">
@@ -386,7 +517,7 @@
ASSERT_EQ(1ul, config.locale_groups.size());
ASSERT_EQ(1u, config.locale_groups.count("europe"));
- const auto& out = config.locale_groups["europe"];
+ const auto& out = config.locale_groups["europe"].entry;
ConfigDescription en = test::ParseConfigOrDie("en");
ConfigDescription es = test::ParseConfigOrDie("es");
@@ -396,29 +527,56 @@
ASSERT_THAT(out, ElementsAre(en, es, fr, de));
}
+TEST_F(ConfigurationParserTest, LocaleGroupAction_EmtpyGroup) {
+ static constexpr const char* xml = R"xml(<locale-group label="en"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ ASSERT_EQ(1ul, config.locale_groups.size());
+ ASSERT_EQ(1u, config.locale_groups.count("en"));
+
+ const auto& out = config.locale_groups["en"].entry;
+
+ ConfigDescription en = test::ParseConfigOrDie("en");
+
+ ASSERT_THAT(out, ElementsAre(en));
+}
+
+TEST_F(ConfigurationParserTest, LocaleGroupAction_InvalidEmtpyGroup) {
+ static constexpr const char* xml = R"xml(<locale-group label="arm64"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_FALSE(ok);
+}
+
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) {
static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk
+ <android-sdk label="v19"
minSdkVersion="19"
targetSdkVersion="24"
maxSdkVersion="25">
<manifest>
<!--- manifest additions here XSLT? TODO -->
</manifest>
- </android-sdk>
- </android-sdk-group>)xml";
+ </android-sdk>)xml";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
+ auto& out = config.android_sdks["v19"];
AndroidSdk sdk;
sdk.min_sdk_version = 19;
@@ -431,98 +589,86 @@
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_SingleVersion) {
{
- static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk minSdkVersion="19"></android-sdk>
- </android-sdk-group>)xml";
-
+ const char* xml = "<android-sdk label='v19' minSdkVersion='19'></android-sdk>";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
- EXPECT_EQ(19, out.min_sdk_version.value());
+ auto& out = config.android_sdks["v19"];
+ EXPECT_EQ(19, out.min_sdk_version);
EXPECT_FALSE(out.max_sdk_version);
EXPECT_FALSE(out.target_sdk_version);
}
{
- static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk maxSdkVersion="19"></android-sdk>
- </android-sdk-group>)xml";
-
+ const char* xml =
+ "<android-sdk label='v19' minSdkVersion='19' maxSdkVersion='19'></android-sdk>";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
+ auto& out = config.android_sdks["v19"];
EXPECT_EQ(19, out.max_sdk_version.value());
- EXPECT_FALSE(out.min_sdk_version);
+ EXPECT_EQ(19, out.min_sdk_version);
EXPECT_FALSE(out.target_sdk_version);
}
{
- static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk targetSdkVersion="19"></android-sdk>
- </android-sdk-group>)xml";
-
+ const char* xml =
+ "<android-sdk label='v19' minSdkVersion='19' targetSdkVersion='19'></android-sdk>";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
+ auto& out = config.android_sdks["v19"];
EXPECT_EQ(19, out.target_sdk_version.value());
- EXPECT_FALSE(out.min_sdk_version);
+ EXPECT_EQ(19, out.min_sdk_version);
EXPECT_FALSE(out.max_sdk_version);
}
}
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_InvalidVersion) {
static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk
- minSdkVersion="v19"
- targetSdkVersion="v24"
- maxSdkVersion="v25">
- <manifest>
- <!--- manifest additions here XSLT? TODO -->
- </manifest>
- </android-sdk>
- </android-sdk-group>)xml";
+ <android-sdk
+ label="v19"
+ minSdkVersion="v19"
+ targetSdkVersion="v24"
+ maxSdkVersion="v25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>)xml";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_FALSE(ok);
}
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) {
static constexpr const char* xml = R"xml(
- <android-sdk-group label="P">
<android-sdk
+ label="P"
minSdkVersion="25"
targetSdkVersion="%s"
maxSdkVersion="%s">
- </android-sdk>
- </android-sdk-group>)xml";
+ </android-sdk>)xml";
const auto& dev_sdk = GetDevelopmentSdkCodeNameAndVersion();
const char* codename = dev_sdk.first.data();
@@ -531,13 +677,13 @@
auto doc = test::BuildXmlDom(StringPrintf(xml, codename, codename));
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("P"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("P"));
- auto& out = config.android_sdk_groups["P"];
+ auto& out = config.android_sdks["P"];
AndroidSdk sdk;
sdk.min_sdk_version = 25;
@@ -567,7 +713,7 @@
EXPECT_THAT(config.gl_texture_groups, SizeIs(1ul));
ASSERT_EQ(1u, config.gl_texture_groups.count("dxt1"));
- auto& out = config.gl_texture_groups["dxt1"];
+ auto& out = config.gl_texture_groups["dxt1"].entry;
GlTexture texture{
std::string("GL_EXT_texture_compression_dxt1"),
@@ -596,7 +742,7 @@
EXPECT_THAT(config.device_feature_groups, SizeIs(1ul));
ASSERT_EQ(1u, config.device_feature_groups.count("low-latency"));
- auto& out = config.device_feature_groups["low-latency"];
+ auto& out = config.device_feature_groups["low-latency"].entry;
DeviceFeature low_latency = "android.hardware.audio.low_latency";
DeviceFeature pro = "android.hardware.audio.pro";
@@ -650,7 +796,7 @@
artifact.device_feature_group = {"df1"};
artifact.gl_texture_group = {"glx1"};
artifact.locale_group = {"en-AU"};
- artifact.android_sdk_group = {"v26"};
+ artifact.android_sdk = {"v26"};
{
auto result = artifact.ToArtifactName(
diff --git a/tools/aapt2/configuration/aapt2.xsd b/tools/aapt2/configuration/aapt2.xsd
index 134153a..fb2f49b 100644
--- a/tools/aapt2/configuration/aapt2.xsd
+++ b/tools/aapt2/configuration/aapt2.xsd
@@ -8,22 +8,52 @@
<xsd:element name="post-process">
<xsd:complexType>
<xsd:sequence>
- <xsd:element name="groups" type="groups"/>
<xsd:element name="artifacts" type="artifacts"/>
+ <xsd:element name="android-sdks" type="android-sdks"/>
+ <xsd:element name="abi-groups" type="abi-groups"/>
+ <xsd:element name="screen-density-groups" type="screen-density-groups"/>
+ <xsd:element name="locale-groups" type="locale-groups"/>
+ <xsd:element name="gl-texture-groups" type="gl-texture-groups"/>
+ <xsd:element name="device-feature-groups" type="device-feature-groups"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
- <xsd:complexType name="groups">
+ <xsd:complexType name="android-sdks">
+ <xsd:sequence>
+ <xsd:element name="android-sdk" type="android-sdk" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="abi-groups">
<xsd:sequence>
<xsd:element name="abi-group" type="abi-group" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="screen-density-groups">
+ <xsd:sequence>
<xsd:element name="screen-density-group" type="screen-density-group" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="locale-groups">
+ <xsd:sequence>
<xsd:element name="locale-group" type="locale-group" maxOccurs="unbounded"/>
- <xsd:element name="android-sdk-group" type="android-sdk-group" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="gl-texture-groups">
+ <xsd:sequence>
<xsd:element
name="gl-texture-group"
type="gl-texture-group"
maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="device-feature-groups">
+ <xsd:sequence>
<xsd:element name="device-feature-group" type="device-feature-group" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
@@ -38,8 +68,6 @@
<!-- Groups output artifacts together by dimension labels. -->
<xsd:complexType name="artifact">
- <xsd:attribute name="name" type="xsd:string"/>
- <xsd:attribute name="version" type="xsd:integer"/>
<xsd:attribute name="abi-group" type="xsd:string"/>
<xsd:attribute name="android-sdk-group" type="xsd:string"/>
<xsd:attribute name="device-feature-group" type="xsd:string"/>
@@ -52,7 +80,7 @@
<xsd:sequence>
<xsd:element name="gl-texture" type="gl-texture" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="gl-texture">
@@ -66,14 +94,14 @@
<xsd:sequence>
<xsd:element name="supports-feature" type="xsd:string" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="abi-group">
<xsd:sequence>
<xsd:element name="abi" type="abi-name" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:simpleType name="abi-name">
@@ -93,7 +121,7 @@
<xsd:sequence>
<xsd:element name="screen-density" type="screen-density" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:simpleType name="screen-density">
@@ -108,20 +136,14 @@
</xsd:restriction>
</xsd:simpleType>
- <xsd:complexType name="android-sdk-group">
- <xsd:sequence>
- <xsd:element name="android-sdk" type="android-sdk" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
- </xsd:complexType>
-
<xsd:complexType name="android-sdk">
<!-- TODO(safarmer): Add permissions to add/remove. -->
<!-- TODO(safarmer): Add option for uncompressed native libs. -->
<xsd:sequence>
<xsd:element name="manifest" type="manifest"/>
</xsd:sequence>
- <xsd:attribute name="minSdkVersion" type="xsd:integer"/>
+ <xsd:attribute name="label" type="xsd:string" use="required"/>
+ <xsd:attribute name="minSdkVersion" type="xsd:integer" use="required"/>
<xsd:attribute name="targetSdkVersion" type="xsd:integer"/>
<xsd:attribute name="maxSdkVersion" type="xsd:integer"/>
</xsd:complexType>
@@ -135,7 +157,7 @@
<xsd:sequence>
<xsd:element name="locale" type="locale" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="locale">
diff --git a/tools/aapt2/configuration/example/config.xml b/tools/aapt2/configuration/example/config.xml
index ce31e61..d8aba09 100644
--- a/tools/aapt2/configuration/example/config.xml
+++ b/tools/aapt2/configuration/example/config.xml
@@ -1,70 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<post-process xmlns="http://schemas.android.com/tools/aapt">
- <groups>
- <abi-group label="arm">
- <abi>armeabi-v7a</abi>
- <abi>arm64-v8a</abi>
- </abi-group>
-
- <abi-group label="other">
- <abi>x86</abi>
- <abi>mips</abi>
- </abi-group>
-
- <screen-density-group label="large">
- <screen-density>xhdpi</screen-density>
- <screen-density>xxhdpi</screen-density>
- <screen-density>xxxhdpi</screen-density>
- </screen-density-group>
-
- <screen-density-group label="alldpi">
- <screen-density>ldpi</screen-density>
- <screen-density>mdpi</screen-density>
- <screen-density>hdpi</screen-density>
- <screen-density>xhdpi</screen-density>
- <screen-density>xxhdpi</screen-density>
- <screen-density>xxxhdpi</screen-density>
- </screen-density-group>
-
- <locale-group label="europe">
- <locale lang="en"/>
- <locale lang="es"/>
- <locale lang="fr"/>
- <locale lang="de" compressed="true"/>
- </locale-group>
-
- <locale-group label="north-america">
- <locale lang="en"/>
- <locale lang="es" region="MX"/>
- <locale lang="fr" region="CA" compressed="true"/>
- </locale-group>
-
- <locale-group label="all">
- <locale compressed="true"/>
- </locale-group>
-
- <android-sdk-group label="19">
- <android-sdk
- minSdkVersion="19"
- targetSdkVersion="24"
- maxSdkVersion="25">
- <manifest>
- <!--- manifest additions here XSLT? TODO -->
- </manifest>
- </android-sdk>
- </android-sdk-group>
-
- <gl-texture-group label="dxt1">
- <gl-texture name="GL_EXT_texture_compression_dxt1">
- <texture-path>assets/dxt1/*</texture-path>
- </gl-texture>
- </gl-texture-group>
-
- <device-feature-group label="low-latency">
- <supports-feature>android.hardware.audio.low_latency</supports-feature>
- </device-feature-group>
- </groups>
-
<artifacts>
<artifact-format>
${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
@@ -87,4 +22,79 @@
device-feature-group="low-latency"/>
</artifacts>
+
+ <android-sdks>
+ <android-sdk
+ label="19"
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>
+ </android-sdks>
+
+ <abi-groups>
+ <abi-group label="arm">
+ <abi>armeabi-v7a</abi>
+ <abi>arm64-v8a</abi>
+ </abi-group>
+
+ <abi-group label="other">
+ <abi>x86</abi>
+ <abi>mips</abi>
+ </abi-group>
+ </abi-groups>
+
+ <screen-density-groups>
+ <screen-density-group label="large">
+ <screen-density>xhdpi</screen-density>
+ <screen-density>xxhdpi</screen-density>
+ <screen-density>xxxhdpi</screen-density>
+ </screen-density-group>
+
+ <screen-density-group label="alldpi">
+ <screen-density>ldpi</screen-density>
+ <screen-density>mdpi</screen-density>
+ <screen-density>hdpi</screen-density>
+ <screen-density>xhdpi</screen-density>
+ <screen-density>xxhdpi</screen-density>
+ <screen-density>xxxhdpi</screen-density>
+ </screen-density-group>
+ </screen-density-groups>
+
+ <locale-groups>
+ <locale-group label="europe">
+ <locale lang="en"/>
+ <locale lang="es"/>
+ <locale lang="fr"/>
+ <locale lang="de" compressed="true"/>
+ </locale-group>
+
+ <locale-group label="north-america">
+ <locale lang="en"/>
+ <locale lang="es" region="MX"/>
+ <locale lang="fr" region="CA" compressed="true"/>
+ </locale-group>
+
+ <locale-group label="all">
+ <locale compressed="true"/>
+ </locale-group>
+ </locale-groups>
+
+ <gl-texture-groups>
+ <gl-texture-group label="dxt1">
+ <gl-texture name="GL_EXT_texture_compression_dxt1">
+ <texture-path>assets/dxt1/*</texture-path>
+ </gl-texture>
+ </gl-texture-group>
+ </gl-texture-groups>
+
+ <device-feature-groups>
+ <device-feature-group label="low-latency">
+ <supports-feature>android.hardware.audio.low_latency</supports-feature>
+ </device-feature-group>
+ </device-feature-groups>
+
</post-process>
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 5078678..8d079ff 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -223,7 +223,7 @@
break;
case android::RES_TABLE_TYPE_SPEC_TYPE:
- if (!ParseTypeSpec(parser.chunk())) {
+ if (!ParseTypeSpec(package, parser.chunk())) {
return false;
}
break;
@@ -260,7 +260,8 @@
return true;
}
-bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
+bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package,
+ const ResChunk_header* chunk) {
if (type_pool_.getError() != NO_ERROR) {
diag_->Error(DiagMessage(source_) << "missing type string pool");
return false;
@@ -276,6 +277,34 @@
diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id);
return false;
}
+
+ // The data portion of this chunk contains entry_count 32bit entries,
+ // each one representing a set of flags.
+ const size_t entry_count = dtohl(type_spec->entryCount);
+
+ // There can only be 2^16 entries in a type, because that is the ID
+ // space for entries (EEEE) in the resource ID 0xPPTTEEEE.
+ if (entry_count > std::numeric_limits<uint16_t>::max()) {
+ diag_->Error(DiagMessage(source_)
+ << "ResTable_typeSpec has too many entries (" << entry_count << ")");
+ return false;
+ }
+
+ const size_t data_size = util::DeviceToHost32(type_spec->header.size) -
+ util::DeviceToHost16(type_spec->header.headerSize);
+ if (entry_count * sizeof(uint32_t) > data_size) {
+ diag_->Error(DiagMessage(source_) << "ResTable_typeSpec too small to hold entries.");
+ return false;
+ }
+
+ // Record the type_spec_flags for later. We don't know resource names yet, and we need those
+ // to mark resources as overlayable.
+ const uint32_t* type_spec_flags = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<uintptr_t>(type_spec) + util::DeviceToHost16(type_spec->header.headerSize));
+ for (size_t i = 0; i < entry_count; i++) {
+ ResourceId id(package->id.value_or_default(0x0), type_spec->id, static_cast<size_t>(i));
+ entry_type_spec_flags_[id] = util::DeviceToHost32(type_spec_flags[i]);
+ }
return true;
}
@@ -346,18 +375,34 @@
return false;
}
- if (!table_->AddResourceAllowMangled(name, res_id, config, {}, std::move(resource_value),
- diag_)) {
+ if (!table_->AddResourceWithIdMangled(name, res_id, config, {}, std::move(resource_value),
+ diag_)) {
return false;
}
- if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
- Symbol symbol;
- symbol.state = SymbolState::kPublic;
- symbol.source = source_.WithLine(0);
- if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, diag_)) {
- return false;
+ const uint32_t type_spec_flags = entry_type_spec_flags_[res_id];
+ if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0 ||
+ (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) != 0) {
+ if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
+ Visibility visibility;
+ visibility.level = Visibility::Level::kPublic;
+ visibility.source = source_.WithLine(0);
+ if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) {
+ return false;
+ }
}
+
+ if (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) {
+ Overlayable overlayable;
+ overlayable.source = source_.WithLine(0);
+ if (!table_->SetOverlayableMangled(name, overlayable, diag_)) {
+ return false;
+ }
+ }
+
+ // Erase the ID from the map once processed, so that we don't mark the same symbol more than
+ // once.
+ entry_type_spec_flags_.erase(res_id);
}
// Add this resource name->id mapping to the index so
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index 052f806..a1f9f83 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -50,7 +50,7 @@
bool ParseTable(const android::ResChunk_header* chunk);
bool ParsePackage(const android::ResChunk_header* chunk);
- bool ParseTypeSpec(const android::ResChunk_header* chunk);
+ bool ParseTypeSpec(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
bool ParseLibrary(const android::ResChunk_header* chunk);
@@ -105,6 +105,9 @@
// A mapping of resource ID to resource name. When we finish parsing
// we use this to convert all resource IDs to symbolic references.
std::map<ResourceId, ResourceName> id_index_;
+
+ // A mapping of resource ID to type spec flags.
+ std::unordered_map<ResourceId, uint32_t> entry_type_spec_flags_;
};
} // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index a3034df..24a4112 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -283,7 +283,7 @@
T* result = buffer->NextBlock<T>();
ResTable_entry* out_entry = (ResTable_entry*)result;
- if (entry->entry->symbol_status.state == SymbolState::kPublic) {
+ if (entry->entry->visibility.level == Visibility::Level::kPublic) {
out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
}
@@ -443,10 +443,15 @@
// Populate the config masks for this entry.
- if (entry->symbol_status.state == SymbolState::kPublic) {
+ if (entry->visibility.level == Visibility::Level::kPublic) {
config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
}
+ if (entry->overlayable) {
+ config_masks[entry->id.value()] |=
+ util::HostToDevice32(ResTable_typeSpec::SPEC_OVERLAYABLE);
+ }
+
const size_t config_count = entry->values.size();
for (size_t i = 0; i < config_count; i++) {
const ConfigDescription& config = entry->values[i]->config;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index f0b80d2..51ccdc7 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -26,6 +26,7 @@
using namespace android;
+using ::testing::Gt;
using ::testing::IsNull;
using ::testing::NotNull;
@@ -250,15 +251,15 @@
const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
const auto value =
util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
- CHECK(table->AddResource(name, resid, ConfigDescription::DefaultConfig(), "",
- std::unique_ptr<Value>(value->Clone(nullptr)),
- context->GetDiagnostics()));
+ CHECK(table->AddResourceWithId(name, resid, ConfigDescription::DefaultConfig(), "",
+ std::unique_ptr<Value>(value->Clone(nullptr)),
+ context->GetDiagnostics()));
// Every few entries, write out a sparse_config value. This will give us the desired load.
if (i % stride == 0) {
- CHECK(table->AddResource(name, resid, sparse_config, "",
- std::unique_ptr<Value>(value->Clone(nullptr)),
- context->GetDiagnostics()));
+ CHECK(table->AddResourceWithId(name, resid, sparse_config, "",
+ std::unique_ptr<Value>(value->Clone(nullptr)),
+ context->GetDiagnostics()));
}
}
return table;
@@ -568,4 +569,25 @@
ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
}
+TEST_F(TableFlattenerTest, FlattenOverlayable) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.test", 0x7f)
+ .AddSimple("com.app.test:integer/overlayable", ResourceId(0x7f020000))
+ .Build();
+
+ ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.test:integer/overlayable"),
+ Overlayable{}, test::GetDiagnostics()));
+
+ ResTable res_table;
+ ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
+
+ const StringPiece16 overlayable_name(u"com.app.test:integer/overlayable");
+ uint32_t spec_flags = 0u;
+ ASSERT_THAT(res_table.identifierForName(overlayable_name.data(), overlayable_name.size(), nullptr,
+ 0u, nullptr, 0u, &spec_flags),
+ Gt(0u));
+ EXPECT_TRUE(spec_flags & android::ResTable_typeSpec::SPEC_OVERLAYABLE);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 0f0bce8..3d6975d 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -358,16 +358,16 @@
out_source->line = static_cast<size_t>(pb_source.position().line_number());
}
-static SymbolState DeserializeVisibilityFromPb(const pb::SymbolStatus_Visibility& pb_visibility) {
- switch (pb_visibility) {
- case pb::SymbolStatus_Visibility_PRIVATE:
- return SymbolState::kPrivate;
- case pb::SymbolStatus_Visibility_PUBLIC:
- return SymbolState::kPublic;
+static Visibility::Level DeserializeVisibilityFromPb(const pb::Visibility::Level& pb_level) {
+ switch (pb_level) {
+ case pb::Visibility::PRIVATE:
+ return Visibility::Level::kPrivate;
+ case pb::Visibility::PUBLIC:
+ return Visibility::Level::kPublic;
default:
break;
}
- return SymbolState::kUndefined;
+ return Visibility::Level::kUndefined;
}
static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool,
@@ -402,28 +402,48 @@
}
// Deserialize the symbol status (public/private with source and comments).
- if (pb_entry.has_symbol_status()) {
- const pb::SymbolStatus& pb_status = pb_entry.symbol_status();
- if (pb_status.has_source()) {
- DeserializeSourceFromPb(pb_status.source(), src_pool, &entry->symbol_status.source);
+ if (pb_entry.has_visibility()) {
+ const pb::Visibility& pb_visibility = pb_entry.visibility();
+ if (pb_visibility.has_source()) {
+ DeserializeSourceFromPb(pb_visibility.source(), src_pool, &entry->visibility.source);
}
+ entry->visibility.comment = pb_visibility.comment();
- entry->symbol_status.comment = pb_status.comment();
- entry->symbol_status.allow_new = pb_status.allow_new();
-
- const SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility());
- entry->symbol_status.state = visibility;
- if (visibility == SymbolState::kPublic) {
+ const Visibility::Level level = DeserializeVisibilityFromPb(pb_visibility.level());
+ entry->visibility.level = level;
+ if (level == Visibility::Level::kPublic) {
// Propagate the public visibility up to the Type.
- type->symbol_status.state = SymbolState::kPublic;
- } else if (visibility == SymbolState::kPrivate) {
+ type->visibility_level = Visibility::Level::kPublic;
+ } else if (level == Visibility::Level::kPrivate) {
// Only propagate if no previous state was assigned.
- if (type->symbol_status.state == SymbolState::kUndefined) {
- type->symbol_status.state = SymbolState::kPrivate;
+ if (type->visibility_level == Visibility::Level::kUndefined) {
+ type->visibility_level = Visibility::Level::kPrivate;
}
}
}
+ if (pb_entry.has_allow_new()) {
+ const pb::AllowNew& pb_allow_new = pb_entry.allow_new();
+
+ AllowNew allow_new;
+ if (pb_allow_new.has_source()) {
+ DeserializeSourceFromPb(pb_allow_new.source(), src_pool, &allow_new.source);
+ }
+ allow_new.comment = pb_allow_new.comment();
+ entry->allow_new = std::move(allow_new);
+ }
+
+ if (pb_entry.has_overlayable()) {
+ const pb::Overlayable& pb_overlayable = pb_entry.overlayable();
+
+ Overlayable overlayable;
+ if (pb_overlayable.has_source()) {
+ DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &overlayable.source);
+ }
+ overlayable.comment = pb_overlayable.comment();
+ entry->overlayable = std::move(overlayable);
+ }
+
ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
pb_entry.entry_id().id());
if (resid.is_valid()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 97ce01a..78f1281 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -43,16 +43,16 @@
}
}
-static pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) {
+static pb::Visibility::Level SerializeVisibilityToPb(Visibility::Level state) {
switch (state) {
- case SymbolState::kPrivate:
- return pb::SymbolStatus_Visibility_PRIVATE;
- case SymbolState::kPublic:
- return pb::SymbolStatus_Visibility_PUBLIC;
+ case Visibility::Level::kPrivate:
+ return pb::Visibility::PRIVATE;
+ case Visibility::Level::kPublic:
+ return pb::Visibility::PUBLIC;
default:
break;
}
- return pb::SymbolStatus_Visibility_UNKNOWN;
+ return pb::Visibility::UNKNOWN;
}
void SerializeConfig(const ConfigDescription& config, pb::Configuration* out_pb_config) {
@@ -293,12 +293,26 @@
}
pb_entry->set_name(entry->name);
- // Write the SymbolStatus struct.
- pb::SymbolStatus* pb_status = pb_entry->mutable_symbol_status();
- pb_status->set_visibility(SerializeVisibilityToPb(entry->symbol_status.state));
- SerializeSourceToPb(entry->symbol_status.source, &source_pool, pb_status->mutable_source());
- pb_status->set_comment(entry->symbol_status.comment);
- pb_status->set_allow_new(entry->symbol_status.allow_new);
+ // Write the Visibility struct.
+ pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
+ pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level));
+ SerializeSourceToPb(entry->visibility.source, &source_pool,
+ pb_visibility->mutable_source());
+ pb_visibility->set_comment(entry->visibility.comment);
+
+ if (entry->allow_new) {
+ pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new();
+ SerializeSourceToPb(entry->allow_new.value().source, &source_pool,
+ pb_allow_new->mutable_source());
+ pb_allow_new->set_comment(entry->allow_new.value().comment);
+ }
+
+ if (entry->overlayable) {
+ pb::Overlayable* pb_overlayable = pb_entry->mutable_overlayable();
+ SerializeSourceToPb(entry->overlayable.value().source, &source_pool,
+ pb_overlayable->mutable_source());
+ pb_overlayable->set_comment(entry->overlayable.value().comment);
+ }
for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
pb::ConfigValue* pb_config_value = pb_entry->add_config_value();
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 9649a4d..d7f83fd 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -44,14 +44,15 @@
.AddReference("com.app.a:layout/other", ResourceId(0x7f020001), "com.app.a:layout/main")
.AddString("com.app.a:string/text", {}, "hi")
.AddValue("com.app.a:id/foo", {}, util::make_unique<Id>())
- .SetSymbolState("com.app.a:bool/foo", {}, SymbolState::kUndefined, true /*allow_new*/)
+ .SetSymbolState("com.app.a:bool/foo", {}, Visibility::Level::kUndefined,
+ true /*allow_new*/)
.Build();
- Symbol public_symbol;
- public_symbol.state = SymbolState::kPublic;
- ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"),
- ResourceId(0x7f020000), public_symbol,
- context->GetDiagnostics()));
+ Visibility public_symbol;
+ public_symbol.level = Visibility::Level::kPublic;
+ ASSERT_TRUE(table->SetVisibilityWithId(test::ParseNameOrDie("com.app.a:layout/main"),
+ public_symbol, ResourceId(0x7f020000),
+ context->GetDiagnostics()));
Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo");
ASSERT_THAT(id, NotNull());
@@ -89,6 +90,10 @@
test::ParseNameOrDie("com.app.a:layout/abc"), ConfigDescription::DefaultConfig(), {},
util::make_unique<Reference>(expected_ref), context->GetDiagnostics()));
+ // Make an overlayable resource.
+ ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
+ Overlayable{}, test::GetDiagnostics()));
+
pb::ResourceTable pb_table;
SerializeTableToPb(*table, &pb_table);
@@ -110,13 +115,13 @@
new_table.FindResource(test::ParseNameOrDie("com.app.a:layout/main"));
ASSERT_TRUE(result);
- EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic));
- EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));
+ EXPECT_THAT(result.value().type->visibility_level, Eq(Visibility::Level::kPublic));
+ EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
ASSERT_TRUE(result);
- EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined));
- EXPECT_TRUE(result.value().entry->symbol_status.allow_new);
+ EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
+ EXPECT_TRUE(result.value().entry->allow_new);
// Find the product-dependent values
BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
@@ -148,6 +153,12 @@
EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b"));
EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u));
EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u));
+
+ Maybe<ResourceTable::SearchResult> search_result =
+ new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable"));
+ ASSERT_TRUE(search_result);
+ ASSERT_THAT(search_result.value().entry, NotNull());
+ EXPECT_TRUE(search_result.value().entry->overlayable);
}
TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 8c8c254..6b07b1e 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -191,14 +191,14 @@
const JavaClassGeneratorOptions& options)
: context_(context), table_(table), options_(options) {}
-bool JavaClassGenerator::SkipSymbol(SymbolState state) {
+bool JavaClassGenerator::SkipSymbol(Visibility::Level level) {
switch (options_.types) {
case JavaClassGeneratorOptions::SymbolTypes::kAll:
return false;
case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
- return state == SymbolState::kUndefined;
+ return level == Visibility::Level::kUndefined;
case JavaClassGeneratorOptions::SymbolTypes::kPublic:
- return state != SymbolState::kPublic;
+ return level != Visibility::Level::kPublic;
}
return true;
}
@@ -444,8 +444,8 @@
AnnotationProcessor* processor = resource_member->GetCommentBuilder();
// Add the comments from any <public> tags.
- if (entry.symbol_status.state != SymbolState::kUndefined) {
- processor->AppendComment(entry.symbol_status.comment);
+ if (entry.visibility.level != Visibility::Level::kUndefined) {
+ processor->AppendComment(entry.visibility.comment);
}
// Add the comments from all configurations of this entry.
@@ -484,7 +484,7 @@
Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
const StringPiece& package_name_to_generate,
const ResourceEntry& entry) {
- if (SkipSymbol(entry.symbol_status.state)) {
+ if (SkipSymbol(entry.visibility.level)) {
return {};
}
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 4992f07..853120b 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -82,7 +82,7 @@
static std::string TransformToFieldName(const android::StringPiece& symbol);
private:
- bool SkipSymbol(SymbolState state);
+ bool SkipSymbol(Visibility::Level state);
bool SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol);
// Returns the unmangled resource entry name if the unmangled package is the same as
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 02f4cb1..5beb594 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -139,8 +139,8 @@
.AddSimple("android:id/one", ResourceId(0x01020000))
.AddSimple("android:id/two", ResourceId(0x01020001))
.AddSimple("android:id/three", ResourceId(0x01020002))
- .SetSymbolState("android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
- .SetSymbolState("android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
+ .SetSymbolState("android:id/one", ResourceId(0x01020000), Visibility::Level::kPublic)
+ .SetSymbolState("android:id/two", ResourceId(0x01020001), Visibility::Level::kPrivate)
.Build();
std::unique_ptr<IAaptContext> context =
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index a68df1d..da05dc3 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -430,7 +430,10 @@
return false;
}
- if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist, context->GetDiagnostics(), doc)) {
+ xml::XmlActionExecutorPolicy policy = options_.warn_validation
+ ? xml::XmlActionExecutorPolicy::kWhitelistWarning
+ : xml::XmlActionExecutorPolicy::kWhitelist;
+ if (!executor.Execute(policy, context->GetDiagnostics(), doc)) {
return false;
}
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index f5715f6..0caa52e 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -57,6 +57,11 @@
// The version codename of the framework being compiled against to set for
// 'android:compileSdkVersionCodename' in the <manifest> tag.
Maybe<std::string> compile_sdk_version_codename;
+
+ // Wether validation errors should be treated only as warnings. If this is 'true', then an
+ // incorrect node will not result in an error, but only as a warning, and the parsing will
+ // continue.
+ bool warn_validation = false;
};
// Verifies that the manifest is correctly formed and inserts defaults where specified with
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 1320dcd..c6f895b 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -112,7 +112,9 @@
}
TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
- ManifestFixerOptions options = {std::string("8"), std::string("22")};
+ ManifestFixerOptions options;
+ options.min_sdk_version_default = std::string("8");
+ options.target_sdk_version_default = std::string("22");
std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -193,7 +195,9 @@
}
TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) {
- ManifestFixerOptions options = {std::string("8"), std::string("22")};
+ ManifestFixerOptions options;
+ options.min_sdk_version_default = std::string("8");
+ options.target_sdk_version_default = std::string("22");
std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
@@ -467,4 +471,27 @@
EXPECT_THAT(attr->value, StrEq("P"));
}
+TEST_F(ManifestFixerTest, UnexpectedElementsInManifest) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <beep/>
+ </manifest>)";
+ ManifestFixerOptions options;
+ options.warn_validation = true;
+
+ // Unexpected element should result in a warning if the flag is set to 'true'.
+ std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+ ASSERT_THAT(manifest, NotNull());
+
+ // Unexpected element should result in an error if the flag is set to 'false'.
+ options.warn_validation = false;
+ manifest = VerifyWithOptions(input, options);
+ ASSERT_THAT(manifest, IsNull());
+
+ // By default the flag should be set to 'false'.
+ manifest = Verify(input);
+ ASSERT_THAT(manifest, IsNull());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index eee4b60..675b02a 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -62,7 +62,7 @@
continue;
}
- if (type->symbol_status.state != SymbolState::kPublic) {
+ if (type->visibility_level != Visibility::Level::kPublic) {
// No public attributes, so we can safely leave these private attributes
// where they are.
continue;
@@ -72,7 +72,7 @@
move_if(type->entries, std::back_inserter(private_attr_entries),
[](const std::unique_ptr<ResourceEntry>& entry) -> bool {
- return entry->symbol_status.state != SymbolState::kPublic;
+ return entry->visibility.level != Visibility::Level::kPublic;
});
if (private_attr_entries.empty()) {
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index 7fcf6e7..168234b 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -30,9 +30,9 @@
.AddSimple("android:attr/publicB")
.AddSimple("android:attr/privateB")
.SetSymbolState("android:attr/publicA", ResourceId(0x01010000),
- SymbolState::kPublic)
+ Visibility::Level::kPublic)
.SetSymbolState("android:attr/publicB", ResourceId(0x01010000),
- SymbolState::kPublic)
+ Visibility::Level::kPublic)
.Build();
PrivateAttributeMover mover;
@@ -81,7 +81,7 @@
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.AddSimple("android:attr/pub")
- .SetSymbolState("android:attr/pub", ResourceId(0x01010000), SymbolState::kPublic)
+ .SetSymbolState("android:attr/pub", ResourceId(0x01010000), Visibility::Level::kPublic)
.Build();
ResourceTablePackage* package = table->FindPackage("android");
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index ad7d8b6..b8f8804 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -363,8 +363,8 @@
NameMangler::Unmangle(&name.entry, &name.package);
// Symbol state information may be lost if there is no value for the resource.
- if (entry->symbol_status.state != SymbolState::kUndefined && entry->values.empty()) {
- context->GetDiagnostics()->Error(DiagMessage(entry->symbol_status.source)
+ if (entry->visibility.level != Visibility::Level::kUndefined && entry->values.empty()) {
+ context->GetDiagnostics()->Error(DiagMessage(entry->visibility.source)
<< "no definition for declared symbol '" << name << "'");
error = true;
}
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 58d0607..e819f51 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -83,44 +83,58 @@
static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type,
ResourceTableType* src_type) {
- if (dst_type->symbol_status.state < src_type->symbol_status.state) {
+ if (src_type->visibility_level > dst_type->visibility_level) {
// The incoming type's visibility is stronger, so we should override the visibility.
- if (src_type->symbol_status.state == SymbolState::kPublic) {
+ if (src_type->visibility_level == Visibility::Level::kPublic) {
// Only copy the ID if the source is public, or else the ID is meaningless.
dst_type->id = src_type->id;
}
- dst_type->symbol_status = std::move(src_type->symbol_status);
- } else if (dst_type->symbol_status.state == SymbolState::kPublic &&
- src_type->symbol_status.state == SymbolState::kPublic &&
- dst_type->id && src_type->id &&
- dst_type->id.value() != src_type->id.value()) {
+ dst_type->visibility_level = src_type->visibility_level;
+ } else if (dst_type->visibility_level == Visibility::Level::kPublic &&
+ src_type->visibility_level == Visibility::Level::kPublic && dst_type->id &&
+ src_type->id && dst_type->id.value() != src_type->id.value()) {
// Both types are public and have different IDs.
- context->GetDiagnostics()->Error(DiagMessage(src)
- << "cannot merge type '" << src_type->type
- << "': conflicting public IDs");
+ context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge type '" << src_type->type
+ << "': conflicting public IDs");
return false;
}
return true;
}
-static bool MergeEntry(IAaptContext* context, const Source& src, ResourceEntry* dst_entry,
- ResourceEntry* src_entry) {
- if (dst_entry->symbol_status.state < src_entry->symbol_status.state) {
- // The incoming type's visibility is stronger, so we should override the visibility.
- if (src_entry->symbol_status.state == SymbolState::kPublic) {
- // Only copy the ID if the source is public, or else the ID is meaningless.
+static bool MergeEntry(IAaptContext* context, const Source& src, bool overlay,
+ ResourceEntry* dst_entry, ResourceEntry* src_entry) {
+ // Copy over the strongest visibility.
+ if (src_entry->visibility.level > dst_entry->visibility.level) {
+ // Only copy the ID if the source is public, or else the ID is meaningless.
+ if (src_entry->visibility.level == Visibility::Level::kPublic) {
dst_entry->id = src_entry->id;
}
- dst_entry->symbol_status = std::move(src_entry->symbol_status);
- } else if (src_entry->symbol_status.state == SymbolState::kPublic &&
- dst_entry->symbol_status.state == SymbolState::kPublic &&
- dst_entry->id && src_entry->id &&
- dst_entry->id.value() != src_entry->id.value()) {
+ dst_entry->visibility = std::move(src_entry->visibility);
+ } else if (src_entry->visibility.level == Visibility::Level::kPublic &&
+ dst_entry->visibility.level == Visibility::Level::kPublic && dst_entry->id &&
+ src_entry->id && src_entry->id != dst_entry->id) {
// Both entries are public and have different IDs.
context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge entry '" << src_entry->name
<< "': conflicting public IDs");
return false;
}
+
+ // Copy over the rest of the properties, if needed.
+ if (src_entry->allow_new) {
+ dst_entry->allow_new = std::move(src_entry->allow_new);
+ }
+
+ if (src_entry->overlayable) {
+ if (dst_entry->overlayable && !overlay) {
+ context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source)
+ << "duplicate overlayable declaration for resource '"
+ << src_entry->name << "'");
+ context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source)
+ << "previous declaration here");
+ return false;
+ }
+ dst_entry->overlayable = std::move(src_entry->overlayable);
+ }
return true;
}
@@ -202,7 +216,7 @@
}
ResourceEntry* dst_entry;
- if (allow_new_resources || src_entry->symbol_status.allow_new) {
+ if (allow_new_resources || src_entry->allow_new) {
dst_entry = dst_type->FindOrCreateEntry(entry_name);
} else {
dst_entry = dst_type->FindEntry(entry_name);
@@ -220,7 +234,7 @@
continue;
}
- if (!MergeEntry(context_, src, dst_entry, src_entry.get())) {
+ if (!MergeEntry(context_, src, overlay, dst_entry, src_entry.get())) {
error = true;
continue;
}
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 6aab8de..34461c6 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -182,14 +182,12 @@
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
ResourceTable final_table;
@@ -205,14 +203,12 @@
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001), Visibility::Level::kPublic)
.Build();
ResourceTable final_table;
@@ -228,14 +224,12 @@
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002), Visibility::Level::kPublic)
.Build();
ResourceTable final_table;
@@ -253,7 +247,7 @@
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", {}, SymbolState::kUndefined, true /*allow new overlay*/)
+ .SetSymbolState("bool/foo", {}, Visibility::Level::kUndefined, true /*allow new overlay*/)
.AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
.Build();
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 16898d6..991faad 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -120,8 +120,6 @@
}
bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) {
- // TODO(safarmer): Handle APK version codes for the generated APKs.
-
std::unordered_set<std::string> artifacts_to_keep = options.kept_artifacts;
std::unordered_set<std::string> filtered_artifacts;
std::unordered_set<std::string> kept_artifacts;
@@ -237,8 +235,8 @@
splits.config_filter = &axis_filter;
}
- if (artifact.android_sdk && artifact.android_sdk.value().min_sdk_version) {
- wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version.value());
+ if (artifact.android_sdk) {
+ wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version);
}
std::unique_ptr<ResourceTable> table = old_table.Clone();
@@ -301,7 +299,7 @@
if (xml::Attribute* min_sdk_attr =
uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
// Populate with a pre-compiles attribute to we don't need to relink etc.
- const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version.value());
+ const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version);
min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
} else {
// There was no minSdkVersion. This is strange since at this point we should have been
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 0cfc0bd..3cae0e8 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -190,7 +190,7 @@
ResourceTable::SearchResult sr = result.value();
std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>();
- symbol->is_public = (sr.entry->symbol_status.state == SymbolState::kPublic);
+ symbol->is_public = (sr.entry->visibility.level == Visibility::Level::kPublic);
if (sr.package->id && sr.type->id && sr.entry->id) {
symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 9d49ca6..e991743 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -233,13 +233,13 @@
ResourceTableType* split_type = split_pkg->FindOrCreateType(type->type);
if (!split_type->id) {
split_type->id = type->id;
- split_type->symbol_status = type->symbol_status;
+ split_type->visibility_level = type->visibility_level;
}
ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name);
if (!split_entry->id) {
split_entry->id = entry->id;
- split_entry->symbol_status = entry->symbol_status;
+ split_entry->visibility = entry->visibility;
}
// Copy the selected values into the new Split Entry.
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 88897a8..495a48a 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -26,6 +26,7 @@
using ::aapt::configuration::Abi;
using ::aapt::configuration::AndroidSdk;
using ::aapt::configuration::ConfiguredArtifact;
+using ::aapt::configuration::GetOrCreateGroup;
using ::aapt::io::StringInputStream;
using ::android::StringPiece;
@@ -116,19 +117,20 @@
const ResourceId& id,
std::unique_ptr<Value> value) {
ResourceName res_name = ParseNameOrDie(name);
- CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value),
- GetDiagnostics()));
+ CHECK(table_->AddResourceWithIdMangled(res_name, id, config, {}, std::move(value),
+ GetDiagnostics()));
return *this;
}
ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name,
- const ResourceId& id, SymbolState state,
+ const ResourceId& id,
+ Visibility::Level level,
bool allow_new) {
ResourceName res_name = ParseNameOrDie(name);
- Symbol symbol;
- symbol.state = state;
- symbol.allow_new = allow_new;
- CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics()));
+ Visibility visibility;
+ visibility.level = level;
+ CHECK(table_->SetVisibilityWithIdMangled(res_name, visibility, id, GetDiagnostics()));
+ CHECK(table_->SetAllowNewMangled(res_name, AllowNew{}, GetDiagnostics()));
return *this;
}
@@ -226,6 +228,11 @@
return *this;
}
+ArtifactBuilder& ArtifactBuilder::SetVersion(int version) {
+ artifact_.version = version;
+ return *this;
+}
+
ArtifactBuilder& ArtifactBuilder::AddAbi(configuration::Abi abi) {
artifact_.abis.push_back(abi);
return *this;
@@ -250,5 +257,54 @@
return artifact_;
}
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddAbiGroup(
+ const std::string& label, std::vector<configuration::Abi> abis) {
+ return AddGroup(label, &config_.abi_groups, std::move(abis));
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddDensityGroup(
+ const std::string& label, std::vector<std::string> densities) {
+ std::vector<ConfigDescription> configs;
+ for (const auto& density : densities) {
+ configs.push_back(test::ParseConfigOrDie(density));
+ }
+ return AddGroup(label, &config_.screen_density_groups, configs);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddLocaleGroup(
+ const std::string& label, std::vector<std::string> locales) {
+ std::vector<ConfigDescription> configs;
+ for (const auto& locale : locales) {
+ configs.push_back(test::ParseConfigOrDie(locale));
+ }
+ return AddGroup(label, &config_.locale_groups, configs);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddDeviceFeatureGroup(
+ const std::string& label) {
+ return AddGroup(label, &config_.device_feature_groups);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddGlTextureGroup(
+ const std::string& label) {
+ return AddGroup(label, &config_.gl_texture_groups);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddAndroidSdk(
+ std::string label, int min_sdk) {
+ config_.android_sdks[label] = AndroidSdk::ForMinSdk(min_sdk);
+ return *this;
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddArtifact(
+ configuration::ConfiguredArtifact artifact) {
+ config_.artifacts.push_back(std::move(artifact));
+ return *this;
+}
+
+configuration::PostProcessingConfiguration PostProcessingConfigurationBuilder::Build() {
+ return config_;
+}
+
} // namespace test
} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 2f83b78..0d7451b 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -68,7 +68,7 @@
ResourceTableBuilder& AddValue(const android::StringPiece& name, const ConfigDescription& config,
const ResourceId& id, std::unique_ptr<Value> value);
ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
- SymbolState state, bool allow_new = false);
+ Visibility::Level level, bool allow_new = false);
StringPool* string_pool();
std::unique_ptr<ResourceTable> Build();
@@ -160,6 +160,7 @@
ArtifactBuilder() = default;
ArtifactBuilder& SetName(const std::string& name);
+ ArtifactBuilder& SetVersion(int version);
ArtifactBuilder& AddAbi(configuration::Abi abi);
ArtifactBuilder& AddDensity(const ConfigDescription& density);
ArtifactBuilder& AddLocale(const ConfigDescription& locale);
@@ -167,9 +168,41 @@
configuration::OutputArtifact Build();
private:
+ DISALLOW_COPY_AND_ASSIGN(ArtifactBuilder);
+
configuration::OutputArtifact artifact_;
};
+class PostProcessingConfigurationBuilder {
+ public:
+ PostProcessingConfigurationBuilder() = default;
+
+ PostProcessingConfigurationBuilder& AddAbiGroup(const std::string& label,
+ std::vector<configuration::Abi> abis = {});
+ PostProcessingConfigurationBuilder& AddDensityGroup(const std::string& label,
+ std::vector<std::string> densities = {});
+ PostProcessingConfigurationBuilder& AddLocaleGroup(const std::string& label,
+ std::vector<std::string> locales = {});
+ PostProcessingConfigurationBuilder& AddDeviceFeatureGroup(const std::string& label);
+ PostProcessingConfigurationBuilder& AddGlTextureGroup(const std::string& label);
+ PostProcessingConfigurationBuilder& AddAndroidSdk(std::string label, int min_sdk);
+ PostProcessingConfigurationBuilder& AddArtifact(configuration::ConfiguredArtifact artrifact);
+
+ configuration::PostProcessingConfiguration Build();
+
+ private:
+ template <typename T>
+ inline PostProcessingConfigurationBuilder& AddGroup(const std::string& label,
+ configuration::Group<T>* group,
+ std::vector<T> to_add = {}) {
+ auto& values = GetOrCreateGroup(label, group);
+ values.insert(std::begin(values), std::begin(to_add), std::end(to_add));
+ return *this;
+ }
+
+ configuration::PostProcessingConfiguration config_;
+};
+
} // namespace test
} // namespace aapt
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index 602a902..cb844f0 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -66,7 +66,7 @@
continue;
}
- if (policy == XmlActionExecutorPolicy::kWhitelist) {
+ if (policy != XmlActionExecutorPolicy::kNone) {
DiagMessage error_msg(child_el->line_number);
error_msg << "unexpected element ";
PrintElementToDiagMessage(child_el, &error_msg);
@@ -74,8 +74,14 @@
for (const StringPiece& element : *bread_crumb) {
error_msg << "<" << element << ">";
}
- diag->Error(error_msg);
- error = true;
+ if (policy == XmlActionExecutorPolicy::kWhitelistWarning) {
+ // Treat the error only as a warning.
+ diag->Warn(error_msg);
+ } else {
+ // Policy is XmlActionExecutorPolicy::kWhitelist, we should fail.
+ diag->Error(error_msg);
+ error = true;
+ }
}
}
}
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
index df70100..f689b2a 100644
--- a/tools/aapt2/xml/XmlActionExecutor.h
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -34,10 +34,15 @@
// Actions are run if elements are matched, errors occur only when actions return false.
kNone,
- // The actions defined must match and run. If an element is found that does
- // not match an action, an error occurs.
+ // The actions defined must match and run. If an element is found that does not match an action,
+ // an error occurs.
// Note: namespaced elements are always ignored.
kWhitelist,
+
+ // The actions defined should match and run. if an element is found that does not match an
+ // action, a warning is printed.
+ // Note: namespaced elements are always ignored.
+ kWhitelistWarning,
};
// Contains the actions to perform at this XML node. This is a recursive data structure that
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 4e9391d..1121ead 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -32,26 +32,6 @@
shared_libs: ["libprotoc"],
}
-cc_library {
- name: "streamingflags",
- host_supported: true,
- proto: {
- export_proto_headers: true,
- include_dirs: ["external/protobuf/src"],
- },
-
- target: {
- host: {
- proto: {
- type: "full",
- },
- srcs: [
- "stream.proto",
- ],
- },
- },
-}
-
cc_binary_host {
name: "protoc-gen-javastream",
srcs: [
@@ -68,5 +48,4 @@
],
defaults: ["protoc-gen-stream-defaults"],
- static_libs: ["streamingflags"],
}
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index 745b3dc..d6b9d81 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -2,8 +2,6 @@
#include "stream_proto_utils.h"
#include "string_utils.h"
-#include <frameworks/base/tools/streaming_proto/stream.pb.h>
-
#include <iomanip>
#include <iostream>
#include <sstream>
@@ -12,18 +10,14 @@
using namespace google::protobuf::io;
using namespace std;
+const bool GENERATE_MAPPING = true;
+
static string
make_filename(const FileDescriptorProto& file_descriptor)
{
return file_descriptor.name() + ".h";
}
-static inline bool
-should_generate_enums_mapping(const EnumDescriptorProto& enu)
-{
- return enu.options().GetExtension(stream_enum).enable_enums_mapping();
-}
-
static void
write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
{
@@ -36,7 +30,7 @@
<< " = " << value.number() << ";" << endl;
}
- if (should_generate_enums_mapping(enu)) {
+ if (GENERATE_MAPPING) {
string name = make_constant_name(enu.name());
string prefix = name + "_";
text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
@@ -79,23 +73,11 @@
text << endl;
}
-static inline bool
-should_generate_fields_mapping(const DescriptorProto& message)
-{
- return message.options().GetExtension(stream_msg).enable_fields_mapping();
-}
-
-static inline bool
-should_generate_fields_mapping_recursively(const DescriptorProto& message) {
- return message.options().GetExtension(stream_msg).enable_fields_mapping_recursively();
-}
-
static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent, bool genMapping)
+write_message(stringstream& text, const DescriptorProto& message, const string& indent)
{
int N;
const string indented = indent + INDENT;
- genMapping |= should_generate_fields_mapping_recursively(message);
text << indent << "// message " << message.name() << endl;
text << indent << "namespace " << message.name() << " {" << endl;
@@ -109,7 +91,7 @@
// Nested classes
N = message.nested_type_size();
for (int i=0; i<N; i++) {
- write_message(text, message.nested_type(i), indented, genMapping);
+ write_message(text, message.nested_type(i), indented);
}
// Fields
@@ -118,7 +100,7 @@
write_field(text, message.field(i), indented);
}
- if (genMapping | should_generate_fields_mapping(message)) {
+ if (GENERATE_MAPPING) {
N = message.field_size();
text << indented << "const int _FIELD_COUNT = " << N << ";" << endl;
text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl;
@@ -167,7 +149,7 @@
N = file_descriptor.message_type_size();
for (size_t i=0; i<N; i++) {
- write_message(text, file_descriptor.message_type(i), "", false);
+ write_message(text, file_descriptor.message_type(i), "");
}
for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) {
diff --git a/tools/streaming_proto/stream.proto b/tools/streaming_proto/stream.proto
deleted file mode 100644
index e9b24a8..0000000
--- a/tools/streaming_proto/stream.proto
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto2";
-
-import "google/protobuf/descriptor.proto";
-
-package android.stream_proto;
-
-// This option tells streaming proto plugin to compile .proto files with extra features.
-message MessageOptions {
- // creates a mapping of field names of the message to its field ids
- optional bool enable_fields_mapping = 1;
-
- // creates mapping between field names to its field ids and recursively for its submessages.
- optional bool enable_fields_mapping_recursively = 2;
-}
-
-extend google.protobuf.MessageOptions {
- // Flags used by streaming proto plugins
- optional MessageOptions stream_msg = 126856794;
-}
-
-message EnumOptions {
- // creates a mapping of enum names to its values, strip its prefix enum type for each value
- optional bool enable_enums_mapping = 1;
-}
-
-extend google.protobuf.EnumOptions {
- // Flags used by streaming proto plugins
- optional EnumOptions stream_enum = 126856794;
-}