diff options
656 files changed, 20614 insertions, 3506 deletions
diff --git a/Android.bp b/Android.bp index 979ed1cbf06c..e23d9c245c89 100644 --- a/Android.bp +++ b/Android.bp @@ -215,7 +215,6 @@ java_library { "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 @@ java_library { "libphonenumber-platform", "nist-sip", "tagsoup", + "rappor", ], dxflags: ["--core-library"], } @@ -693,7 +693,6 @@ cc_library { srcs: [ "core/proto/**/*.proto", "libs/incident/**/*.proto", - "tools/streaming_proto/stream.proto", ], target: { diff --git a/Android.mk b/Android.mk index 103536297838..995630b7ac68 100644 --- a/Android.mk +++ b/Android.mk @@ -274,6 +274,9 @@ non_base_dirs := \ ../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 @@ framework_base_android_test_runner_src_files := \ # 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_check_apis_generated := \ 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 @@ framework_docs_LOCAL_SRC_FILES := \ # 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 @@ LOCAL_PROTOC_FLAGS := \ -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 9bdcdadc7841..60d314f5cc1b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3953,7 +3953,7 @@ package android.app { 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 @@ package android.app { 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 @@ package android.app { 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 @@ package android.app { 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 @@ package android.app.admin { 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,10 +7035,12 @@ package android.app.slice { 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"; field public static final java.lang.String SUBTYPE_MESSAGE = "message"; + field public static final java.lang.String SUBTYPE_PRIORITY = "priority"; field public static final java.lang.String SUBTYPE_SLIDER = "slider"; field public static final java.lang.String SUBTYPE_SOURCE = "source"; field public static final java.lang.String SUBTYPE_TOGGLE = "toggle"; @@ -8221,7 +8228,7 @@ package android.bluetooth { 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); @@ -32257,6 +32264,7 @@ package android.os { method public deprecated void setUserRestrictions(android.os.Bundle); method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle); method public static boolean supportsMultipleUsers(); + method public boolean trySetQuietModeEnabled(boolean, android.os.UserHandle); field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking"; field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile"; field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; @@ -37692,11 +37700,8 @@ package android.service.autofill { field public static final android.os.Parcelable.Creator<android.service.autofill.EditDistanceScorer> CREATOR; } - public final class FieldClassification implements android.os.Parcelable { - method public int describeContents(); + public final class FieldClassification { method public java.util.List<android.service.autofill.FieldClassification.Match> getMatches(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.autofill.FieldClassification> CREATOR; } public static final class FieldClassification.Match { @@ -40344,6 +40349,7 @@ package android.telephony { 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 @@ -41066,6 +41072,7 @@ package android.telephony { 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(); @@ -41099,11 +41106,12 @@ package android.telephony { 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(); @@ -41112,10 +41120,11 @@ package android.telephony { 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); @@ -41511,61 +41520,6 @@ package android.telephony.mbms { } -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 { @@ -41859,25 +41813,6 @@ package android.test.mock { } -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 { @@ -41981,9 +41916,9 @@ package android.text { } 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); @@ -42370,9 +42305,9 @@ package android.text { } 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); @@ -44789,6 +44724,14 @@ package android.view { 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(); @@ -47742,8 +47685,10 @@ package android.view { 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(); @@ -47803,6 +47748,7 @@ package android.view { 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 @@ -47897,6 +47843,7 @@ package android.view { 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; @@ -49193,9 +49140,13 @@ package android.view.textclassifier { 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"; @@ -49205,6 +49156,13 @@ package android.view.textclassifier { 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(); @@ -49219,7 +49177,9 @@ package android.view.textclassifier { 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 { @@ -49920,6 +49880,7 @@ package android.webkit { 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>); @@ -49980,6 +49941,7 @@ package android.webkit { 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 be4d5be1e1a3..1a4e796bda15 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -506,14 +506,6 @@ package android.view { } -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 227b4833cf76..ec509ad59539 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -103,6 +103,7 @@ package android { field public static final deprecated java.lang.String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING"; field public static final java.lang.String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS"; field public static final java.lang.String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE"; + field public static final java.lang.String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE"; field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS"; field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"; field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE"; @@ -1339,7 +1340,7 @@ package android.hardware.hdmi { 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 +4160,7 @@ package android.telephony { 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 +4606,7 @@ package android.webkit { 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 5635b5653a77..e64c32087f1e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -326,6 +326,13 @@ package android.net { 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 4777dcd32377..162a34b957f8 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 @@ void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs) { } 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 c37f05e16508..1c6d9b09a839 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -79,7 +79,8 @@ message Atom { 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 @@ message AppHook { } /** + * 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 200ef0bb700b..5d0e97e24f79 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -206,7 +206,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti // 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 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti 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 6e7a613b05a3..3018be145ad8 100644 --- a/cmds/statsd/src/packages/UidMap.cpp +++ b/cmds/statsd/src/packages/UidMap.cpp @@ -31,10 +31,9 @@ namespace android { 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 @@ bool UidMap::hasApp(int uid, const string& packageName) const { 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 @@ void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const i 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 @@ void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const i 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::ensureBytesUsedBelowLimit() { 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 9e1ad6946af2..487fdf945f20 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 @@ public: // 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 @@ public: 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 @@ private: } // 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 5b2ceddc6858..3fa96d392927 100644 --- a/cmds/statsd/tests/UidMap_test.cpp +++ b/cmds/statsd/tests/UidMap_test.cpp @@ -74,6 +74,14 @@ TEST(UidMapTest, TestMatching) { 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 @@ TEST(UidMapTest, TestAddAndRemove) { 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 847082a0a4e1..1adae7a84fcc 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -457,6 +457,20 @@ public class ActivityManager { /** @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 @@ public class ActivityManager { /** @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 @@ public class ActivityManager { * 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 @@ public class ActivityManager { * 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 class ActivityManager { 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 class ActivityManager { 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 class ActivityManager { 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 @@ public class ActivityManager { 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 @@ public class ActivityManager { 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 @@ public class ActivityManager { 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 @@ public class ActivityManager { 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 84f032d432ea..de346f315016 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1152,7 +1152,7 @@ public final class ActivityThread extends ClientTransactionHandler { 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 @@ public final class ActivityThread extends ClientTransactionHandler { } @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 @@ public final class ActivityThread extends ClientTransactionHandler { * * @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 @@ public final class ActivityThread extends ClientTransactionHandler { 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 @@ public final class ActivityThread extends ClientTransactionHandler { 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 @@ public final class ActivityThread extends ClientTransactionHandler { } } - 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 @@ public final class ActivityThread extends ClientTransactionHandler { 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 @@ public final class ActivityThread extends ClientTransactionHandler { } } - 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 a0fb6eeb9e15..3a355d97a5c2 100644 --- a/core/java/android/app/DialogFragment.java +++ b/core/java/android/app/DialogFragment.java @@ -137,7 +137,9 @@ import java.io.PrintWriter; * {@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 a92684b5d304..4ff07f2edbf5 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -257,7 +257,9 @@ import java.lang.reflect.InvocationTargetException; * 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 a1dd32ffe95d..536c866083d7 100644 --- a/core/java/android/app/FragmentContainer.java +++ b/core/java/android/app/FragmentContainer.java @@ -25,7 +25,8 @@ import android.view.View; /** * 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 cbb58d402bca..40bc2483aa40 100644 --- a/core/java/android/app/FragmentController.java +++ b/core/java/android/app/FragmentController.java @@ -38,7 +38,8 @@ import java.util.List; * 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 1edc68eda808..b48817b1554d 100644 --- a/core/java/android/app/FragmentHostCallback.java +++ b/core/java/android/app/FragmentHostCallback.java @@ -38,7 +38,8 @@ import java.io.PrintWriter; * 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 12e60b874702..708450f672ee 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -75,7 +75,9 @@ import java.util.concurrent.CopyOnWriteArrayList; * <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 @@ public abstract class FragmentManager { * 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 @@ public abstract class FragmentManager { /** * 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 @@ public abstract class FragmentManager { * 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 beb1a15adae3..326438af1bd3 100644 --- a/core/java/android/app/FragmentManagerNonConfig.java +++ b/core/java/android/app/FragmentManagerNonConfig.java @@ -28,7 +28,8 @@ import java.util.List; * {@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 1103649cb9a9..713a559afdc8 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -22,7 +22,8 @@ import java.lang.annotation.RetentionPolicy; * 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 b25d7782652f..893a41cc992b 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -110,6 +110,9 @@ oneway interface IApplicationThread { 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 49d58eb2c38d..80782e3adb08 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -155,4 +155,9 @@ interface IWallpaperManager { * 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 90b77b398f44..7790f70b4f25 100644 --- a/core/java/android/app/ListFragment.java +++ b/core/java/android/app/ListFragment.java @@ -145,7 +145,9 @@ import android.widget.TextView; * @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 7969684ab5a9..86d0fd6272d2 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -55,14 +55,16 @@ import java.lang.reflect.Modifier; * <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 705f9a0513f6..85c3be826c0d 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1090,6 +1090,12 @@ public class Notification implements Parcelable 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 class Notification implements Parcelable 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 @@ public class Notification implements Parcelable } /** - * 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 @@ public class Notification implements Parcelable } /** + * 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 @@ public class Notification implements Parcelable } fixTitleAndTextExtras(extras); + extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation); } private void fixTitleAndTextExtras(Bundle extras) { @@ -6136,6 +6162,7 @@ public class Notification implements Parcelable 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 9d40381fcefd..35a17892eec3 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.AttributeSet; 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 @@ public final class WallpaperInfo implements Parcelable { final int mContextUriResource; final int mContextDescriptionResource; final boolean mShowMetadataInPreview; + final boolean mSupportsAmbientMode; /** * Constructor. @@ -89,15 +90,7 @@ public final class WallpaperInfo implements Parcelable { 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 @@ public final class WallpaperInfo implements Parcelable { 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 @@ public final class WallpaperInfo implements Parcelable { } 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 @@ public final class WallpaperInfo implements Parcelable { mContextUriResource = source.readInt(); mContextDescriptionResource = source.readInt(); mShowMetadataInPreview = source.readInt() != 0; + mSupportsAmbientMode = source.readInt() != 0; mService = ResolveInfo.CREATOR.createFromParcel(source); } @@ -326,6 +315,16 @@ public final class WallpaperInfo implements Parcelable { } /** + * 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 @@ public final class WallpaperInfo implements Parcelable { 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 89df421efcc1..98162976173d 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2635,10 +2635,121 @@ public class DevicePolicyManager { } /** + * 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 @@ public class DevicePolicyManager { * @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 @@ public class DevicePolicyManager { 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 912820818a78..5b02c221332a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -78,6 +78,10 @@ interface IDevicePolicyManager { 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 @@ interface IDevicePolicyManager { 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 1aff7e9c9eff..5c7f67411a83 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -63,7 +63,7 @@ public final class Slice implements Parcelable { HINT_ACTIONS, HINT_SELECTED, HINT_NO_TINT, - HINT_HIDDEN, + HINT_SHORTCUT, HINT_TOGGLE, HINT_HORIZONTAL, HINT_PARTIAL, @@ -118,12 +118,10 @@ public final class Slice implements Parcelable { */ 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. @@ -182,6 +180,10 @@ public final class Slice implements Parcelable { * which can be retrieved to see the new state of the toggle. */ public static final String SUBTYPE_TOGGLE = "toggle"; + /** + * Subtype to tag an item representing priority. + */ + public static final String SUBTYPE_PRIORITY = "priority"; private final SliceItem[] mItems; private final @SliceHint String[] mHints; diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java index f38e462eab4f..2fab305ba8bd 100644 --- a/core/java/android/bluetooth/BluetoothHidDevice.java +++ b/core/java/android/bluetooth/BluetoothHidDevice.java @@ -85,7 +85,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { * * @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 @@ public final class BluetoothHidDevice implements BluetoothProfile { } @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 bd19955b4e62..e71b00f2349f 100644 --- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java +++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java @@ -106,8 +106,8 @@ public abstract class BluetoothHidDeviceCallback { * @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 6e9f09cbef0e..c44e35687909 100644 --- a/core/java/android/content/AsyncTaskLoader.java +++ b/core/java/android/content/AsyncTaskLoader.java @@ -50,7 +50,8 @@ import java.util.concurrent.Executor; * * @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 7f24c51d37a1..5a08636c8fff 100644 --- a/core/java/android/content/CursorLoader.java +++ b/core/java/android/content/CursorLoader.java @@ -39,7 +39,8 @@ import java.util.Arrays; * {@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 80f9a14c6e18..b0555d4ce7e0 100644 --- a/core/java/android/content/Loader.java +++ b/core/java/android/content/Loader.java @@ -49,7 +49,8 @@ import java.io.PrintWriter; * * @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 @@ public class Loader<D> { writer.print(" mReset="); writer.println(mReset); } } -}
\ No newline at end of file +} diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index e2fd82d1f3e1..21e203bc50d7 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -87,7 +87,6 @@ import android.util.SparseArray; import android.util.TypedValue; import android.util.apk.ApkSignatureSchemeV2Verifier; import android.util.apk.ApkSignatureVerifier; -import android.util.apk.SignatureNotFoundException; import android.view.Gravity; import com.android.internal.R; @@ -1561,41 +1560,35 @@ public class PackageParser { boolean systemDir = (parseFlags & PARSE_IS_SYSTEM_DIR) != 0; int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME; - if ((parseFlags & PARSE_IS_EPHEMERAL) != 0 || pkg.applicationInfo.isStaticSharedLibrary()) { + if (pkg.applicationInfo.isStaticSharedLibrary()) { // must use v2 signing scheme minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2; } - try { - ApkSignatureVerifier.Result verified = - ApkSignatureVerifier.verify(apkPath, minSignatureScheme, systemDir); - if (pkg.mCertificates == null) { - pkg.mCertificates = verified.certs; - pkg.mSignatures = verified.sigs; - pkg.mSigningKeys = new ArraySet<>(verified.certs.length); - for (int i = 0; i < verified.certs.length; i++) { - Certificate[] signerCerts = verified.certs[i]; - Certificate signerCert = signerCerts[0]; - pkg.mSigningKeys.add(signerCert.getPublicKey()); - } - } else { - if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) { - throw new PackageParserException( - INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, - apkPath + " has mismatched certificates"); - } - } - } catch (SignatureNotFoundException e) { + ApkSignatureVerifier.Result verified = + ApkSignatureVerifier.verify(apkPath, minSignatureScheme, systemDir); + if (verified.signatureSchemeVersion + < ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2) { + // TODO (b/68860689): move this logic to packagemanagerserivce if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No APK Signature Scheme v2 signature in ephemeral package " + apkPath, - e); + "No APK Signature Scheme v2 signature in ephemeral package " + apkPath); } - if (pkg.applicationInfo.isStaticSharedLibrary()) { - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "Static shared libs must use v2 signature scheme " + apkPath); + } + if (pkg.mCertificates == null) { + pkg.mCertificates = verified.certs; + pkg.mSignatures = verified.sigs; + pkg.mSigningKeys = new ArraySet<>(verified.certs.length); + for (int i = 0; i < verified.certs.length; i++) { + Certificate[] signerCerts = verified.certs[i]; + Certificate signerCert = signerCerts[0]; + pkg.mSigningKeys.add(signerCert.getPublicKey()); + } + } else { + if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) { + throw new PackageParserException( + INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, + apkPath + " has mismatched certificates"); } - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No APK Signature Scheme v2 signature in package " + apkPath, e); } } diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index 61b0eb0b51f8..30222b74bdc1 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -36,15 +36,26 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.List; /** - * The ShortcutManager manages an app's <em>shortcuts</em>. Shortcuts provide users with quick - * access to activities other than an app's main activity in the currently-active launcher, provided - * that the launcher supports app shortcuts. For example, an email app may publish the "compose new - * email" action, which will directly open the compose activity. The {@link ShortcutInfo} class - * contains information about each of the shortcuts themselves. + * 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 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 + * example, an email app may publish the "compose new email" shortcut, which allows the app to + * directly open the compose activity.</p> + * + * <p class="note"><b>Note:</b> Only main activities—activities that handle the + * {@link Intent#ACTION_MAIN} action and the {@link Intent#CATEGORY_LAUNCHER} category—can + * have shortcuts. If an app has multiple main activities, you need to define the set of shortcuts + * for <em>each</em> activity. * * <p>This page discusses the implementation details of the <code>ShortcutManager</code> class. For - * guidance on performing operations on app shortcuts within your app, see the - * <a href="/guide/topics/ui/shortcuts.html">App Shortcuts</a> feature guide. + * definitions of key terms and guidance on performing operations on shortcuts within your app, see + * the <a href="/guide/topics/ui/shortcuts.html">App Shortcuts</a> feature guide. * * <h3>Shortcut characteristics</h3> * @@ -69,8 +80,8 @@ import java.util.List; * <ul> * <li>The user removes it. * <li>The publisher app associated with the shortcut is uninstalled. - * <li>The user performs the clear data action on the publisher app from the device's - * <b>Settings</b> app. + * <li>The user selects <b>Clear data</b> from the publisher app's <i>Storage</i> screen, within + * the system's <b>Settings</b> app. * </ul> * * <p>Because the system performs @@ -83,12 +94,17 @@ import java.util.List; * * <p>When the launcher displays an app's shortcuts, they should appear in the following order: * - * <ul> - * <li>Static shortcuts (if {@link ShortcutInfo#isDeclaredInManifest()} is {@code true}), - * and then show dynamic shortcuts (if {@link ShortcutInfo#isDynamic()} is {@code true}). - * <li>Within each shortcut type (static and dynamic), sort the shortcuts 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> * * <p>Shortcut ranks are non-negative, sequential integers that determine the order in which * shortcuts appear, assuming that the shortcuts are all in the same category. You can update ranks @@ -103,64 +119,99 @@ import java.util.List; * * <h3>Options for static shortcuts</h3> * - * The following list includes descriptions for the different attributes within a static shortcut: + * The following list includes descriptions for the different attributes within a static shortcut. + * You must provide a value for {@code android:shortcutId} and {@code android:shortcutShortLabel}; + * all other values are optional. + * * <dl> * <dt>{@code android:shortcutId}</dt> - * <dd>Mandatory shortcut ID. - * <p> - * This must be a string literal. - * A resource string, such as <code>@string/foo</code>, cannot be used. + * <dd><p>A string literal, which represents the shortcut when a {@code ShortcutManager} object + * performs operations on it.</p> + * <p class="note"><b>Note: </b>You cannot set this attribute's value to a resource string, such + * as <code>@string/foo</code>.</p> * </dd> * * <dt>{@code android:enabled}</dt> - * <dd>Default is {@code true}. Can be set to {@code false} in order - * to disable a static shortcut that was published in a previous version and set a custom - * disabled message. If a custom disabled message is not needed, then a static shortcut can - * be simply removed from the XML file rather than keeping it with {@code enabled="false"}.</dd> + * <dd><p>Whether the user can interact with the shortcut from a supported launcher.</p> + * <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 the shortcut's + * {@code android:enabled} and {@code android:shortcutDisabledMessage} attributes. + * </dd> * * <dt>{@code android:icon}</dt> - * <dd>Shortcut icon.</dd> + * <dd><p>The <a href="/topic/performance/graphics/index.html">bitmap</a> or + * <a href="/guide/practices/ui_guidelines/icon_design_adaptive.html">adaptive icon</a> that the + * launcher uses when displaying the shortcut to the user. This value can be either the path to an + * image or the resource file that contains the image. Use adaptive icons whenever possible to + * improve performance and consistency.</p> + * <p class="note"><b>Note: </b>Shortcut icons cannot include + * <a href="/training/material/drawables.html#DrawableTint">tints</a>. + * </dd> * * <dt>{@code android:shortcutShortLabel}</dt> - * <dd>Mandatory shortcut short label. - * See {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}. - * <p> - * This must be a resource string, such as <code>@string/shortcut_label</code>. + * <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_short_label</code>.</p> * </dd> * * <dt>{@code android:shortcutLongLabel}</dt> - * <dd>Shortcut long label. - * See {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}. - * <p> - * This must be a resource string, such as <code>@string/shortcut_long_label</code>. + * <dd><p>An extended phrase that describes the shortcut's purpose. If there's enough space, the + * launcher displays this value instead of {@code android:shortcutShortLabel}. For more + * information, see {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}.</p> + * <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as + * <code>@string/shortcut_long_label</code>.</p> * </dd> * * <dt>{@code android:shortcutDisabledMessage}</dt> - * <dd>When {@code android:enabled} is set to - * {@code false}, this attribute is used to display a custom disabled message. - * <p> - * This must be a resource string, such as <code>@string/shortcut_disabled_message</code>. + * <dd><p>The message that appears in a supported launcher when the user attempts to launch a + * 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> + * </dl> + * + * <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 + * static shortcut that you define.</p> * + * <dl> * <dt>{@code intent}</dt> - * <dd>Intent to launch when the user selects the shortcut. - * {@code android:action} is mandatory. - * See <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a> for the - * other supported tags. - * <p>You can provide multiple intents for a single shortcut so that the last defined activity is - * launched with the other activities in the + * <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. 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 - * {@link android.app.TaskStackBuilder} for details. - * <p><b>Note:</b> String resources may not be used within an {@code <intent>} element. + * <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>To learn more about how to configure intents, see + * <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a>.</p> * </dd> + * * <dt>{@code categories}</dt> - * <dd>Specify shortcut categories. Currently only - * {@link ShortcutInfo#SHORTCUT_CATEGORY_CONVERSATION} is defined in the framework. + * <dd><p>Provides a grouping for the types of actions that your app's shortcuts perform, such as + * creating new chat messages.</p> + * <p>For a list of supported shortcut categories, see the {@link ShortcutInfo} class reference + * for a list of supported shortcut categories. * </dd> * </dl> * * <h3>Updating shortcuts</h3> * + * <p>Each app's launcher icon can contain at most {@link #getMaxShortcutCountPerActivity()} number + * of static and dynamic shortcuts combined. There is no limit to the number of pinned shortcuts + * that an app can create, though. + * + * <p>When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut, + * the pinned shortcut is still visible and launchable. This allows an app to have more than + * {@link #getMaxShortcutCountPerActivity()} number of shortcuts. + * * <p>As an example, suppose {@link #getMaxShortcutCountPerActivity()} is 5: * <ol> * <li>A chat app publishes 5 dynamic shortcuts for the 5 most recent @@ -168,18 +219,13 @@ import java.util.List; * * <li>The user pins all 5 of the shortcuts. * - * <li>Later, the user has started 3 additional conversations (c6, c7, and c8), - * so the publisher app - * re-publishes its dynamic shortcuts. The new dynamic shortcut list is: - * c4, c5, ..., c8. - * The publisher app has to remove c1, c2, and c3 because it can't have more than - * 5 dynamic shortcuts. - * - * <li>However, even though c1, c2, and c3 are no longer dynamic shortcuts, the pinned - * shortcuts for these conversations are still available and launchable. - * - * <li>At this point, the user can access a total of 8 shortcuts that link to activities in - * the publisher app, including the 3 pinned shortcuts, even though an app can have at most 5 + * <li>Later, the user has started 3 additional conversations (c6, c7, and c8), so the publisher + * app re-publishes its dynamic shortcuts. The new dynamic shortcut list is: c4, c5, ..., c8. + * <p>The publisher app has to remove c1, c2, and c3 because it can't have more than 5 dynamic + * shortcuts. However, c1, c2, and c3 are still pinned shortcuts that the user can access and + * launch. + * <p>At this point, the user can access a total of 8 shortcuts that link to activities in the + * publisher app, including the 3 pinned shortcuts, even though an app can have at most 5 * dynamic shortcuts. * * <li>The app can use {@link #updateShortcuts(List)} to update <em>any</em> of the existing @@ -196,44 +242,23 @@ import java.util.List; * Dynamic shortcuts can be published with any set of {@link Intent#addFlags Intent} flags. * Typically, {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} is specified, possibly along with other * flags; otherwise, if the app is already running, the app is simply brought to - * the foreground, and the target activity may not appear. + * the foreground, and the target activity might not appear. * * <p>Static shortcuts <b>cannot</b> have custom intent flags. * The first intent of a static shortcut will always have {@link Intent#FLAG_ACTIVITY_NEW_TASK} * and {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} set. This means, when the app is already running, all - * the existing activities in your app will be destroyed when a static shortcut is launched. + * the existing activities in your app are destroyed when a static shortcut is launched. * If this behavior is not desirable, you can use a <em>trampoline activity</em>, or an invisible * activity that starts another activity in {@link Activity#onCreate}, then calls * {@link Activity#finish()}: * <ol> * <li>In the <code>AndroidManifest.xml</code> file, the trampoline activity should include the * attribute assignment {@code android:taskAffinity=""}. - * <li>In the shortcuts resource file, the intent within the static shortcut should point at + * <li>In the shortcuts resource file, the intent within the static shortcut should reference * the trampoline activity. * </ol> * - * <h3>Handling system locale changes</h3> - * - * <p>Apps should update dynamic and pinned shortcuts when the system locale changes using the - * {@link Intent#ACTION_LOCALE_CHANGED} broadcast. When the system locale changes, - * <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate limiting</a> is reset, so even - * background apps can add and update dynamic shortcuts until the rate limit is reached again. - * - * <h3>Shortcut limits</h3> - * - * <p>Only main activities—activities that handle the {@code MAIN} action and the - * {@code LAUNCHER} category—can have shortcuts. If an app has multiple main activities, you - * need to define the set of shortcuts for <em>each</em> activity. - * - * <p>Each launcher icon can have at most {@link #getMaxShortcutCountPerActivity()} number of - * static and dynamic shortcuts combined. There is no limit to the number of pinned shortcuts that - * an app can create. - * - * <p>When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut, - * the pinned shortcut is still visible and launchable. This allows an app to have more than - * {@link #getMaxShortcutCountPerActivity()} number of shortcuts. - * - * <h4>Rate limiting</h4> + * <h3>Rate limiting</h3> * * <p>When <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate limiting</a> is active, * {@link #isRateLimitingActive()} returns {@code true}. @@ -243,8 +268,20 @@ import java.util.List; * <ul> * <li>An app comes to the foreground. * <li>The system locale changes. - * <li>The user performs the <strong>inline reply</strong> action on a notification. + * <li>The user performs the <a href="/guide/topics/ui/notifiers/notifications.html#direct">inline + * reply</a> action on a notification. * </ul> + * + * <h3>Handling system locale changes</h3> + * + * <p>Apps should update dynamic and pinned shortcuts when they receive the + * {@link Intent#ACTION_LOCALE_CHANGED} broadcast, indicating that the system locale has changed. + * <p>When the system locale changes, <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate + * limiting</a> is reset, so even background apps can add and update dynamic shortcuts until the + * rate limit is reached again. + * + * <h3>Retrieving class instances</h3> + * <!-- Provides a heading for the content filled in by the @SystemService annotation below --> */ @SystemService(Context.SHORTCUT_SERVICE) public class ShortcutManager { diff --git a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java index e02e68dced95..5bf3a7c43640 100644 --- a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java +++ b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java @@ -37,7 +37,7 @@ public class SQLiteCompatibilityWalFlags { 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 7409671f9b9f..a85b5f710696 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -486,6 +486,7 @@ public final class OutputConfiguration implements Parcelable { this.mConfiguredSize = other.mConfiguredSize; this.mConfiguredGenerationId = other.mConfiguredGenerationId; this.mIsDeferredConfig = other.mIsDeferredConfig; + this.mIsShared = other.mIsShared; } /** @@ -498,6 +499,7 @@ public final class OutputConfiguration implements Parcelable { 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 @@ public final class OutputConfiguration implements Parcelable { 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 e1137aa51348..c2b280016f68 100644 --- a/core/java/android/hardware/location/ContextHubInfo.java +++ b/core/java/android/hardware/location/ContextHubInfo.java @@ -26,7 +26,7 @@ import java.util.Arrays; * @hide */ @SystemApi -public class ContextHubInfo { +public class ContextHubInfo implements Parcelable { private int mId; private String mName; private String mVendor; @@ -262,7 +262,7 @@ public class ContextHubInfo { @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 @@ public class ContextHubInfo { 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 5b89f54b5050..6da6fb7bfdb7 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -258,9 +258,9 @@ public final class ContextHubManager { } /** - * 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 @@ public final class ContextHubManager { */ @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 db5bd36e1109..233e857d8e67 100644 --- a/core/java/android/hardware/location/IContextHubService.aidl +++ b/core/java/android/hardware/location/IContextHubService.aidl @@ -43,23 +43,26 @@ interface IContextHubService { 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 b7e6b66fb755..f73fd87b1a19 100644 --- a/core/java/android/hardware/location/NanoAppInstanceInfo.java +++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java @@ -27,15 +27,13 @@ import libcore.util.EmptyArray; * 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 class NanoAppInstanceInfo { 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 8071e8b83738..11d338d05c68 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1794,7 +1794,7 @@ public class ConnectivityManager { 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 954e59c2c424..d701550817df 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -19,6 +19,7 @@ package android.net; 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 @@ public class TrafficStats { 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 @@ public class TrafficStats { 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 2e9eeb16a887..9513b9bfea03 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -658,32 +658,40 @@ public abstract class BatteryStats implements Parcelable { */ 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 2acf36fed85f..848ab88d3cbc 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -1136,7 +1136,7 @@ public final class Debug int intervalUs) { VMDebug.startMethodTracing(fixTracePath(tracePath), bufferSize, 0, true, intervalUs); } - + /** * Formats name of trace log file for method tracing. */ @@ -1706,11 +1706,11 @@ public final class Debug * 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 1a76648c2565..b67b99fabb9e 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/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 9c90c3802caf..f643c578ebe7 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -79,9 +79,7 @@ interface IUserManager { void setDefaultGuestRestrictions(in Bundle restrictions); Bundle getDefaultGuestRestrictions(); boolean markGuestForDeletion(int userHandle); - void setQuietModeEnabled(int userHandle, boolean enableQuietMode, in IntentSender target); boolean isQuietModeEnabled(int userHandle); - boolean trySetQuietModeDisabled(int userHandle, in IntentSender target); void setSeedAccountData(int userHandle, in String accountName, in String accountType, in PersistableBundle accountOptions, boolean persist); String getSeedAccountName(); @@ -99,4 +97,5 @@ interface IUserManager { boolean isUserRunning(int userId); boolean isUserNameSet(int userHandle); boolean hasRestrictedProfiles(); + boolean trySetQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target); } diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index b9b9a18e361d..bbb8a7b5d5f6 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -334,6 +334,23 @@ public class RemoteCallbackList<E extends IInterface> { } /** + * 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/UserManager.java b/core/java/android/os/UserManager.java index 3504142a81c9..75cbd57fe178 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2130,15 +2130,46 @@ public class UserManager { } /** - * Set quiet mode of a managed profile. + * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a + * managed profile don't run, generate notifications, or consume data or battery. + * <p> + * If a user's credential is needed to turn off quiet mode, a confirm credential screen will be + * shown to the user. + * <p> + * The change may not happen instantly, however apps can listen for + * {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and + * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE} broadcasts in order to be notified of + * the change of the quiet mode. Apps can also check the current state of quiet mode by + * calling {@link #isQuietModeEnabled(UserHandle)}. + * <p> + * The caller must either be the foreground default launcher or have one of these permissions: + * {@code MANAGE_USERS} or {@code MODIFY_QUIET_MODE}. + * + * @param enableQuietMode whether quiet mode should be enabled or disabled + * @param userHandle user handle of the profile + * @return {@code false} if user's credential is needed in order to turn off quiet mode, + * {@code true} otherwise + * @throws SecurityException if the caller is invalid + * @throws IllegalArgumentException if {@code userHandle} is not a managed profile + * + * @see #isQuietModeEnabled(UserHandle) + */ + public boolean trySetQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) { + return trySetQuietModeEnabled(enableQuietMode, userHandle, null); + } + + /** + * Similar to {@link #trySetQuietModeEnabled(boolean, UserHandle)}, except you can specify + * a target to start when user is unlocked. * - * @param userHandle The user handle of the profile. - * @param enableQuietMode Whether quiet mode should be enabled or disabled. + * @see {@link #trySetQuietModeEnabled(boolean, UserHandle)} * @hide */ - public void setQuietModeEnabled(@UserIdInt int userHandle, boolean enableQuietMode) { + public boolean trySetQuietModeEnabled( + boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) { try { - mService.setQuietModeEnabled(userHandle, enableQuietMode, null); + return mService.trySetQuietModeEnabled( + mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2160,27 +2191,6 @@ public class UserManager { } /** - * Tries disabling quiet mode for a given user. If the user is still locked, we unlock the user - * first by showing the confirm credentials screen and disable quiet mode upon successful - * unlocking. If the user is already unlocked, we call through to {@link #setQuietModeEnabled} - * directly. - * - * @param userHandle The user that is going to disable quiet mode. - * @param target The target to launch when the user is unlocked. - * @return {@code true} if quiet mode is disabled without showing confirm credentials screen, - * {@code false} otherwise. - * @hide - */ - public boolean trySetQuietModeDisabled( - @UserIdInt int userHandle, @Nullable IntentSender target) { - try { - return mService.trySetQuietModeDisabled(userHandle, target); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** * If the target user is a managed profile of the calling user or the caller * is itself a managed profile, then this returns a badged copy of the given * icon to be able to distinguish it from the original icon. For badging an diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index ecec448aba4c..8632aad87dac 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 @@ public class WorkSource implements Parcelable { 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 class WorkSource implements Parcelable { */ public WorkSource() { mNum = 0; + mChains = null; } /** @@ -48,6 +54,7 @@ public class WorkSource implements Parcelable { public WorkSource(WorkSource orig) { if (orig == null) { mNum = 0; + mChains = null; return; } mNum = orig.mNum; @@ -58,6 +65,16 @@ public class WorkSource implements Parcelable { 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 @@ public class WorkSource implements Parcelable { mNum = 1; mUids = new int[] { uid, 0 }; mNames = null; + mChains = null; } /** @hide */ @@ -75,12 +93,21 @@ public class WorkSource implements Parcelable { 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 @@ public class WorkSource implements Parcelable { } /** - * 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 class WorkSource implements Parcelable { */ 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 @@ public class WorkSource implements Parcelable { result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode(); } } + + if (mChains != null) { + result = ((result << 4) | (result >>> 28)) ^ mChains.hashCode(); + } + return result; } @@ -153,6 +191,8 @@ public class WorkSource implements Parcelable { * @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 @@ public class WorkSource implements Parcelable { /** * 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 @@ public class WorkSource implements Parcelable { 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 @@ public class WorkSource implements Parcelable { if (mUids == null) mUids = new int[2]; mUids[0] = uid; mNames = null; + mChains.clear(); } /** @hide */ @@ -225,9 +281,21 @@ public class WorkSource implements Parcelable { } 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 class WorkSource implements Parcelable { */ 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 @@ public class WorkSource implements Parcelable { 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 @@ public class WorkSource implements Parcelable { throw new IllegalArgumentException("Target " + this + " has names, but other " + other + " does not"); } - return removeUidsAndNames(other); + uidRemoved = removeUidsAndNames(other); } - } - /** @hide */ - public WorkSource stripNames() { - if (mNum <= 0) { - return new WorkSource(); - } - 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); + boolean chainRemoved = false; + if (other.mChains != null) { + if (mChains != null) { + chainRemoved = mChains.removeAll(other.mChains); } + } else if (mChains != null) { + mChains.clear(); + chainRemoved = true; } - return result; + + return uidRemoved || chainRemoved; + } + + /** + * Create a new {@code WorkChain} associated with this WorkSource and return it. + * + * @hide + */ + public WorkChain createWorkChain() { + if (mChains == null) { + mChains = new ArrayList<>(4); + } + + 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 @@ public class WorkSource implements Parcelable { } } + /** + * 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 @@ public class WorkSource implements Parcelable { 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 @@ public class WorkSource implements Parcelable { 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 000000000000..e14893e7dd03 --- /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 000000000000..9355d6a5c723 --- /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 000000000000..ee910fc1a39d --- /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 000000000000..219868d663d2 --- /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 000000000000..221999bf0a95 --- /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 000000000000..2eca4c98d235 --- /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/KeyDerivationParameters.java b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java index 2205c416921d..978e60eec3a5 100644 --- a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java +++ b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java @@ -60,7 +60,7 @@ public final class KeyDerivationParameters implements Parcelable { /** * Creates instance of the class to to derive key using salted SHA256 hash. */ - public KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) { + public static KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) { return new KeyDerivationParameters(ALGORITHM_SHA256, salt); } diff --git a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl index 1058463aa561..1674e51f77e1 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 0510320d3e11..f2f225df7f93 100644 --- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java +++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java @@ -19,37 +19,40 @@ package android.security.recoverablekeystore; 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; - - // Exception codes, should be in sync with {@code KeyStoreException}. - public static final int SYSTEM_ERROR = 4; + public static final String PERMISSION_RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE"; + 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 @@ public class RecoverableKeyStoreLoader { } /** + * 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; + + /** + * 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); + } - public RecoverableKeyStoreLoaderException(int errorCode, String message) { + private RecoverableKeyStoreLoaderException(int errorCode, String message) { super(message); - mErrorCode = errorCode; } + /** 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 @@ public class RecoverableKeyStoreLoader { * @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 @@ public class RecoverableKeyStoreLoader { * @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 2600f8ab4d69..917efa8bd5f4 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -438,7 +438,7 @@ import com.android.internal.os.SomeArgs; * 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/autofill/FieldClassification.java b/core/java/android/service/autofill/FieldClassification.java index b28c6f81e782..001b2917aadf 100644 --- a/core/java/android/service/autofill/FieldClassification.java +++ b/core/java/android/service/autofill/FieldClassification.java @@ -20,31 +20,39 @@ import static android.view.autofill.Helper.sDebug; import android.annotation.NonNull; import android.os.Parcel; -import android.os.Parcelable; import android.view.autofill.Helper; import com.android.internal.util.Preconditions; -import com.google.android.collect.Lists; - +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; /** * Represents the <a href="AutofillService.html#FieldClassification">field classification</a> * results for a given field. */ -// TODO(b/70291841): let caller handle Parcelable... -public final class FieldClassification implements Parcelable { +public final class FieldClassification { - private final Match mMatch; + private final ArrayList<Match> mMatches; /** @hide */ - public FieldClassification(@NonNull Match match) { - mMatch = Preconditions.checkNotNull(match); + public FieldClassification(@NonNull ArrayList<Match> matches) { + mMatches = Preconditions.checkNotNull(matches); + Collections.sort(mMatches, new Comparator<Match>() { + @Override + public int compare(Match o1, Match o2) { + if (o1.mScore > o2.mScore) return -1; + if (o1.mScore < o2.mScore) return 1; + return 0; + }} + ); } /** - * Gets the {@link Match matches} with the highest {@link Match#getScore() scores}. + * Gets the {@link Match matches} with the highest {@link Match#getScore() scores} (sorted in + * descending order). * * <p><b>Note:</b> There's no guarantee of how many matches will be returned. In fact, * the Android System might return just the top match to minimize the impact of field @@ -52,43 +60,48 @@ public final class FieldClassification implements Parcelable { */ @NonNull public List<Match> getMatches() { - return Lists.newArrayList(mMatch); + return mMatches; } @Override public String toString() { if (!sDebug) return super.toString(); - return "FieldClassification: " + mMatch; + return "FieldClassification: " + mMatches; } - ///////////////////////////////////// - // Parcelable "contract" methods. // - ///////////////////////////////////// - - @Override - public int describeContents() { - return 0; + private void writeToParcel(Parcel parcel) { + parcel.writeInt(mMatches.size()); + for (int i = 0; i < mMatches.size(); i++) { + mMatches.get(i).writeToParcel(parcel); + } } - @Override - public void writeToParcel(Parcel parcel, int flags) { - mMatch.writeToParcel(parcel); - } + private static FieldClassification readFromParcel(Parcel parcel) { + final int size = parcel.readInt(); + final ArrayList<Match> matches = new ArrayList<>(); + for (int i = 0; i < size; i++) { + matches.add(i, Match.readFromParcel(parcel)); + } - public static final Parcelable.Creator<FieldClassification> CREATOR = - new Parcelable.Creator<FieldClassification>() { + return new FieldClassification(matches); + } - @Override - public FieldClassification createFromParcel(Parcel parcel) { - return new FieldClassification(Match.readFromParcel(parcel)); + static FieldClassification[] readArrayFromParcel(Parcel parcel) { + final int length = parcel.readInt(); + final FieldClassification[] fcs = new FieldClassification[length]; + for (int i = 0; i < length; i++) { + fcs[i] = readFromParcel(parcel); } + return fcs; + } - @Override - public FieldClassification[] newArray(int size) { - return new FieldClassification[size]; + static void writeArrayToParcel(@NonNull Parcel parcel, @NonNull FieldClassification[] fcs) { + parcel.writeInt(fcs.length); + for (int i = 0; i < fcs.length; i++) { + fcs[i].writeToParcel(parcel); } - }; + } /** * Represents the score of a {@link UserData} entry for the field. @@ -151,23 +164,5 @@ public final class FieldClassification implements Parcelable { private static Match readFromParcel(@NonNull Parcel parcel) { return new Match(parcel.readString(), parcel.readFloat()); } - - /** @hide */ - public static Match[] readArrayFromParcel(@NonNull Parcel parcel) { - final int length = parcel.readInt(); - final Match[] matches = new Match[length]; - for (int i = 0; i < length; i++) { - matches[i] = readFromParcel(parcel); - } - return matches; - } - - /** @hide */ - public static void writeArrayToParcel(@NonNull Parcel parcel, @NonNull Match[] matches) { - parcel.writeInt(matches.length); - for (int i = 0; i < matches.length; i++) { - matches[i].writeToParcel(parcel); - } - } } } diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java index 07fab611e6be..df62446427d3 100644 --- a/core/java/android/service/autofill/FillEventHistory.java +++ b/core/java/android/service/autofill/FillEventHistory.java @@ -25,7 +25,6 @@ import android.content.IntentSender; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.service.autofill.FieldClassification.Match; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -157,7 +156,8 @@ public final class FillEventHistory implements Parcelable { final AutofillId[] detectedFields = event.mDetectedFieldIds; parcel.writeParcelableArray(detectedFields, flags); if (detectedFields != null) { - Match.writeArrayToParcel(parcel, event.mDetectedMatches); + FieldClassification.writeArrayToParcel(parcel, + event.mDetectedFieldClassifications); } } } @@ -251,7 +251,7 @@ public final class FillEventHistory implements Parcelable { @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds; @Nullable private final AutofillId[] mDetectedFieldIds; - @Nullable private final Match[] mDetectedMatches; + @Nullable private final FieldClassification[] mDetectedFieldClassifications; /** * Returns the type of the event. @@ -370,11 +370,11 @@ public final class FillEventHistory implements Parcelable { final ArrayMap<AutofillId, FieldClassification> map = new ArrayMap<>(size); for (int i = 0; i < size; i++) { final AutofillId id = mDetectedFieldIds[i]; - final Match match = mDetectedMatches[i]; + final FieldClassification fc = mDetectedFieldClassifications[i]; if (sVerbose) { - Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", match=" + match); + Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", fc=" + fc); } - map.put(id, new FieldClassification(match)); + map.put(id, fc); } return map; } @@ -455,7 +455,7 @@ public final class FillEventHistory implements Parcelable { * and belonged to datasets. * @param manuallyFilledDatasetIds The ids of datasets that had values matching the * respective entry on {@code manuallyFilledFieldIds}. - * @param detectedMatches the field classification matches. + * @param detectedFieldClassifications the field classification matches. * * @throws IllegalArgumentException If the length of {@code changedFieldIds} and * {@code changedDatasetIds} doesn't match. @@ -471,7 +471,8 @@ public final class FillEventHistory implements Parcelable { @Nullable ArrayList<String> changedDatasetIds, @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, - @Nullable AutofillId[] detectedFieldIds, @Nullable Match[] detectedMatches) { + @Nullable AutofillId[] detectedFieldIds, + @Nullable FieldClassification[] detectedFieldClassifications) { mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED, "eventType"); mDatasetId = datasetId; @@ -496,7 +497,7 @@ public final class FillEventHistory implements Parcelable { mManuallyFilledDatasetIds = manuallyFilledDatasetIds; mDetectedFieldIds = detectedFieldIds; - mDetectedMatches = detectedMatches; + mDetectedFieldClassifications = detectedFieldClassifications; } @Override @@ -510,7 +511,8 @@ public final class FillEventHistory implements Parcelable { + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds + ", detectedFieldIds=" + Arrays.toString(mDetectedFieldIds) - + ", detectedMaches =" + Arrays.toString(mDetectedMatches) + + ", detectedFieldClassifications =" + + Arrays.toString(mDetectedFieldClassifications) + "]"; } } @@ -548,15 +550,16 @@ public final class FillEventHistory implements Parcelable { } final AutofillId[] detectedFieldIds = parcel.readParcelableArray(null, AutofillId.class); - final Match[] detectedMatches = (detectedFieldIds != null) - ? Match.readArrayFromParcel(parcel) + final FieldClassification[] detectedFieldClassifications = + (detectedFieldIds != null) + ? FieldClassification.readArrayFromParcel(parcel) : null; selection.addEvent(new Event(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, - detectedFieldIds, detectedMatches)); + detectedFieldIds, detectedFieldClassifications)); } return selection; } diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl index fb6f637fd927..b8f2191fccfd 100644 --- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl +++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl @@ -27,6 +27,7 @@ oneway interface IWallpaperEngine { 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 e5ab3e1caa3d..595bfb7a8c90 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -102,6 +102,7 @@ public abstract class WallpaperService extends Service { 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 @@ public abstract class WallpaperService extends Service { float mPendingYOffsetStep; boolean mPendingSync; MotionEvent mPendingMove; + boolean mIsInAmbientMode; // Needed for throttling onComputeColors. private long mLastColorInvalidation; @@ -431,6 +433,15 @@ public abstract class WallpaperService extends Service { 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 @@ public abstract class WallpaperService extends Service { } /** + * 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 @@ public abstract class WallpaperService extends Service { 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 @@ public abstract class WallpaperService extends Service { } Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event); mCaller.sendMessage(msg); - } else {event.recycle(); + } else { + event.recycle(); } } @@ -986,6 +1017,26 @@ public abstract class WallpaperService extends Service { 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 @@ public abstract class WallpaperService extends Service { 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 @@ public abstract class WallpaperService extends Service { mCaller.sendMessage(msg); } + @Override public void executeMessage(Message message) { switch (message.what) { case DO_ATTACH: { @@ -1289,6 +1347,11 @@ public abstract class WallpaperService extends Service { } 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 fba358cf4c1b..6bca37af376a 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -42,8 +42,7 @@ import java.lang.ref.WeakReference; * {@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 @@ public class DynamicLayout extends Layout } /** - * 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 @@ public class DynamicLayout extends Layout } /** - * 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 @@ public class DynamicLayout extends Layout } /** - * 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 @@ public class DynamicLayout extends Layout * 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 @@ public class DynamicLayout extends Layout } } - 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 @@ public class DynamicLayout extends Layout 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 @@ public class DynamicLayout extends Layout } 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 @@ public class DynamicLayout extends Layout 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); + } + if (block > lastBlock) { + // block index is after lastBlock, the index reduced to += changedBlockCount + block += changedBlockCount; + set.add(block); } - set.add(block); } mBlocksAlwaysNeedToBeRedrawn = set; } diff --git a/core/java/android/text/OWNERS b/core/java/android/text/OWNERS new file mode 100644 index 000000000000..0f85e1f9c5d9 --- /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 2e10fe8d4267..d69b1190140f 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -454,6 +454,10 @@ public class StaticLayout extends Layout { 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 @@ public class StaticLayout extends Layout { } /** - * @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 @@ public class StaticLayout extends Layout { } /** - * @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 @@ public class StaticLayout extends Layout { /** * @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 @@ public class StaticLayout extends Layout { Builder.recycle(b); } + /** + * Used by DynamicLayout. + */ /* package */ StaticLayout(@Nullable CharSequence text) { super(text, null, 0, null, 0, 0); diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 73a9478509e6..75c1000ba41d 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -65,18 +65,14 @@ public class ApkSignatureVerifier { * v2 stripping rollback protection, or verify integrity of the APK. * * @throws PackageParserException if the APK's signature failed to verify. - * @throws SignatureNotFoundException if a signature corresponding to minLevel or greater - * is not found, except in the case of no JAR signature. */ public static Result verify(String apkPath, int minSignatureSchemeVersion, boolean systemDir) - throws PackageParserException, SignatureNotFoundException { - boolean verified = false; - Certificate[][] signerCerts; - int level = VERSION_APK_SIGNATURE_SCHEME_V2; + throws PackageParserException { // first try v2 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2"); try { + Certificate[][] signerCerts; signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath); Signature[] signerSigs = convertToSignatures(signerCerts); @@ -93,12 +89,12 @@ public class ApkSignatureVerifier { } finally { closeQuietly(jarFile); } - return new Result(signerCerts, signerSigs); + return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2); } catch (SignatureNotFoundException e) { // not signed with v2, try older if allowed if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) { - throw new SignatureNotFoundException( - "No APK Signature Scheme v2 signature found for " + apkPath, e); + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No APK Signature Scheme v2 signature in package " + apkPath, e); } } catch (PackageParserException e) { // preserve any new exceptions explicitly thrown here @@ -178,7 +174,7 @@ public class ApkSignatureVerifier { } } } - return new Result(lastCerts, lastSigs); + return new Result(lastCerts, lastSigs, VERSION_JAR_SIGNATURE_SCHEME); } catch (GeneralSecurityException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, "Failed to collect certificates from " + apkPath, e); @@ -254,10 +250,12 @@ public class ApkSignatureVerifier { public static class Result { public final Certificate[][] certs; public final Signature[] sigs; + public final int signatureSchemeVersion; - public Result(Certificate[][] certs, Signature[] sigs) { + public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) { this.certs = certs; this.sigs = sigs; + this.signatureSchemeVersion = signingVersion; } } } diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index 19cd42e1fa18..e448f14ca97d 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_180; 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 @@ public final class DisplayCutout { * @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. - * - * If false, the safe insets will all return zero, and the bounding box or polygon will be - * empty or outside the content view. + * Returns true if there is no cutout or it is outside of 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 @@ public final class DisplayCutout { } /** - * 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 @@ public final class DisplayCutout { 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 @@ public final class DisplayCutout { @Override public String toString() { return "DisplayCutout{insets=" + mSafeInsets - + " bounding=" + mBoundingRect + + " bounds=" + mBounds + "}"; } @@ -172,15 +161,13 @@ public final class DisplayCutout { * @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 @@ public final class DisplayCutout { 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 @@ public final class DisplayCutout { * @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 @@ public final class DisplayCutout { 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 @@ public final class DisplayCutout { 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 @@ public final class DisplayCutout { * @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(); + + 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); - return new DisplayCutout(ZERO_RECT, boundingRect, boundingPolygon); + bounds.setPath(path, clipRegion); + return new DisplayCutout(ZERO_RECT, bounds); } /** @@ -336,8 +321,7 @@ public final class DisplayCutout { } 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 @@ public final class DisplayCutout { 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/View.java b/core/java/android/view/View.java index 02beee05811b..cc63a62c5651 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -26890,7 +26890,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mAttachInfo == null || mTooltipInfo == null) { return false; } - if ((mViewFlags & ENABLED_MASK) != ENABLED) { + if (fromLongClick && (mViewFlags & ENABLED_MASK) != ENABLED) { return false; } if (TextUtils.isEmpty(mTooltipInfo.mTooltipText)) { @@ -26938,7 +26938,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } switch(event.getAction()) { case MotionEvent.ACTION_HOVER_MOVE: - if ((mViewFlags & TOOLTIP) != TOOLTIP || (mViewFlags & ENABLED_MASK) != ENABLED) { + if ((mViewFlags & TOOLTIP) != TOOLTIP) { break; } if (!mTooltipInfo.mTooltipFromLongClick && mTooltipInfo.updateAnchorPos(event)) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3f8da093487b..6c5091c28708 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -327,6 +327,8 @@ public final class ViewRootImpl implements ViewParent, // This is used to reduce the race between window focus changes being dispatched from // the window manager and input events coming through the input system. @GuardedBy("this") + boolean mWindowFocusChanged; + @GuardedBy("this") boolean mUpcomingWindowFocus; @GuardedBy("this") boolean mUpcomingInTouchMode; @@ -1600,7 +1602,7 @@ public final class ViewRootImpl implements ViewParent, 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); } @@ -2472,14 +2474,14 @@ public final class ViewRootImpl implements ViewParent, final boolean hasWindowFocus; final boolean inTouchMode; synchronized (this) { + if (!mWindowFocusChanged) { + return; + } + mWindowFocusChanged = false; hasWindowFocus = mUpcomingWindowFocus; inTouchMode = mUpcomingInTouchMode; } - if (mAttachInfo.mHasWindowFocus == hasWindowFocus) { - return; - } - if (mAdded) { profileRendering(hasWindowFocus); @@ -7181,6 +7183,7 @@ public final class ViewRootImpl implements ViewParent, public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { synchronized (this) { + mWindowFocusChanged = true; mUpcomingWindowFocus = hasFocus; mUpcomingInTouchMode = inTouchMode; } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index df124ac5be28..e5cbe96b9173 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { mIsRound = src.mIsRound; mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar; mDisplayCutout = src.mDisplayCutout; - mCutoutConsumed = src.mCutoutConsumed; + mDisplayCutoutConsumed = src.mDisplayCutoutConsumed; } /** @hide */ @@ -269,15 +270,16 @@ public final class WindowInsets { */ 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 @@ public final class WindowInsets { * 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 final class WindowInsets { */ public boolean isConsumed() { return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed - && mCutoutConsumed; + && mDisplayCutoutConsumed; } /** @@ -530,7 +531,7 @@ public final class WindowInsets { 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 012e86406579..cbe012af0b21 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1286,7 +1286,6 @@ public interface WindowManager extends ViewManager { * 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 @@ public interface WindowManager extends ViewManager { * 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 @@ public interface WindowManager extends ViewManager { 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 @@ public interface WindowManager extends ViewManager { 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 @@ public interface WindowManager extends ViewManager { 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 @@ public interface WindowManager extends ViewManager { 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 9c2f6bb8cc33..28ef6978ac93 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -2325,7 +2325,7 @@ public class AccessibilityNodeInfo implements Parcelable { /** * 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 f11767debbc9..ef1a3f3bcc8f 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -87,6 +87,7 @@ public final class AccessibilityWindowInfo implements Parcelable { 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 @@ public final class AccessibilityWindowInfo implements Parcelable { 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 @@ public final class AccessibilityWindowInfo implements Parcelable { * * @hide */ - public void setAnchorId(int anchorId) { + public void setAnchorId(long anchorId) { mAnchorId = anchorId; } @@ -212,7 +212,8 @@ public final class AccessibilityWindowInfo implements Parcelable { * @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 @@ public final class AccessibilityWindowInfo implements Parcelable { /** @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 @@ public final class AccessibilityWindowInfo implements Parcelable { * @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 @@ public final class AccessibilityWindowInfo implements Parcelable { 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 @@ public final class AccessibilityWindowInfo implements Parcelable { 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 @@ public final class AccessibilityWindowInfo implements Parcelable { 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 @@ public final class AccessibilityWindowInfo implements Parcelable { 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 @@ public final class AccessibilityWindowInfo implements Parcelable { 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 @@ public final class AccessibilityWindowInfo implements Parcelable { 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 fdc9f92347db..ed6043038600 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 @@ public interface TextClassifier { }) @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 @@ public interface TextClassifier { * 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 @@ public interface TextClassifier { } /** + * 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 @@ public interface TextClassifier { 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 d7aaee73f976..aea3cb0603f9 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -42,6 +42,9 @@ import java.io.File; 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 @@ final class TextClassifierImpl implements TextClassifier { 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 @@ final class TextClassifierImpl implements TextClassifier { @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 @@ final class TextClassifierImpl implements TextClassifier { } @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 4fe5662cca22..6c587cf9d475 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -161,11 +161,12 @@ public final class TextLinks { 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 @@ public final class TextLinks { } /** + * 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 final class TextLinks { 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 e9859239fdd6..de5a822da46b 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2081,6 +2081,48 @@ public class WebView extends AbsoluteLayout } /** + * 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 73399313cbb5..f0670914558e 100644 --- a/core/java/android/webkit/WebViewDelegate.java +++ b/core/java/android/webkit/WebViewDelegate.java @@ -218,4 +218,11 @@ public final class WebViewDelegate { 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 9db0e8d9a2fe..e3efad024341 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -33,6 +33,7 @@ import android.util.AndroidRuntimeException; import android.util.ArraySet; import android.util.Log; +import java.io.File; import java.lang.reflect.Method; /** @@ -63,6 +64,8 @@ public final class WebViewFactory { 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 @@ public final class WebViewFactory { /** * @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 @@ public final class WebViewFactory { 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 000000000000..8f0d02f5d788 --- /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 9ac443b43701..1e17f34af2a6 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4867,6 +4867,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * 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/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java index 8016a6559bcc..2eadaf3a06b0 100644 --- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java +++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java @@ -111,7 +111,7 @@ public class UnlaunchableAppActivity extends Activity @Override public void onClick(DialogInterface dialog, int which) { if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE && which == DialogInterface.BUTTON_POSITIVE) { - UserManager.get(this).trySetQuietModeDisabled(mUserId, mTarget); + UserManager.get(this).trySetQuietModeEnabled(false, UserHandle.of(mUserId), mTarget); } } diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index 6fb02b162309..efc9c02f1da9 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 @@ public final class ProcessState { 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 2ce7936d157b..96ba2b0c5fd9 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 final class ProcessStats implements Parcelable { 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 final class ProcessStats implements Parcelable { 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 final class ProcessStats implements Parcelable { 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 d49d572310d7..a075705c2e40 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -236,6 +236,7 @@ public abstract class FileSystemProvider extends DocumentsProvider { 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 bef247f52da4..6510a70c0cf6 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -120,7 +120,7 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { } 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 000000000000..648989455b2b --- /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 738ecc0bdaa0..1c09bd6a2a66 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.FileDescriptor; 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 final class TransferPipe implements Runnable, Closeable { } 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 @@ public final class TransferPipe implements Runnable, Closeable { } } + 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 @@ public final class TransferPipe implements Runnable, Closeable { 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 f983de17e0b1..7985e5743023 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -290,11 +290,11 @@ public class CollectionUtils { 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 eb92c1c0dfb2..82ac2412e4b2 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 @@ public class FunctionalUtils { 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 @@ public class FunctionalUtils { } /** - * 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 + @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 - public interface ThrowingConsumer<T> { - void accept(T t) throws Exception; + @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 8f80bfe3fb50..0d2b29b69586 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 @@ public class ListMenuItemView extends LinearLayout private TextView mShortcutView; private ImageView mSubMenuArrowView; private ImageView mGroupDivider; + private LinearLayout mContent; private Drawable mBackground; private int mTextAppearance; @@ -114,6 +115,8 @@ public class ListMenuItemView extends LinearLayout 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 @@ public class ListMenuItemView extends LinearLayout 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 @@ public class ListMenuItemView extends LinearLayout 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 @@ public class ListMenuItemView extends LinearLayout 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 @@ public class ListMenuItemView extends LinearLayout 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 ee16ab609a2a..164a74509ed7 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 @@ interface ILockSettings { 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 8df0fb891f1c..297bae25c3f3 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -123,6 +123,7 @@ cc_library_shared { "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 @@ cc_library_shared { "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 9e907bdf458f..b5d18683403f 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -57,10 +57,12 @@ extern int register_android_os_Process(JNIEnv* env); 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 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) 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 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) * 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 @@ static const RegJNIRec gRegJNI[] = { 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 @@ static const RegJNIRec gRegJNI[] = { 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 2e8c27ab7217..79aa5acac4ee 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -47,9 +47,6 @@ jfieldID gOptions_bitmapFieldID; jfieldID gBitmap_ninePatchInsetsFieldID; -jclass gInsetStruct_class; -jmethodID gInsetStruct_constructorMethodID; - jclass gBitmapConfig_class; jmethodID gBitmapConfig_nativeToConfigMethodID; @@ -99,43 +96,6 @@ jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format) { 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 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, 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 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, 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 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, 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 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, 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 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) { 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 000000000000..115edd4fe4f4 --- /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 000000000000..367a48fad9b9 --- /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 000000000000..bacab2a304cc --- /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 564afeb39490..261910727db9 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 @@ public: }; +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 @@ static const JNINativeMethod gNinePatchMethods[] = { }; 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 1ea5650bc20b..9171fc687276 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 @@ bool NinePatchPeeker::readChunk(const char tag[], const void* data, size_t lengt } 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 126eab25fc30..e4e58dda4783 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 @@ public: 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 1522c20dbe2f..1676d4b91e3d 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 @@ static int checkFormat(SkColorType colorType, int format, int type) 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 @@ static int getInternalFormat(SkColorType colorType) 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 @@ static int getType(SkColorType colorType) 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 000000000000..5629c2d291f7 --- /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 a95fa57ac2b2..522ff24c1e60 100644 --- a/core/proto/android/os/cpuinfo.proto +++ b/core/proto/android/os/cpuinfo.proto @@ -18,8 +18,6 @@ syntax = "proto2"; 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 @@ package android.os; 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 @@ message CpuInfo { 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 @@ message CpuInfo { 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 @@ message CpuInfo { // 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 @@ message CpuInfo { 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 @@ message CpuInfo { // 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 4015a7e7cea9..ac8f26d94ca2 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/os/system_properties.proto"; 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 @@ message IncidentProto { 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 eaad37af2ebc..7e5be9d39f69 100644 --- a/core/proto/android/os/kernelwake.proto +++ b/core/proto/android/os/kernelwake.proto @@ -18,8 +18,6 @@ syntax = "proto2"; 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 @@ message KernelWakeSources { // 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 b86ee01f31b7..f82ea7672879 100644 --- a/core/proto/android/os/pagetypeinfo.proto +++ b/core/proto/android/os/pagetypeinfo.proto @@ -18,8 +18,6 @@ syntax = "proto2"; 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 @@ message MigrateTypeProto { // 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 f684c84c12e9..204a5af70947 100644 --- a/core/proto/android/os/procrank.proto +++ b/core/proto/android/os/procrank.proto @@ -19,7 +19,6 @@ option java_multiple_files = true; 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 @@ message Procrank { // 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 76a108b1ac08..a51253f550c2 100644 --- a/core/proto/android/os/system_properties.proto +++ b/core/proto/android/os/system_properties.proto @@ -19,14 +19,12 @@ syntax = "proto2"; 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 3af6f51fffc9..d3ca496995a9 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -21,6 +21,7 @@ package com.android.server.am.proto; 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 @@ message MemInfoProto { 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 @@ message MemInfoProto { 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 @@ message MemInfoProto { } 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 801411dbed13..f42206562fc4 100644 --- a/core/proto/android/service/graphicsstats.proto +++ b/core/proto/android/service/graphicsstats.proto @@ -19,7 +19,6 @@ package android.service; 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 15e439ed3586..e303927e78c6 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" @@ -3627,6 +3632,11 @@ <permission android:name="android.permission.READ_RUNTIME_PROFILES" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to turn on / off quiet mode. + @hide <p>Not for use by third-party applications. --> + <permission android:name="android.permission.MODIFY_QUIET_MODE" + android:protectionLevel="signature|privileged" /> + <application android:process="system" android:persistent="true" android:hasCode="false" 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 000000000000..c678ad4ddc47 --- /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/interpolator/aggressive_ease.xml b/core/res/res/interpolator/aggressive_ease.xml new file mode 100644 index 000000000000..620424fb4112 --- /dev/null +++ b/core/res/res/interpolator/aggressive_ease.xml @@ -0,0 +1,22 @@ +<?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 + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.2" + android:controlY1="0" + android:controlX2="0" + android:controlY2="1"/>
\ No newline at end of file diff --git a/core/res/res/interpolator/emphasized_deceleration.xml b/core/res/res/interpolator/emphasized_deceleration.xml new file mode 100644 index 000000000000..60c315c0c4d4 --- /dev/null +++ b/core/res/res/interpolator/emphasized_deceleration.xml @@ -0,0 +1,22 @@ +<?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 + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.1" + android:controlY1="0.8" + android:controlX2="0.2" + android:controlY2="1"/>
\ No newline at end of file diff --git a/core/res/res/interpolator/exaggerated_ease.xml b/core/res/res/interpolator/exaggerated_ease.xml new file mode 100644 index 000000000000..4961c1c72984 --- /dev/null +++ b/core/res/res/interpolator/exaggerated_ease.xml @@ -0,0 +1,19 @@ +<?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 + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.08, 0.166666, 0.4 C 0.225, 0.94, 0.25, 1, 1, 1"/> diff --git a/core/res/res/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml index 865685ff26f5..435289de92de 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 3eecb36f0ea6..3b89f0d0cde7 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 c9611e49da62..2f69bd58b7b1 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 073fa1218039..fa3c9b1dab6d 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 b5aab394d323..b4e390473628 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 cff1a8500f21..eada63324240 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 b2778a1e179c..7be15f156c57 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 000000000000..e15b0b015f03 --- /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 000000000000..92ab682d47ee --- /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 000000000000..0c59edd12681 --- /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 4485561754eb..99db9e6f6a69 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 e580a59a96b0..cfa05dedc029 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 2595f948bf6c..fc958faf3927 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 9223c7686fde..f0a45a32c750 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 000000000000..faadc5009347 --- /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 000000000000..537a80ceadd4 --- /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 000000000000..e69bbbcd5eca --- /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 283701e4ef53..b5b6a00ba920 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 434b50b99800..dc2d6e451c85 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 c1526f3fef53..c517223fcea6 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 a96bcaf1aeec..fd103bc9f8f4 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 2155b34482c6..2771432dd56f 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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 000000000000..9044fccd88ee --- /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 000000000000..4296b0d37dd2 --- /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 000000000000..1a7ade9dd5f9 --- /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 51ffa6007ae0..64febf1f7ee4 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/strings.xml b/core/res/res/values/strings.xml index 5ccaf5cf2f43..5783435e13bd 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3014,6 +3014,13 @@ <!-- Notification action name for opening the wifi picker, showing the user all the nearby networks. --> <string name="wifi_available_action_all_networks">All Networks</string> + <!--Notification title for Wi-Fi Wake onboarding. This is displayed the first time a user disables Wi-Fi with the feature enabled. --> + <string name="wifi_wakeup_onboarding_title">Wi\u2011Fi will turn on automatically</string> + <!--Notification subtext for Wi-Fi Wake onboarding.--> + <string name="wifi_wakeup_onboarding_subtext">When you\'re near a high quality saved network</string> + <!--Notification action to disable Wi-Fi Wake during onboarding.--> + <string name="wifi_wakeup_onboarding_action_disable">Don\'t turn back on</string> + <!-- A notification is shown when a wifi captive portal network is detected. This is the notification's title. --> <string name="wifi_available_sign_in">Sign in to Wi-Fi network</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4b2424ffd213..7f714460992e 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" /> @@ -1905,6 +1906,9 @@ <java-symbol type="string" name="wifi_available_content_failed_to_connect" /> <java-symbol type="string" name="wifi_available_action_connect" /> <java-symbol type="string" name="wifi_available_action_all_networks" /> + <java-symbol type="string" name="wifi_wakeup_onboarding_title" /> + <java-symbol type="string" name="wifi_wakeup_onboarding_subtext" /> + <java-symbol type="string" name="wifi_wakeup_onboarding_action_disable" /> <java-symbol type="string" name="accessibility_binding_label" /> <java-symbol type="string" name="adb_active_notification_message" /> <java-symbol type="string" name="adb_active_notification_title" /> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 3f2a46a0e9ca..e0947723f502 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1051,7 +1051,7 @@ </intent-filter> </activity> - <activity android:name=".menus.MenuLayoutPortrait" android:label="MenuLayoutPortrait" + <activity android:name="android.view.menu.MenuLayoutPortrait" android:label="MenuLayoutPortrait" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> diff --git a/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java index 3c8185328582..e26bdf53b872 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 java.util.concurrent.TimeUnit; 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 38df78d3bad5..a9961e19e0c6 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 com.android.frameworks.coretests.R; 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 f60bf94f8c99..063bef7387c6 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.IPackageManager; 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 java.util.List; 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 ee3834cabbc2..9b59da48e760 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 c14dc90dbacd..718393410d3b 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -214,6 +214,20 @@ public class NotificationTest { 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 e9e8bfcb09c5..13e70ebdb2e3 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.Bundle; 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 3c309154eedc..8c1d79b5d89c 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.Activity; 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 0c204e098405..5b8e481cf573 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.content.res.AssetManager; 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.io.InputStreamReader; 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 3869cd29f69f..bc6fc15db163 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.mock; 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.List; 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 4b1f2dab61b9..e4de5263bb7c 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 @@ public class TransactionParcelTests { } @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 70a0877beb5f..e20645c7d3c8 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.assertFalse; 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 eecae46910fd..b69054cebbd2 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.assertFalse; 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 99abe243556c..dd462403ed82 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.assertFalse; 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 91f8ebc9ec1c..e4aac509c2ef 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 static org.mockito.hamcrest.MockitoHamcrest.argThat; 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 @@ import org.junit.Test; /** * 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 d92eece8afca..10d74f7d2945 100644 --- a/core/tests/coretests/src/android/content/RestrictionsManagerTest.java +++ b/core/tests/coretests/src/android/content/RestrictionsManagerTest.java @@ -17,6 +17,7 @@ package android.content; 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.HashSet; 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 948e7220978b..659f9ea77956 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 javax.crypto.spec.SecretKeySpec; 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 a9d19b4b295c..952bb5530c15 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 @@ package android.content.pm; 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.ArrayList; 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 271c6392bdf4..d3d1f22af3cb 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.FileUtils; 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 @@ import java.util.Set; /** * 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 89d599742e90..a3fa1a9c06cf 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 d963812db622..68942cbd54ad 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 @@ package android.content.pm; 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 @@ import android.test.AndroidTestCase; * 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 cb13eb7c846e..88d7a59a98e2 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 7550cb5211b9..b28a4b5ae628 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.Path.Direction; 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.io.FileOutputStream; 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 a2e9ae89e255..f30b1a29c781 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.Bundle; 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 @@ import java.util.concurrent.locks.ReentrantLock; * 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 ada59cd84931..3be776deb9f1 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 d10b3519bccf..784a12fa1f57 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 junit.framework.TestCase; 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 000000000000..7350db7a0811 --- /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 41f8e03f46ce..72f62f167989 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 aa9aed8e2fcc..ea954f65476d 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.platform.test.annotations.Presubmit; 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 @@ public class DynamicLayoutTest { } @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 000000000000..0f85e1f9c5d9 --- /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 f1a730a7a420..d0f2d46f9c2b 100644 --- a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java +++ b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java @@ -20,6 +20,7 @@ package android.text; 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 android.text.style.UnderlineSpan; 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 f4514b56ec8d..d817278330d4 100644 --- a/core/tests/coretests/src/android/text/StaticLayoutTest.java +++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java @@ -697,9 +697,13 @@ public class StaticLayoutTest { 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 2d9401686e81..9c544f47717c 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.Context; 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 org.junit.runner.RunWith; 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 ab4320c65eb2..7e629f944684 100644 --- a/core/tests/coretests/src/android/transition/TransitionTest.java +++ b/core/tests/coretests/src/android/transition/TransitionTest.java @@ -19,6 +19,7 @@ package android.transition; 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 com.android.frameworks.coretests.R; 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 32aa29fa3339..f0cc7f77fe63 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 @@ import java.util.ConcurrentModificationException; /** * 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 53368d40104e..3aee5839983e 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 a63c8a084964..5144c85e72a2 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.Arrays; 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 cb468bc68eeb..3f03db9dc1fb 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 @@ import java.util.Random; /** * 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 6dd787d2e038..0d8c679a312f 100644 --- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java +++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertTrue; 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 android.view.DisplayCutout.ParcelableWrapper; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; import java.util.Arrays; @RunWith(AndroidJUnit4.class) @@ -45,19 +45,14 @@ public class DisplayCutoutTest { /** 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 @@ public class DisplayCutoutTest { 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 @@ public class DisplayCutoutTest { 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 @@ public class DisplayCutoutTest { } 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 23d025164ed1..fba8eae6c3dd 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 org.junit.runner.RunWith; 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 44fcd13b5313..aa8f8d82e725 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.Context; 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 android.view.WindowManager; 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 2f32d13693c7..4de8155663f5 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.never; 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.Arrays; 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 c1b2309ae64a..318d122b3f2f 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 static org.mockito.MockitoAnnotations.initMocks; 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 @@ import java.util.List; /** * 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 79e6316ed614..b135025f6d21 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 @@ package android.view.accessibility; 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 org.junit.runner.RunWith; 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 e3c91e64f69c..9edbf3efeebb 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.ResolveInfo; 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 com.android.frameworks.coretests.R; 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 9347b27bbf5d..8ed0d86d850d 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.IconMenuView; 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 b053699673aa..ccf12643593b 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 9092c852db47..a8de374cf30b 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.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Collection; - @SmallTest @RunWith(AndroidJUnit4.class) public class TextClassificationManagerTest { @@ -166,20 +163,50 @@ public class TextClassificationManagerTest { } @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))); + } + + @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))); + } - 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_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)); + } - // TODO: Add more entity types when the model supports them. + @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 @@ public class TextClassificationManagerTest { 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 @@ public class TextClassificationManagerTest { }; } + 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 513e40f4bcc3..be85450be429 100644 --- a/core/tests/coretests/src/android/widget/DatePickerFocusTest.java +++ b/core/tests/coretests/src/android/widget/DatePickerFocusTest.java @@ -19,6 +19,7 @@ package android.widget; 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 @@ import com.android.frameworks.coretests.R; /** * 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 28a3b67f7b0d..2add22105681 100644 --- a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java +++ b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java @@ -22,6 +22,7 @@ import static java.util.function.Function.identity; 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.ArrayList; 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 b591e5f4f690..43986eed9b14 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 @@ package android.widget.focus; 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 @@ import static android.widget.focus.VerticalFocusSearchTest.NewFocusSearchAlg; * 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 f05d83abde5e..f01422ebfc52 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 @@ package android.widget.focus; 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 @@ import android.widget.LinearLayout; * 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 400fd7d9247a..9a8e63421d61 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 c1e8c981579c..5bfde78c424a 100644 --- a/core/tests/packagemanagertests/Android.mk +++ b/core/tests/packagemanagertests/Android.mk @@ -10,7 +10,8 @@ LOCAL_SRC_FILES := \ 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 000000000000..7b112259c9d7 --- /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 000000000000..a0e52814bdcd --- /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 000000000000..91664381efe0 --- /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 000000000000..dad98b8e4a35 --- /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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 8e7147cbfece..7bb28599c505 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -236,6 +236,7 @@ applications that come with the platform <permission name="android.permission.CHANGE_CONFIGURATION"/> <permission name="android.permission.DELETE_PACKAGES"/> <permission name="android.permission.FORCE_STOP_PACKAGES"/> + <permission name="android.permission.LOCAL_MAC_ADDRESS"/> <permission name="android.permission.MANAGE_DEVICE_ADMINS"/> <permission name="android.permission.MANAGE_FINGERPRINT"/> <permission name="android.permission.MANAGE_USB"/> diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java new file mode 100644 index 000000000000..60416a720231 --- /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 000000000000..c5a31e828c97 --- /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 eca52cc3e8b6..7c7417dfaaac 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -35,6 +35,7 @@ interface IKeyChainService { 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 3fe75cffaa71..5b95c81db24e 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -6880,6 +6880,9 @@ status_t ResTable::createIdmap(const ResTable& overlay, 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 @@ status_t ResTable::createIdmap(const ResTable& overlay, 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 @@ status_t ResTable::createIdmap(const ResTable& overlay, 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 @@ status_t ResTable::createIdmap(const ResTable& overlay, 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 20d017813cf7..8cf4de9167e5 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1339,9 +1339,13 @@ struct ResTable_typeSpec // 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 Binary files differindex 0c17328e86b2..18ef75e91ded 100644 --- a/libs/androidfw/tests/data/basic/basic.apk +++ b/libs/androidfw/tests/data/basic/basic.apk diff --git a/libs/androidfw/tests/data/basic/basic_de_fr.apk b/libs/androidfw/tests/data/basic/basic_de_fr.apk Binary files differindex e45258c6a005..767dff6fcfa5 100644 --- a/libs/androidfw/tests/data/basic/basic_de_fr.apk +++ b/libs/androidfw/tests/data/basic/basic_de_fr.apk diff --git a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk Binary files differindex 4ae1a7c87a70..58953f56d736 100644 --- a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk +++ b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk diff --git a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk Binary files differindex a240d4c06c1d..103f6565bb06 100644 --- a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk +++ b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk diff --git a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk Binary files differindex fd3d9b233084..61369d506786 100644 --- a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk +++ b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build index d619800d6fc5..5682ed4d3041 100755 --- a/libs/androidfw/tests/data/basic/build +++ b/libs/androidfw/tests/data/basic/build @@ -19,11 +19,15 @@ set -e 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 638c9832ab4c..6c474596b5cd 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 112f373ead30..716b1bd9db64 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 Binary files differindex 40bf17c5951a..33f961117c44 100644 --- a/libs/androidfw/tests/data/overlay/overlay.apk +++ b/libs/androidfw/tests/data/overlay/overlay.apk diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp index 8c0ca5c12d1e..0a9a74eccfc9 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 @@ int main(int argc, char* argv[]) { 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 8aa4b107c25e..5f3e4071afef 100644 --- a/libs/incident/Android.mk +++ b/libs/incident/Android.mk @@ -31,7 +31,6 @@ LOCAL_C_INCLUDES := \ 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 5f8877afa6c3..a61e6cebc01b 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 d11df01988a7..915f1b674f3c 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 2e6f89258124..8f2b51af5505 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 dfd9754f0991..d346d9f919f7 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 1cf6a2ee065f..f707730c69ef 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 ebee5c1425f4..a4d07ac0d898 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 141bb9528198..f7cce96abf71 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 71ce1ccb02d3..ab95bd56c009 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 e82046966c62..d6f7b2bcdc3a 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 57d1657bd5e4..22e9d35d87ba 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 f5cf5f610ec5..47d840881a3a 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/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index fa2499f6c144..5c73d5485e2a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -21,6 +21,9 @@ import android.app.AppGlobals; import android.app.Application; import android.app.usage.StorageStats; import android.app.usage.StorageStatsManager; +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.LifecycleObserver; +import android.arch.lifecycle.OnLifecycleEvent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -52,11 +55,6 @@ import android.util.SparseArray; import com.android.internal.R; import com.android.internal.util.ArrayUtils; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnDestroy; -import com.android.settingslib.core.lifecycle.events.OnPause; -import com.android.settingslib.core.lifecycle.events.OnResume; import java.io.File; import java.io.IOException; @@ -595,7 +593,7 @@ public class ApplicationsState { .replaceAll("").toLowerCase(); } - public class Session implements LifecycleObserver, OnPause, OnResume, OnDestroy { + public class Session implements LifecycleObserver { final Callbacks mCallbacks; boolean mResumed; @@ -621,6 +619,7 @@ public class ApplicationsState { } } + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void onResume() { if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock..."); synchronized (mEntriesMap) { @@ -633,6 +632,7 @@ public class ApplicationsState { if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock"); } + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) public void onPause() { if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock..."); synchronized (mEntriesMap) { @@ -752,6 +752,7 @@ public class ApplicationsState { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) public void onDestroy() { if (!mHasLifecycle) { // TODO: Legacy, remove this later once all usages are switched to Lifecycle diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java index a8262c8cc4c8..974b2a4389e2 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 @@ public class ZoneGetter { * @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/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java new file mode 100644 index 000000000000..2b6d09f9b72e --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java @@ -0,0 +1,108 @@ +/* + * 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.settingslib.fuelgauge; + +import android.os.IDeviceIdleController; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.support.annotation.VisibleForTesting; +import android.util.ArraySet; +import android.util.Log; + +/** + * Handles getting/changing the whitelist for the exceptions to battery saving features. + */ +public class PowerWhitelistBackend { + + private static final String TAG = "PowerWhitelistBackend"; + + private static final String DEVICE_IDLE_SERVICE = "deviceidle"; + + private static PowerWhitelistBackend sInstance; + + private final IDeviceIdleController mDeviceIdleService; + private final ArraySet<String> mWhitelistedApps = new ArraySet<>(); + private final ArraySet<String> mSysWhitelistedApps = new ArraySet<>(); + + public PowerWhitelistBackend() { + mDeviceIdleService = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(DEVICE_IDLE_SERVICE)); + refreshList(); + } + + @VisibleForTesting + PowerWhitelistBackend(IDeviceIdleController deviceIdleService) { + mDeviceIdleService = deviceIdleService; + refreshList(); + } + + public int getWhitelistSize() { + return mWhitelistedApps.size(); + } + + public boolean isSysWhitelisted(String pkg) { + return mSysWhitelistedApps.contains(pkg); + } + + public boolean isWhitelisted(String pkg) { + return mWhitelistedApps.contains(pkg); + } + + public void addApp(String pkg) { + try { + mDeviceIdleService.addPowerSaveWhitelistApp(pkg); + mWhitelistedApps.add(pkg); + } catch (RemoteException e) { + Log.w(TAG, "Unable to reach IDeviceIdleController", e); + } + } + + public void removeApp(String pkg) { + try { + mDeviceIdleService.removePowerSaveWhitelistApp(pkg); + mWhitelistedApps.remove(pkg); + } catch (RemoteException e) { + Log.w(TAG, "Unable to reach IDeviceIdleController", e); + } + } + + @VisibleForTesting + public void refreshList() { + mSysWhitelistedApps.clear(); + mWhitelistedApps.clear(); + try { + String[] whitelistedApps = mDeviceIdleService.getFullPowerWhitelist(); + for (String app : whitelistedApps) { + mWhitelistedApps.add(app); + } + String[] sysWhitelistedApps = mDeviceIdleService.getSystemPowerWhitelist(); + for (String app : sysWhitelistedApps) { + mSysWhitelistedApps.add(app); + } + } catch (RemoteException e) { + Log.w(TAG, "Unable to reach IDeviceIdleController", e); + } + } + + public static PowerWhitelistBackend getInstance() { + if (sInstance == null) { + sInstance = new PowerWhitelistBackend(); + } + return sInstance; + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java index 3f826cc00b8b..6025d68a6d0e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java @@ -16,21 +16,21 @@ package com.android.settingslib.location; -import android.app.AppGlobals; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.drawable.Drawable; import android.os.Process; -import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.support.annotation.VisibleForTesting; import android.util.IconDrawableFactory; import android.util.Log; - import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; /** @@ -38,11 +38,13 @@ import java.util.List; */ public class RecentLocationApps { private static final String TAG = RecentLocationApps.class.getSimpleName(); - private static final String ANDROID_SYSTEM_PACKAGE_NAME = "android"; + @VisibleForTesting + static final String ANDROID_SYSTEM_PACKAGE_NAME = "android"; private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000; - private static final int[] LOCATION_OPS = new int[] { + @VisibleForTesting + static final int[] LOCATION_OPS = new int[] { AppOpsManager.OP_MONITOR_LOCATION, AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, }; @@ -59,6 +61,7 @@ public class RecentLocationApps { /** * Fills a list of applications which queried location recently within specified time. + * Apps are sorted by recency. Apps with more recent location requests are in the front. */ public List<Request> getAppList() { // Retrieve a location usage list from AppOps @@ -91,7 +94,18 @@ public class RecentLocationApps { requests.add(request); } } + return requests; + } + public List<Request> getAppListSorted() { + List<Request> requests = getAppList(); + // Sort the list of Requests by recency. Most recent request first. + Collections.sort(requests, Collections.reverseOrder(new Comparator<Request>() { + @Override + public int compare(Request request1, Request request2) { + return Long.compare(request1.requestFinishTime, request2.requestFinishTime); + } + })); return requests; } @@ -108,10 +122,12 @@ public class RecentLocationApps { List<AppOpsManager.OpEntry> entries = ops.getOps(); boolean highBattery = false; boolean normalBattery = false; + long locationRequestFinishTime = 0L; // Earliest time for a location request to end and still be shown in list. long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS; for (AppOpsManager.OpEntry entry : entries) { if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) { + locationRequestFinishTime = entry.getTime() + entry.getDuration(); switch (entry.getOp()) { case AppOpsManager.OP_MONITOR_LOCATION: normalBattery = true; @@ -133,15 +149,13 @@ public class RecentLocationApps { } // The package is fresh enough, continue. - int uid = ops.getUid(); int userId = UserHandle.getUserId(uid); Request request = null; try { - IPackageManager ipm = AppGlobals.getPackageManager(); - ApplicationInfo appInfo = - ipm.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId); + ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser( + packageName, PackageManager.GET_META_DATA, userId); if (appInfo == null) { Log.w(TAG, "Null application info retrieved for package " + packageName + ", userId " + userId); @@ -158,12 +172,10 @@ public class RecentLocationApps { badgedAppLabel = null; } request = new Request(packageName, userHandle, icon, appLabel, highBattery, - badgedAppLabel); - } catch (RemoteException e) { - Log.w(TAG, "Error while retrieving application info for package " + packageName - + ", userId " + userId, e); + badgedAppLabel, locationRequestFinishTime); + } catch (NameNotFoundException e) { + Log.w(TAG, "package name not found for " + packageName + ", userId " + userId); } - return request; } @@ -174,15 +186,18 @@ public class RecentLocationApps { public final CharSequence label; public final boolean isHighBattery; public final CharSequence contentDescription; + public final long requestFinishTime; private Request(String packageName, UserHandle userHandle, Drawable icon, - CharSequence label, boolean isHighBattery, CharSequence contentDescription) { + CharSequence label, boolean isHighBattery, CharSequence contentDescription, + long requestFinishTime) { this.packageName = packageName; this.userHandle = userHandle; this.icon = icon; this.label = label; this.isHighBattery = isHighBattery; this.contentDescription = contentDescription; + this.requestFinishTime = requestFinishTime; } } } diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk index 273802754bbb..02a49738e6a1 100644 --- a/packages/SettingsLib/tests/robotests/Android.mk +++ b/packages/SettingsLib/tests/robotests/Android.mk @@ -49,7 +49,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ LOCAL_JAVA_LIBRARIES := \ junit \ - platform-robolectric-3.4.2-prebuilt + platform-robolectric-3.5.1-prebuilt LOCAL_INSTRUMENTATION_FOR := SettingsLibShell LOCAL_MODULE := SettingsLibRoboTests @@ -74,4 +74,4 @@ LOCAL_TEST_PACKAGE := SettingsLibShell LOCAL_ROBOTEST_TIMEOUT := 36000 -include prebuilts/misc/common/robolectric/3.4.2/run_robotests.mk +include prebuilts/misc/common/robolectric/3.5.1/run_robotests.mk diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java index 698e44247e73..df850bee13df 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java @@ -38,32 +38,25 @@ public class SettingsLibRobolectricTestRunner extends RobolectricTestRunner { final String resDir = appRoot + "/tests/robotests/res"; final String assetsDir = appRoot + config.assetDir(); - final AndroidManifest manifest = new AndroidManifest(Fs.fileFromPath(manifestPath), - Fs.fileFromPath(resDir), Fs.fileFromPath(assetsDir)) { + return new AndroidManifest(Fs.fileFromPath(manifestPath), Fs.fileFromPath(resDir), + Fs.fileFromPath(assetsDir), "com.android.settingslib") { @Override public List<ResourcePath> getIncludedResourcePaths() { List<ResourcePath> paths = super.getIncludedResourcePaths(); - SettingsLibRobolectricTestRunner.getIncludedResourcePaths(getPackageName(), paths); + paths.add(new ResourcePath( + null, + Fs.fileFromPath("./frameworks/base/packages/SettingsLib/res"), + null)); + paths.add(new ResourcePath( + null, + Fs.fileFromPath("./frameworks/base/core/res/res"), + null)); + paths.add(new ResourcePath( + null, + Fs.fileFromPath("./frameworks/support/v7/appcompat/res"), + null)); return paths; } }; - manifest.setPackageName("com.android.settingslib"); - return manifest; } - - static void getIncludedResourcePaths(String packageName, List<ResourcePath> paths) { - paths.add(new ResourcePath( - null, - Fs.fileFromPath("./frameworks/base/packages/SettingsLib/res"), - null)); - paths.add(new ResourcePath( - null, - Fs.fileFromPath("./frameworks/base/core/res/res"), - null)); - paths.add(new ResourcePath( - null, - Fs.fileFromPath("./frameworks/support/v7/appcompat/res"), - null)); - } - } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java new file mode 100644 index 000000000000..fc0019d09c41 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java @@ -0,0 +1,97 @@ +/* + * 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.settingslib.fuelgauge; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.os.IDeviceIdleController; + +import com.android.settingslib.SettingsLibRobolectricTestRunner; +import com.android.settingslib.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class PowerWhitelistBackendTest { + + private static final String PACKAGE_ONE = "com.example.packageone"; + private static final String PACKAGE_TWO = "com.example.packagetwo"; + + private PowerWhitelistBackend mPowerWhitelistBackend; + @Mock + private IDeviceIdleController mDeviceIdleService; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + doReturn(new String[] {}).when(mDeviceIdleService).getFullPowerWhitelist(); + doReturn(new String[] {}).when(mDeviceIdleService).getSystemPowerWhitelist(); + doNothing().when(mDeviceIdleService).addPowerSaveWhitelistApp(anyString()); + doNothing().when(mDeviceIdleService).removePowerSaveWhitelistApp(anyString()); + mPowerWhitelistBackend = new PowerWhitelistBackend(mDeviceIdleService); + } + + @Test + public void testIsWhitelisted() throws Exception { + doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getFullPowerWhitelist(); + mPowerWhitelistBackend.refreshList(); + + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue(); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse(); + + mPowerWhitelistBackend.addApp(PACKAGE_TWO); + + verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue(); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isTrue(); + + mPowerWhitelistBackend.removeApp(PACKAGE_TWO); + + verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue(); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse(); + + mPowerWhitelistBackend.removeApp(PACKAGE_ONE); + + verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isFalse(); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse(); + } + + @Test + public void testIsSystemWhitelisted() throws Exception { + doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getSystemPowerWhitelist(); + mPowerWhitelistBackend.refreshList(); + + assertThat(mPowerWhitelistBackend.isSysWhitelisted(PACKAGE_ONE)).isTrue(); + assertThat(mPowerWhitelistBackend.isSysWhitelisted(PACKAGE_TWO)).isFalse(); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isFalse(); + + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java new file mode 100644 index 000000000000..226166b3e632 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java @@ -0,0 +1,162 @@ +package com.android.settingslib.location; + +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.when; +import static com.google.common.truth.Truth.assertThat; + +import android.app.AppOpsManager; +import android.app.AppOpsManager.OpEntry; +import android.app.AppOpsManager.PackageOps; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import com.android.settingslib.SettingsLibRobolectricTestRunner; +import com.android.settingslib.TestConfig; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config( + manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION) +public class RecentLocationAppsTest { + + private static final int TEST_UID = 1234; + private static final long NOW = System.currentTimeMillis(); + // App running duration in milliseconds + private static final int DURATION = 10; + private static final long ONE_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(1); + private static final long FOURTEEN_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(14); + private static final long TWENTY_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(20); + private static final String[] TEST_PACKAGE_NAMES = + {"package_1MinAgo", "package_14MinAgo", "package_20MinAgo"}; + + @Mock + private Context mContext; + @Mock + private PackageManager mPackageManager; + @Mock + private AppOpsManager mAppOpsManager; + @Mock + private Resources mResources; + @Mock + private UserManager mUserManager; + private int mTestUserId; + private RecentLocationApps mRecentLocationApps; + + + + @Before + public void setUp() throws NameNotFoundException { + MockitoAnnotations.initMocks(this); + + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager); + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + when(mPackageManager.getApplicationLabel(isA(ApplicationInfo.class))) + .thenReturn("testApplicationLabel"); + when(mPackageManager.getUserBadgedLabel(isA(CharSequence.class), isA(UserHandle.class))) + .thenReturn("testUserBadgedLabel"); + mTestUserId = UserHandle.getUserId(TEST_UID); + when(mUserManager.getUserProfiles()) + .thenReturn(Collections.singletonList(new UserHandle(mTestUserId))); + + long[] testRequestTime = {ONE_MIN_AGO, FOURTEEN_MIN_AGO, TWENTY_MIN_AGO}; + List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime); + when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps); + mockTestApplicationInfos(mTestUserId, TEST_PACKAGE_NAMES); + + mRecentLocationApps = new RecentLocationApps(mContext); + } + + @Test + public void testGetAppList_shouldFilterRecentApps() { + List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList(); + // Only two of the apps have requested location within 15 min. + assertThat(requests).hasSize(2); + // Make sure apps are ordered by recency + assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]); + assertThat(requests.get(0).requestFinishTime).isEqualTo(ONE_MIN_AGO + DURATION); + assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]); + assertThat(requests.get(1).requestFinishTime).isEqualTo(FOURTEEN_MIN_AGO + DURATION); + } + + @Test + public void testGetAppList_shouldNotShowAndroidOS() throws NameNotFoundException { + // Add android OS to the list of apps. + PackageOps androidSystemPackageOps = + createPackageOps( + RecentLocationApps.ANDROID_SYSTEM_PACKAGE_NAME, + Process.SYSTEM_UID, + AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, + ONE_MIN_AGO, + DURATION); + long[] testRequestTime = + {ONE_MIN_AGO, FOURTEEN_MIN_AGO, TWENTY_MIN_AGO, ONE_MIN_AGO}; + List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime); + appOps.add(androidSystemPackageOps); + when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps); + mockTestApplicationInfos( + Process.SYSTEM_UID, RecentLocationApps.ANDROID_SYSTEM_PACKAGE_NAME); + + List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList(); + // Android OS shouldn't show up in the list of apps. + assertThat(requests).hasSize(2); + // Make sure apps are ordered by recency + assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]); + assertThat(requests.get(0).requestFinishTime).isEqualTo(ONE_MIN_AGO + DURATION); + assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]); + assertThat(requests.get(1).requestFinishTime).isEqualTo(FOURTEEN_MIN_AGO + DURATION); + } + + private void mockTestApplicationInfos(int userId, String... packageNameList) + throws NameNotFoundException { + for (String packageName : packageNameList) { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.packageName = packageName; + when(mPackageManager.getApplicationInfoAsUser( + packageName, PackageManager.GET_META_DATA, userId)).thenReturn(appInfo); + } + } + + private List<PackageOps> createTestPackageOpsList(String[] packageNameList, long[] time) { + List<PackageOps> packageOpsList = new ArrayList<>(); + for (int i = 0; i < packageNameList.length ; i++) { + PackageOps packageOps = createPackageOps( + packageNameList[i], + TEST_UID, + AppOpsManager.OP_MONITOR_LOCATION, + time[i], + DURATION); + packageOpsList.add(packageOps); + } + return packageOpsList; + } + + private PackageOps createPackageOps( + String packageName, int uid, int op, long time, int duration) { + return new PackageOps( + packageName, + uid, + Collections.singletonList(createOpEntryWithTime(op, time, duration))); + } + + private OpEntry createOpEntryWithTime(int op, long time, int duration) { + return new OpEntry(op, AppOpsManager.MODE_ALLOWED, time, 0L, duration, 0, ""); + } +} diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml index 5df39895f501..791f5e9626fb 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 a6ee9a65f424..c62bab861686 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 2ee30e9998f1..2c291124aa93 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 054ce7c01386..a4f5b7dcaf6a 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 7f0e9578e816..d62537dcd65c 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 dc0e53bae184..d5d27cacf525 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 727ea5beef46..2ce57a09875c 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 ddc5928347cc..bdee4a3a0b66 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 5440a87ff22f..cd99c9279890 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 ee11cffd09d2..204bb8b0b737 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 000000000000..b86dfea07682 --- /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 000000000000..1ea293c0b690 --- /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 000000000000..d6867d7265b0 --- /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 000000000000..cc2ae910ae88 --- /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 22c3bcf6b9d7..3d0ab3599bf1 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 b9a228f7d538..15a7d4c5e339 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 961ac4514477..ee9acae25e5e 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 8437705e3cd2..1d8cb4e0f767 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 ae3854fb95af..fd42eb5491dd 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 3c9f83afc828..e82dbff0f1f5 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 1cd4c910c54d..ccdc44bd8a40 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 c82352553f95..96028f70375f 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) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- 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> <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 e87bb45247f5..dacf9d753dc1 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 e0e14a16ce3b..4166697b6c98 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 fb886fc6dd64..52446f80d0a5 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 feb7064be29f..83a8e3374225 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 1fe90101e41f..9a618283e914 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 4af3f5912d36..d808119eb494 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 27e4920b341e..3a5af25136ac 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 5d4181e124fe..87348de89560 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 9360c1dfa614..bb77daa072da 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 864f8d0bb2f0..3bebe4b538df 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 5c4394b86726..a2dde8584838 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 a3d580be0ad6..9fbcdeceb000 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 83f789693518..418f31e1152c 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) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- 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> <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 e7fc696dc89e..1acd6e7c46df 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 88f50973cf97..7ba9111398ba 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 e81df8f54286..8aa2c71cb03d 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 2994b9b05e7b..2841074cf0a2 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 1efc249e6337..66530bdbdc31 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 29271165a427..cbbffa93bda7 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 38622c95e33b..f840225c5a27 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 3f36c72feef4..a9e6ffe094d4 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 1d5d7d4ce9af..5819f2a9ddfc 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 b2e9d8f1730d..5e7e1e23f73f 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) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- 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> <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 83018be6883d..d86c32f6a8da 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 4aaae3c6e311..e10e2b0f08f0 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 bc1e5b90957f..2587a209bb79 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 53ddcb3ecc30..1c97452af21c 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 b1c3db5b7841..c14eb3f6a722 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 3986e168ec59..e30085c7c70b 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 3dfde1302b2a..385331eaa958 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) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- 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> <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 9f09dadc141c..480956c9b99b 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 5b0984d68cbf..8a0d6617f535 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 2907761fc749..e074875a632c 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 51b3b0d6ddf3..661df03a5db3 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 0a63ce2177b7..dce9cc45bdbf 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 c6475dcc361b..43503d6324a5 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) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- 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> <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 bc2a9545e526..e567e6ba2a08 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 7f2c6435612f..681d3e4751ad 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 21084556050a..2a0744edd18b 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 151c2b4e78b7..694695c9f6fe 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 ee628e121636..10d0104c232f 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 7641e6c6d29f..665177fba318 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 b5fab0d76028..cef0f6936ec9 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 cc6f542a0b2c..c8f8db40d9e7 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 b83f10f24819..8fa370d9ea87 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 9d532e27e67a..6568381342c3 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) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- 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> <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 f1c66f5c942d..fa7cb09b5519 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) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- 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> <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 ea61018db6c2..d43efdde7a43 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 cdf28d6e5348..4aa9ca4bc408 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 5dc81a1d7eaf..d028fdd6ac08 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 64980887907f..73fd588616e9 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) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- 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> <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 273079d7dcc2..55eb4fb51ee1 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 df062984e2d3..551decf2dcb6 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 f7999a6d9789..2b64d8685292 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 188ad7cae870..382425d1a083 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 75f8015fda5a..ef9ea43065be 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 78e621e4b559..fd205dd55662 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 d58b69deb2df..2058f15bdcc6 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.Log; 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } - 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 edd1748c7380..6aa465ce9f8c 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.Paint; 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.ViewGroup.LayoutParams; 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 @@ public class EmulatedDisplayCutout extends SystemUI { 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 @@ public class EmulatedDisplayCutout extends SystemUI { }; 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 @@ public class EmulatedDisplayCutout extends SystemUI { @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 e80d6d34b1dc..3177c03d1afa 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 class SystemUIFactory { 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 cc2244a4e934..8515bf2e2968 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 @@ public class AlwaysOnDisplayPolicy { 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 class AlwaysOnDisplayPolicy { */ 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 @@ public class AlwaysOnDisplayPolicy { 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 a409fcb4504a..0f0402d0b3d3 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 @@ public class DozeFactory { 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 6650cc634700..34d392861d7c 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 android.util.Log; 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 000000000000..ee41001d5537 --- /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/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 75321fd7e68f..1cbb44025906 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -98,7 +98,7 @@ public class NotificationShelf extends ActivatableNotificationView implements setClipToActualHeight(false); setClipChildren(false); setClipToPadding(false); - mShelfIcons.setShowAllIcons(false); + mShelfIcons.setIsStaticLayout(false); mViewInvertHelper = new ViewInvertHelper(mShelfIcons, NotificationPanelView.DOZE_ANIMATION_DURATION); mShelfState = new ShelfState(); @@ -681,7 +681,8 @@ public class NotificationShelf extends ActivatableNotificationView implements if (isLayoutRtl()) { start = getWidth() - start - mCollapsedIcons.getWidth(); } - int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(), + int width = (int) NotificationUtils.interpolate( + start + mCollapsedIcons.getFinalTranslationX(), mShelfIcons.getWidth(), openedAmount); mShelfIcons.setActualLayoutWidth(width); @@ -691,6 +692,9 @@ public class NotificationShelf extends ActivatableNotificationView implements // we have to ensure that adding the low priority notification won't lead to an // overflow collapsedPadding -= (1.0f + OVERFLOW_EARLY_AMOUNT) * mCollapsedIcons.getIconSize(); + } else { + // Partial overflow padding will fill enough space to add extra dots + collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding(); } float padding = NotificationUtils.interpolate(collapsedPadding, mShelfIcons.getPaddingEnd(), @@ -700,7 +704,6 @@ public class NotificationShelf extends ActivatableNotificationView implements mShelfIcons.getPaddingStart(), openedAmount); mShelfIcons.setActualPaddingStart(paddingStart); mShelfIcons.setOpenedAmount(openedAmount); - mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption()); } public void setMaxLayoutHeight(int maxLayoutHeight) { 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 50cbd69e31c8..6d85fb37e374 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 android.util.SparseBooleanArray; 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 @@ public class DozeParameters { 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 @@ public class DozeParameters { 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 @@ public class DozeParameters { 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 @@ public class DozeParameters { * 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 @@ public class DozeParameters { * 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/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java index 316bd5bcaca2..7f4deb0372f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java @@ -61,14 +61,10 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { public void setWorkModeEnabled(boolean enableWorkMode) { synchronized (mProfiles) { for (UserInfo ui : mProfiles) { - if (enableWorkMode) { - if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) { - StatusBarManager statusBarManager = (StatusBarManager) mContext - .getSystemService(android.app.Service.STATUS_BAR_SERVICE); - statusBarManager.collapsePanels(); - } - } else { - mUserManager.setQuietModeEnabled(ui.id, true); + if (!mUserManager.trySetQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) { + StatusBarManager statusBarManager = (StatusBarManager) mContext + .getSystemService(android.app.Service.STATUS_BAR_SERVICE); + statusBarManager.collapsePanels(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index a1b49c15a560..91cae0af6b13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -100,8 +100,10 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { }.setDuration(200).setDelay(50); public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5; + public static final int MAX_STATIC_ICONS = 4; + private static final int MAX_DOTS = 3; - private boolean mShowAllIcons = true; + private boolean mIsStaticLayout = true; private final HashMap<View, IconState> mIconStates = new HashMap<>(); private int mDotPadding; private int mStaticDotRadius; @@ -115,11 +117,13 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private int mSpeedBumpIndex = -1; private int mIconSize; private float mOpenedAmount = 0.0f; - private float mVisualOverflowAdaption; private boolean mDisallowNextAnimation; private boolean mAnimationsEnabled = true; private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons; private int mDarkOffsetX; + // Keep track of the last visible icon so collapsed container can report on its location + private IconState mLastVisibleIconState; + public NotificationIconContainer(Context context, AttributeSet attrs) { super(context, attrs); @@ -163,7 +167,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mIconSize = child.getWidth(); } } - if (mShowAllIcons) { + if (mIsStaticLayout) { resetViewStates(); calculateIconTranslations(); applyIconStates(); @@ -287,7 +291,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { float translationX = getActualPaddingStart(); int firstOverflowIndex = -1; int childCount = getChildCount(); - int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : childCount; + int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : + mIsStaticLayout ? MAX_STATIC_ICONS : childCount; float layoutEnd = getLayoutEnd(); float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT); boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount(); @@ -320,23 +325,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { visualOverflowStart += (translationX - overflowStart) / mIconSize * (mStaticDotRadius * 2 + mDotPadding); } - if (mShowAllIcons) { - // We want to perfectly position the overflow in the static state, such that - // it's perfectly centered instead of measuring it from the end. - mVisualOverflowAdaption = 0; - if (firstOverflowIndex != -1) { - View firstOverflowView = getChildAt(i); - IconState overflowState = mIconStates.get(firstOverflowView); - float totalAmount = layoutEnd - overflowState.xTranslation; - float newPosition = overflowState.xTranslation + totalAmount / 2 - - totalDotLength / 2 - - mIconSize * 0.5f + mStaticDotRadius; - mVisualOverflowAdaption = newPosition - visualOverflowStart; - visualOverflowStart = newPosition; - } - } else { - visualOverflowStart += mVisualOverflowAdaption * (1f - mOpenedAmount); - } } translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale; } @@ -348,20 +336,24 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { IconState iconState = mIconStates.get(view); int dotWidth = mStaticDotRadius * 2 + mDotPadding; iconState.xTranslation = translationX; - if (numDots <= 3) { + if (numDots <= MAX_DOTS) { if (numDots == 1 && iconState.iconAppearAmount < 0.8f) { iconState.visibleState = StatusBarIconView.STATE_ICON; numDots--; } else { iconState.visibleState = StatusBarIconView.STATE_DOT; } - translationX += (numDots == 3 ? 3 * dotWidth : dotWidth) + translationX += (numDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth) * iconState.iconAppearAmount; + mLastVisibleIconState = iconState; } else { iconState.visibleState = StatusBarIconView.STATE_HIDDEN; } numDots++; } + } else if (childCount > 0) { + View lastChild = getChildAt(childCount - 1); + mLastVisibleIconState = mIconStates.get(lastChild); } boolean center = mDark; if (center && translationX < getLayoutEnd()) { @@ -415,13 +407,13 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } /** - * Sets whether the layout should always show all icons. + * Sets whether the layout should always show the same number of icons. * If this is true, the icon positions will be updated on layout. * If this if false, the layout is managed from the outside and layouting won't trigger a * repositioning of the icons. */ - public void setShowAllIcons(boolean showAllIcons) { - mShowAllIcons = showAllIcons; + public void setIsStaticLayout(boolean isStaticLayout) { + mIsStaticLayout = isStaticLayout; } public void setActualLayoutWidth(int actualLayoutWidth) { @@ -452,6 +444,14 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { return mActualLayoutWidth; } + public int getFinalTranslationX() { + if (mLastVisibleIconState == null) { + return 0; + } + + return (int) (mLastVisibleIconState.xTranslation + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT)); + } + public void setChangingViewPositions(boolean changingViewPositions) { mChangingViewPositions = changingViewPositions; } @@ -479,17 +479,41 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mOpenedAmount = expandAmount; } - public float getVisualOverflowAdaption() { - return mVisualOverflowAdaption; + public boolean hasOverflow() { + if (mIsStaticLayout) { + return getChildCount() > MAX_STATIC_ICONS; + } + + float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize; + return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0; } - public void setVisualOverflowAdaption(float visualOverflowAdaption) { - mVisualOverflowAdaption = visualOverflowAdaption; + /** + * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then + * extra padding will have to be accounted for + * + * This method has no meaning for non-static containers + */ + public boolean hasPartialOverflow() { + if (mIsStaticLayout) { + int count = getChildCount(); + return count > MAX_STATIC_ICONS && count <= MAX_STATIC_ICONS + MAX_DOTS; + } + + return false; } - public boolean hasOverflow() { - float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize; - return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0; + /** + * Get padding that can account for extra dots up to the max. The only valid values for + * this method are for 1 or 2 dots. + * @return only extraDotPadding or extraDotPadding * 2 + */ + public int getPartialOverflowExtraPadding() { + if (!hasPartialOverflow()) { + return 0; + } + + return (MAX_STATIC_ICONS + MAX_DOTS - getChildCount()) * (mStaticDotRadius + mDotPadding); } public int getIconSize() { 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 168758f64b60..14329b564800 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.Animator; 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.NotificationData; 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, = 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } private void dispatchScrimsVisible() { - boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0; - - if (mScrimsVisible != scrimsVisible) { - mScrimsVisible = scrimsVisible; + 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; + } - mScrimVisibleListener.accept(scrimsVisible); + if (mScrimsVisibility != currentScrimVisibility) { + mScrimsVisibility = currentScrimVisibility; + mScrimVisibleListener.accept(currentScrimVisibility); } } @@ -811,6 +879,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, 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 c33cc509e816..fa2c1b3fa01c 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 @@ package com.android.systemui.statusbar.phone; 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 @@ public enum ScrimState { 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 @@ public enum ScrimState { 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 @@ public enum ScrimState { 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 @@ public enum ScrimState { mDozeParameters = dozeParameters; mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking(); mCanControlScreenOff = dozeParameters.getCanControlScreenOffAnimation(); + mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(scrimInFront.getContext()); } public void prepare(ScrimState previousState) { @@ -218,4 +225,8 @@ public enum ScrimState { 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 fecd6bd4b9ec..c5349d186b86 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.NonNull; 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.PendingIntent; 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.HashMap; 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 @@ public class StatusBar extends SystemUI implements DemoMode, 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 @@ public class StatusBar extends SystemUI implements DemoMode, 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 @@ public class StatusBar extends SystemUI implements DemoMode, 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 b0b5b8ec1e09..c30f6339f8da 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 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D 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 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D 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 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D 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 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D 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 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D apply(mCurrentState); } - public void setScrimsVisible(boolean scrimsVisible) { - mCurrentState.scrimsVisible = scrimsVisible; + public void setScrimsVisibility(int scrimsVisibility) { + mCurrentState.scrimsVisibility = scrimsVisibility; apply(mCurrentState); } @@ -346,6 +352,11 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D 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 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D boolean forceDozeBrightness; boolean forceUserActivity; boolean backdropShowing; + boolean wallpaperSupportsAmbientMode; /** * The {@link StatusBar} state from the status bar. @@ -442,7 +454,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D 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 fa82e3364b1f..f8843a997d59 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.DialogInterface; 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.R; 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 @@ public class OutputChooserDialog extends SystemUIDialog 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 @@ public class OutputChooserDialog extends SystemUIDialog 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 @@ public class OutputChooserDialog extends SystemUIDialog } @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 @@ public class OutputChooserDialog extends SystemUIDialog 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 @@ public class OutputChooserDialog extends SystemUIDialog } } } - 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 @@ public class OutputChooserDialog extends SystemUIDialog 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 e8be4fd553a1..22ced60006b0 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 @@ public class OutputChooserLayout extends FrameLayout { private AutoSizingList mItemList; private View mEmpty; private TextView mEmptyText; - private ImageView mEmptyIcon; private Item[] mItems; @@ -76,7 +75,6 @@ public class OutputChooserLayout extends FrameLayout { 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 class OutputChooserLayout extends FrameLayout { } } - 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 class OutputChooserLayout extends FrameLayout { } 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 class OutputChooserLayout extends FrameLayout { 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 000000000000..8e7f83da6203 --- /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 a40ef64b4a82..6d2691c899f2 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.AndroidTestingRunner; 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 @@ public class ScrimControllerTest extends SysuiTestCase { 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 @@ public class ScrimControllerTest extends SysuiTestCase { 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 @@ public class ScrimControllerTest extends SysuiTestCase { 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(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(false /* front */, true /* behind */); + 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 @@ public class ScrimControllerTest extends SysuiTestCase { 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 @@ public class ScrimControllerTest extends SysuiTestCase { 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 @@ public class ScrimControllerTest extends SysuiTestCase { // 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 @@ public class ScrimControllerTest extends SysuiTestCase { 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 @@ public class ScrimControllerTest extends SysuiTestCase { } @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 @@ public class ScrimControllerTest extends SysuiTestCase { 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 @@ public class ScrimControllerTest extends SysuiTestCase { 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 class ScrimControllerTest extends SysuiTestCase { 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 c19ae5254855..5019a06a377c 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 index 01679ddad04b..3d7d6b7e5f3f 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 @@ import java.util.Set; * 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 @@ abstract class AccessibilityClientConnection extends IAccessibilityServiceConnec 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 @@ abstract class AccessibilityClientConnection extends IAccessibilityServiceConnec } } + 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 @@ abstract class AccessibilityClientConnection extends IAccessibilityServiceConnec 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 @@ abstract class AccessibilityClientConnection extends IAccessibilityServiceConnec 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 270a7620f107..d83f6ae0425e 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.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY 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.MagnificationSpec; 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.FileDescriptor; 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 @@ import java.util.function.Consumer; * 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } return IntPair.of( (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0, - userState.mLastSentRelevantEventTypes); + client.mLastSentRelevantEventTypes); } } } @@ -951,7 +955,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } 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; - - 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 */ - } - }); - }); + mMainHandler.post(() -> { + broadcastToClients(userState, ignoreRemoteException(client -> { + int relevantEventTypes = computeRelevantEventTypes(userState, client); + + 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 class AccessibilityManagerService extends IAccessibilityManager.Stub } 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + /** 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 class AccessibilityManagerService extends IAccessibilityManager.Stub 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 9cafa1e778b6..5f6efb613be7 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 @@ package com.android.server.accessibility; 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.accessibility.AccessibilityManagerService.UserState; import com.android.server.wm.WindowManagerInternal; import java.lang.ref.WeakReference; -import java.util.List; import java.util.Set; /** @@ -50,7 +47,7 @@ import java.util.Set; * 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 56a95345aad0..ed3b3e770338 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 @@ package com.android.server.accessibility; 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 @@ class UiAutomationManager { private AccessibilityServiceInfo mUiAutomationServiceInfo; - private AccessibilityClientConnection.SystemSupport mSystemSupport; + private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport; private int mUiAutomationFlags; @@ -78,7 +79,7 @@ class UiAutomationManager { 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 @@ class UiAutomationManager { 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 @@ class UiAutomationManager { 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/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 4bf3c5ab9268..33618249f3c1 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -52,6 +52,7 @@ import android.os.UserManager; import android.provider.Settings; import android.service.autofill.AutofillService; import android.service.autofill.AutofillServiceInfo; +import android.service.autofill.FieldClassification; import android.service.autofill.FieldClassification.Match; import android.service.autofill.FillEventHistory; import android.service.autofill.FillEventHistory.Event; @@ -81,6 +82,7 @@ import com.android.server.autofill.ui.AutoFillUI; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; import java.util.Random; /** @@ -720,37 +722,45 @@ final class AutofillManagerServiceImpl { @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, @Nullable ArrayList<AutofillId> detectedFieldIdsList, - @Nullable ArrayList<Match> detectedMatchesList, + @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList, @NonNull String appPackageName) { - synchronized (mLock) { if (isValidEventLocked("logDatasetNotSelected()", sessionId)) { AutofillId[] detectedFieldsIds = null; - Match[] detectedMatches = null; + FieldClassification[] detectedFieldClassifications = null; if (detectedFieldIdsList != null) { detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()]; detectedFieldIdsList.toArray(detectedFieldsIds); - detectedMatches = new Match[detectedMatchesList.size()]; - detectedMatchesList.toArray(detectedMatches); + detectedFieldClassifications = + new FieldClassification[detectedFieldClassificationsList.size()]; + detectedFieldClassificationsList.toArray(detectedFieldClassifications); - final int size = detectedMatchesList.size(); + final int numberFields = detectedFieldsIds.length; + int totalSize = 0; float totalScore = 0; - for (int i = 0; i < size; i++) { - totalScore += detectedMatches[i].getScore(); + for (int i = 0; i < numberFields; i++) { + final FieldClassification fc = detectedFieldClassifications[i]; + final List<Match> matches = fc.getMatches(); + final int size = matches.size(); + totalSize += size; + for (int j = 0; j < size; j++) { + totalScore += matches.get(j).getScore(); + } } - final int averageScore = (int) ((totalScore * 100) / size); - mMetricsLogger.write( - Helper.newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES, - appPackageName, getServicePackageName()) - .setCounterValue(size) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, averageScore)); + final int averageScore = (int) ((totalScore * 100) / totalSize); + mMetricsLogger.write(Helper + .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES, + appPackageName, getServicePackageName()) + .setCounterValue(numberFields) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, + averageScore)); } mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null, clientState, selectedDatasets, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, - detectedFieldsIds, detectedMatches)); + detectedFieldsIds, detectedFieldClassifications)); } } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index ceae93cefe95..7b85a6c935da 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -66,6 +66,7 @@ import android.service.autofill.SaveRequest; import android.service.autofill.UserData; import android.service.autofill.ValueFinder; import android.service.autofill.EditDistanceScorer; +import android.service.autofill.FieldClassification; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LocalLog; @@ -961,15 +962,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final UserData userData = mService.getUserData(); final ArrayList<AutofillId> detectedFieldIds; - final ArrayList<Match> detectedMatches; + final ArrayList<FieldClassification> detectedFieldClassifications; if (userData != null) { final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize(); detectedFieldIds = new ArrayList<>(maxFieldsSize); - detectedMatches = new ArrayList<>(maxFieldsSize); + detectedFieldClassifications = new ArrayList<>(maxFieldsSize); } else { detectedFieldIds = null; - detectedMatches = null; + detectedFieldClassifications = null; } for (int i = 0; i < mViewStates.size(); i++) { @@ -1078,8 +1079,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Sets field classification score for field if (userData!= null) { - setScore(detectedFieldIds, detectedMatches, userData, viewState.id, - currentValue); + setScore(detectedFieldIds, detectedFieldClassifications, userData, + viewState.id, currentValue); } } // else } // else @@ -1093,7 +1094,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + ", changedDatasetIds=" + changedDatasetIds + ", manuallyFilledIds=" + manuallyFilledIds + ", detectedFieldIds=" + detectedFieldIds - + ", detectedMatches=" + detectedMatches + + ", detectedFieldClassifications=" + detectedFieldClassifications ); } @@ -1116,16 +1117,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, - detectedFieldIds, detectedMatches, mComponentName.getPackageName()); + detectedFieldIds, detectedFieldClassifications, mComponentName.getPackageName()); } /** - * Adds the top score match to {@code detectedFieldsIds} and {@code detectedMatches} for + * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for * {@code fieldId} based on its {@code currentValue} and {@code userData}. */ private static void setScore(@NonNull ArrayList<AutofillId> detectedFieldIds, - @NonNull ArrayList<Match> detectedMatches, @NonNull UserData userData, - @NonNull AutofillId fieldId, @NonNull AutofillValue currentValue) { + @NonNull ArrayList<FieldClassification> detectedFieldClassifications, + @NonNull UserData userData, @NonNull AutofillId fieldId, + @NonNull AutofillValue currentValue) { final String[] userValues = userData.getValues(); final String[] remoteIds = userData.getRemoteIds(); @@ -1138,23 +1140,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + valuesLength + ", ids.length = " + idsLength); return; } - String remoteId = null; - float topScore = 0; + + ArrayList<Match> matches = null; for (int i = 0; i < userValues.length; i++) { + String remoteId = remoteIds[i]; final String value = userValues[i]; final float score = userData.getScorer().getScore(currentValue, value); - if (score > topScore) { - topScore = score; - remoteId = remoteIds[i]; + if (score > 0) { + if (sVerbose) { + Slog.v(TAG, "adding score " + score + " at index " + i + " and id " + fieldId); + } + if (matches == null) { + matches = new ArrayList<>(userValues.length); + } + matches.add(new Match(remoteId, score)); } + else if (sVerbose) Slog.v(TAG, "skipping score 0 at index " + i + " and id " + fieldId); } - - if (remoteId != null && topScore > 0) { - if (sVerbose) Slog.v(TAG, "setScores(): top score for #" + fieldId + " is " + topScore); + if (matches != null) { detectedFieldIds.add(fieldId); - detectedMatches.add(new Match(remoteId, topScore)); - } else if (sVerbose) { - Slog.v(TAG, "setScores(): no top score for #" + fieldId + ": " + topScore); + detectedFieldClassifications.add(new FieldClassification(matches)); } } diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java index f1854433dcd0..fbdb18327563 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 @@ public class TransportManager { 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 e9eb3b3c469d..d3ab1259c9ed 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.IpSecTransform; 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 @@ public class IpSecService extends IIpSecService.Stub { } private final IpSecServiceConfiguration mSrvConfig; + final UidFdTagger mUidFdTagger; /** * Interface for user-reference and kernel-resource cleanup. @@ -762,8 +764,23 @@ public class IpSecService extends IIpSecService.Stub { /** @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 @@ public class IpSecService extends IIpSecService.Stub { } /** + * 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 @@ public class IpSecService extends IIpSecService.Stub { } 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 @@ public class IpSecService extends IIpSecService.Stub { } 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 @@ public class IpSecService extends IIpSecService.Stub { 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 0ffc77923f1a..31aea63875ea 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 @@ public class AccountManagerService 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 3cd2f6ae3b31..088ddeaab820 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 @@ public final class ActiveServices { 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 eaa7108b9e9d..7dfde56c07ee 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.notification.SystemNotificationChannels; 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } - 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 eb1636d732ee..ab5d64c48ac8 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index efa0bf82f209..2d7a6adeba3f 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3884,7 +3884,8 @@ public class AudioService extends IAudioService.Stub IsInCall = telecomManager.isInCall(); Binder.restoreCallingIdentity(ident); - return (IsInCall || getMode() == AudioManager.MODE_IN_COMMUNICATION); + return (IsInCall || getMode() == AudioManager.MODE_IN_COMMUNICATION || + getMode() == AudioManager.MODE_IN_CALL); } /** diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java index 41d9feb97c4e..9640e042fe6a 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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** - * 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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** - * 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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** - * 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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } } } + + /** + * 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 d58a7460b23f..60b5b1f06c42 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 @@ import java.util.function.Consumer; } /** - * 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 1ad0cf931165..26edf6270770 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 @@ public class ContextHubService extends IContextHubService.Stub { 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 @@ public class ContextHubService extends IContextHubService.Stub { 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 @@ public class ContextHubService extends IContextHubService.Stub { 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 @@ public class ContextHubService extends IContextHubService.Stub { 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()); - } - for (int i = 0; i < mContextHubInfo.length; i++) { - Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId() - + ", name: " + mContextHubInfo[i].getName()); + // Do a query to initialize the service cache list of nanoapps + // TODO(b/69270990): Remove this when old API is deprecated + queryNanoAppsInternal(contextHubId); } + mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap); } /** @@ -267,27 +259,29 @@ public class ContextHubService extends IContextHubService.Stub { @Override public int[] getContextHubHandles() throws RemoteException { checkPermissions(); - int[] returnArray = new int[mContextHubInfo.length]; + return ContextHubServiceUtil.createPrimitiveIntArray(mContextHubIdToInfoMap.keySet()); + } - Log.d(TAG, "System supports " + returnArray.length + " hubs"); - for (int i = 0; i < returnArray.length; ++i) { - returnArray[i] = i; - Log.d(TAG, String.format("Hub %s is mapped to %d", - mContextHubInfo[i].getName(), returnArray[i])); + @Override + public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException { + checkPermissions(); + if (!mContextHubIdToInfoMap.containsKey(contextHubHandle)) { + Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in getContextHubInfo"); + return null; } - return returnArray; + return mContextHubIdToInfoMap.get(contextHubHandle); } + /** + * Returns a List of ContextHubInfo object describing the available hubs. + * + * @return the List of ContextHubInfo objects + */ @Override - public ContextHubInfo getContextHubInfo(int contextHubId) throws RemoteException { + public List<ContextHubInfo> getContextHubs() throws RemoteException { checkPermissions(); - if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) { - Log.e(TAG, "Invalid context hub handle " + contextHubId); - return null; // null means fail - } - - return mContextHubInfo[contextHubId]; + return mContextHubInfoList; } /** @@ -351,28 +345,27 @@ public class ContextHubService extends IContextHubService.Stub { } @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; @@ -388,7 +381,7 @@ public class ContextHubService extends IContextHubService.Stub { 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; } @@ -411,7 +404,8 @@ public class ContextHubService extends IContextHubService.Stub { } @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<>(); @@ -425,12 +419,6 @@ public class ContextHubService extends IContextHubService.Stub { for (int i = 0; i < foundInstances.size(); i++) { retArray[i] = foundInstances.get(i).intValue(); } - - if (retArray.length == 0) { - Log.d(TAG, "No nanoapps found on hub ID " + hubHandle + " using NanoAppFilter: " - + filter); - } - return retArray; } @@ -460,29 +448,29 @@ public class ContextHubService extends IContextHubService.Stub { } @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()); } @@ -492,7 +480,7 @@ public class ContextHubService extends IContextHubService.Stub { 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 { @@ -583,7 +571,7 @@ public class ContextHubService extends IContextHubService.Stub { * @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); } /** @@ -605,13 +593,7 @@ public class ContextHubService extends IContextHubService.Stub { * @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); } /** @@ -772,8 +754,8 @@ public class ContextHubService extends IContextHubService.Stub { 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 ===================="); @@ -789,7 +771,8 @@ public class ContextHubService extends IContextHubService.Stub { 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; } @@ -797,7 +780,8 @@ public class ContextHubService extends IContextHubService.Stub { 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."); @@ -808,7 +792,7 @@ public class ContextHubService extends IContextHubService.Stub { 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 6faeb72e2a4d..7a57dd38c95f 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.NanoAppMessage; 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 @@ import java.util.ArrayList; + 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 @@ import java.util.ArrayList; } /** + * 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 60f451ab437e..aa55930697ba 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.USER_FRP; 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.Process; 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.AndroidKeyStoreProvider; 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.LockPatternUtils; 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 @@ public class LockSettingsService extends ILockSettings.Stub { private final KeyStore mKeyStore; + private final RecoverableKeyStoreManager mRecoverableKeyStoreManager; + private boolean mFirstCallToVold; protected IGateKeeperService mGateKeeperService; @@ -367,6 +377,10 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { mInjector = injector; mContext = injector.getContext(); mKeyStore = injector.getKeyStore(); + mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager(); mHandler = injector.getHandler(); mStrongAuth = injector.getStrongAuth(); mActivityManager = injector.getActivityManager(); @@ -1914,6 +1929,72 @@ public class LockSettingsService extends ILockSettings.Stub { } } + @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 000000000000..9a4d05119a3b --- /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/core/java/android/os/IIncidentReportCompletedListener.aidl b/services/core/java/com/android/server/locksettings/recoverablekeystore/InsecureUserException.java index 2d66bf635a2a..5155a99ee04f 100644 --- a/core/java/android/os/IIncidentReportCompletedListener.aidl +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/InsecureUserException.java @@ -1,11 +1,11 @@ -/** - * Copyright (c) 2016, The Android Open Source Project +/* + * 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,19 +14,18 @@ * limitations under the License. */ -package android.os; +package com.android.server.locksettings.recoverablekeystore; /** - * Listener for incident report status - * - * {@hide} - */ -oneway interface IIncidentReportCompletedListener { + * Error thrown initializing {@link PlatformKeyManager} if the user is not secure (i.e., has no + * lock screen set). + */ +public class InsecureUserException extends Exception { + /** - * 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. + * A new instance with {@code message} error message. */ - void onIncidentReport(String filename); + 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 000000000000..81031770ff68 --- /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 000000000000..59132da7d4c7 --- /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/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java index 37aeb3af051e..25428e7a6028 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java @@ -20,10 +20,13 @@ import com.android.internal.annotations.VisibleForTesting; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; +import java.security.KeyFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; import java.util.HashMap; import java.util.Map; @@ -39,6 +42,7 @@ import javax.crypto.SecretKey; */ public class KeySyncUtils { + private static final String PUBLIC_KEY_FACTORY_ALGORITHM = "EC"; private static final String RECOVERY_KEY_ALGORITHM = "AES"; private static final int RECOVERY_KEY_SIZE_BITS = 256; @@ -237,6 +241,21 @@ public class KeySyncUtils { } /** + * Deserializes a X509 public key. + * + * @param key The bytes of the key. + * @return The key. + * @throws NoSuchAlgorithmException if the public key algorithm is unavailable. + * @throws InvalidKeySpecException if the bytes of the key are not a valid key. + */ + public static PublicKey deserializePublicKey(byte[] key) + throws NoSuchAlgorithmException, InvalidKeySpecException { + KeyFactory keyFactory = KeyFactory.getInstance(PUBLIC_KEY_FACTORY_ALGORITHM); + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(key); + return keyFactory.generatePublic(publicKeySpec); + } + + /** * Returns the concatenation of all the given {@code arrays}. */ @VisibleForTesting diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java new file mode 100644 index 000000000000..0f17294b461f --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.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 com.android.server.locksettings.recoverablekeystore; + +import android.annotation.Nullable; +import android.app.PendingIntent; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Map; +import java.util.HashMap; + +/** + * In memory storage for listeners to be notified when new recovery snapshot is available. + * Note: implementation is not thread safe and it is used to mock final {@link PendingIntent} + * class. + * + * @hide + */ +public class ListenersStorage { + private Map<Integer, PendingIntent> mAgentIntents = new HashMap<>(); + + private static final ListenersStorage mInstance = new ListenersStorage(); + public static ListenersStorage getInstance() { + return mInstance; + } + + /** + * Sets new listener for the recovery agent, identified by {@code uid} + * + * @param recoveryAgentUid uid + * @param intent PendingIntent which will be triggered than new snapshot is available. + */ + public void setSnapshotListener(int recoveryAgentUid, @Nullable PendingIntent intent) { + mAgentIntents.put(recoveryAgentUid, intent); + } + + /** + * Notifies recovery agent, that new snapshot is available. + * Does nothing if a listener was not registered. + * + * @param recoveryAgentUid uid. + */ + public void recoverySnapshotAvailable(int recoveryAgentUid) { + PendingIntent intent = mAgentIntents.get(recoveryAgentUid); + if (intent != null) { + try { + intent.send(); + } catch (PendingIntent.CanceledException e) { + // Ignore - sending intent is not allowed. + } + } + } +} 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 000000000000..24f3f65eec15 --- /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 b22ba4ec8bde..d50a736501ec 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java @@ -20,11 +20,14 @@ 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; @@ -41,39 +44,39 @@ import javax.security.auth.DestroyFailedException; */ 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( - PlatformEncryptionKey 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 PlatformEncryptionKey mPlatformKey; + private final RecoverableKeyStoreDb mDatabase; + private final AndroidKeyStoreFactory mAndroidKeyStoreFactory; private RecoverableKeyGenerator( KeyGenerator keyGenerator, - PlatformEncryptionKey platformKey, - RecoverableKeyStorage recoverableKeyStorage) { + RecoverableKeyStoreDb recoverableKeyStoreDb, + AndroidKeyStoreFactory androidKeyStoreFactory) { mKeyGenerator = keyGenerator; - mRecoverableKeyStorage = recoverableKeyStorage; - mPlatformKey = platformKey; + mAndroidKeyStoreFactory = androidKeyStoreFactory; + mDatabase = recoverableKeyStoreDb; } /** @@ -83,50 +86,70 @@ public class RecoverableKeyGenerator { * 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 6a189efcdb57..000000000000 --- 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 000000000000..f9d28f17d7a3 --- /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 d4dede165177..000000000000 --- 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 000000000000..74d132fd1554 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -0,0 +1,268 @@ +/* + * 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 com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; +import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +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 final Context mContext; + private final RecoverableKeyStoreDb mDatabase; + private final RecoverySessionStorage mRecoverySessionStorage; + + /** + * Returns a new or existing instance. + * + * @hide + */ + public static synchronized RecoverableKeyStoreManager getInstance(Context mContext) { + if (mInstance == null) { + RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(mContext); + mInstance = new RecoverableKeyStoreManager( + mContext.getApplicationContext(), + db, + new RecoverySessionStorage()); + } + return mInstance; + } + + @VisibleForTesting + RecoverableKeyStoreManager( + Context context, + RecoverableKeyStoreDb recoverableKeyStoreDb, + RecoverySessionStorage recoverySessionStorage) { + mContext = context; + mDatabase = recoverableKeyStoreDb; + mRecoverySessionStorage = recoverySessionStorage; + } + + 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. + * + * @param sessionId A unique ID to identify the recovery session. + * @param verifierPublicKey X509-encoded public key. + * @param vaultParams Additional params associated with vault. + * @param vaultChallenge Challenge issued by vault service. + * @param secrets Lock-screen hashes. Should have a single element. TODO: why is this a list? + * @return Encrypted bytes of recovery claim. This can then be issued to the vault service. + * + * @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(); + + if (secrets.size() != 1) { + // TODO: support multiple secrets + throw new RemoteException("Only a single KeyStoreRecoveryMetadata is supported"); + } + + byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); + byte[] kfHash = secrets.get(0).getSecret(); + mRecoverySessionStorage.add( + userId, new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant)); + + try { + byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash); + PublicKey publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey); + return KeySyncUtils.encryptRecoveryClaim( + publicKey, + vaultParams, + vaultChallenge, + thmKfHash, + keyClaimant); + } catch (NoSuchAlgorithmException e) { + // Should never happen: all the algorithms used are required by AOSP implementations. + throw new RemoteException( + "Missing required algorithm", + e, + /*enableSuppression=*/ true, + /*writeableStackTrace=*/ true); + } catch (InvalidKeySpecException | InvalidKeyException e) { + throw new RemoteException( + "Not a valid X509 key", + e, + /*enableSuppression=*/ true, + /*writeableStackTrace=*/ true); + } + } + + 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 742cb4591864..d8a2d31f6703 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 @@ public class SecureBox { @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 @@ public class SecureBox { @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 a0e34c35b314..dfa173c8d463 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java @@ -130,6 +130,7 @@ public class WrappedKey { return mKeyMaterial; } + /** * Returns the generation ID of the platform key, with which this key was wrapped. * 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 new file mode 100644 index 000000000000..3644d368a7f3 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java @@ -0,0 +1,233 @@ +/* + * 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.storage; + +import android.annotation.Nullable; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +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; +import java.util.Map; + +/** + * Database of recoverable key information. + * + * @hide + */ +public class RecoverableKeyStoreDb { + private static final String TAG = "RecoverableKeyStoreDb"; + private static final int IDLE_TIMEOUT_SECONDS = 30; + + private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper; + + /** + * A new instance, storing the database in the user directory of {@code context}. + * + * @hide + */ + public static RecoverableKeyStoreDb newInstance(Context context) { + RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context); + helper.setWriteAheadLoggingEnabled(true); + helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS); + return new RecoverableKeyStoreDb(helper); + } + + private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) { + this.mKeyStoreDbHelper = keyStoreDbHelper; + } + + /** + * Inserts a key into the database. + * + * @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 key. + * @return The primary key of the inserted row, or -1 if failed. + * + * @hide + */ + public long insertKey(int uid, String alias, WrappedKey wrappedKey) { + SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(KeysEntry.COLUMN_NAME_UID, uid); + values.put(KeysEntry.COLUMN_NAME_ALIAS, alias); + 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, wrappedKey.getPlatformKeyGenerationId()); + return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values); + } + + /** + * Gets the key with {@code alias} for the app with {@code uid}. + * + * @hide + */ + @Nullable public WrappedKey getKey(int uid, String alias) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + String[] projection = { + KeysEntry._ID, + KeysEntry.COLUMN_NAME_NONCE, + KeysEntry.COLUMN_NAME_WRAPPED_KEY, + KeysEntry.COLUMN_NAME_GENERATION_ID}; + String selection = + KeysEntry.COLUMN_NAME_UID + " = ? AND " + + KeysEntry.COLUMN_NAME_ALIAS + " = ?"; + String[] selectionArguments = { Integer.toString(uid), alias }; + + try ( + Cursor cursor = db.query( + KeysEntry.TABLE_NAME, + projection, + selection, + selectionArguments, + /*groupBy=*/ null, + /*having=*/ null, + /*orderBy=*/ null) + ) { + int count = cursor.getCount(); + if (count == 0) { + return null; + } + if (count > 1) { + Log.wtf(TAG, + String.format(Locale.US, + "%d WrappedKey entries found for uid=%d alias='%s'. " + + "Should only ever be 0 or 1.", count, uid, alias)); + return null; + } + cursor.moveToFirst(); + byte[] nonce = cursor.getBlob( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE)); + byte[] keyMaterial = cursor.getBlob( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); + int generationId = cursor.getInt( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID)); + return new WrappedKey(nonce, keyMaterial, generationId); + } + } + + /** + * Returns all keys for the given {@code uid} and {@code platformKeyGenerationId}. + * + * @param uid User id of the profile to which all the keys are associated. + * @param platformKeyGenerationId The generation ID of the platform key that wrapped these keys. + * (i.e., this should be the most recent generation ID, as older platform keys are not + * usable.) + * + * @hide + */ + public Map<String, WrappedKey> getAllKeys(int uid, int platformKeyGenerationId) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + String[] projection = { + KeysEntry._ID, + KeysEntry.COLUMN_NAME_NONCE, + KeysEntry.COLUMN_NAME_WRAPPED_KEY, + KeysEntry.COLUMN_NAME_ALIAS}; + String selection = + KeysEntry.COLUMN_NAME_UID + " = ? AND " + + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?"; + String[] selectionArguments = { + Integer.toString(uid), Integer.toString(platformKeyGenerationId) }; + + try ( + Cursor cursor = db.query( + KeysEntry.TABLE_NAME, + projection, + selection, + selectionArguments, + /*groupBy=*/ null, + /*having=*/ null, + /*orderBy=*/ null) + ) { + HashMap<String, WrappedKey> keys = new HashMap<>(); + while (cursor.moveToNext()) { + byte[] nonce = cursor.getBlob( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE)); + byte[] keyMaterial = cursor.getBlob( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); + String alias = cursor.getString( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS)); + 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() { + mKeyStoreDbHelper.close(); + } + + // TODO: Add method for updating the 'last synced' time. +} 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 new file mode 100644 index 000000000000..b6c168f41078 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java @@ -0,0 +1,79 @@ +/* + * 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.storage; + +import android.provider.BaseColumns; + +/** + * Contract for recoverable key database. Describes the tables present. + */ +class RecoverableKeyStoreDbContract { + /** + * Table holding wrapped keys, and information about when they were last synced. + */ + static class KeysEntry implements BaseColumns { + static final String TABLE_NAME = "keys"; + + /** + * The uid of the application that generated the key. + */ + static final String COLUMN_NAME_UID = "uid"; + + /** + * The alias of the key, as set in AndroidKeyStore. + */ + static final String COLUMN_NAME_ALIAS = "alias"; + + /** + * Nonce with which the key was encrypted. + */ + static final String COLUMN_NAME_NONCE = "nonce"; + + /** + * Encrypted bytes of the key. + */ + static final String COLUMN_NAME_WRAPPED_KEY = "wrapped_key"; + + /** + * Generation ID of the platform key that was used to encrypt this key. + */ + static final String COLUMN_NAME_GENERATION_ID = "platform_key_generation_id"; + + /** + * Timestamp of when this key was last synced with remote storage, or -1 if never synced. + */ + 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 new file mode 100644 index 000000000000..686820323241 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java @@ -0,0 +1,57 @@ +package com.android.server.locksettings.recoverablekeystore.storage; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +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. + */ +class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { + private static final int DATABASE_VERSION = 1; + private static final String DATABASE_NAME = "recoverablekeystore.db"; + + private static final String SQL_CREATE_KEYS_ENTRY = + "CREATE TABLE " + KeysEntry.TABLE_NAME + "( " + + KeysEntry._ID + " INTEGER PRIMARY KEY," + + 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," + + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + "," + + KeysEntry.COLUMN_NAME_ALIAS + "))"; + + 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_KEYS_ENTRY); + db.execSQL(SQL_CREATE_USER_METADATA_ENTRY); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL(SQL_DELETE_KEYS_ENTRY); + db.execSQL(SQL_DELETE_USER_METADATA_ENTRY); + onCreate(db); + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java new file mode 100644 index 000000000000..bc56ae1dc181 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java @@ -0,0 +1,173 @@ +/* + * 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.storage; + +import android.annotation.Nullable; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.Arrays; + +import javax.security.auth.Destroyable; + +/** + * Stores pending recovery sessions in memory. We do not write these to disk, as it contains hashes + * of the user's lock screen. + * + * @hide + */ +public class RecoverySessionStorage implements Destroyable { + + private final SparseArray<ArrayList<Entry>> mSessionsByUid = new SparseArray<>(); + + /** + * Returns the session for the given user with the given id. + * + * @param uid The uid of the recovery agent who created the session. + * @param sessionId The unique identifier for the session. + * @return The session info. + * + * @hide + */ + @Nullable + public Entry get(int uid, String sessionId) { + ArrayList<Entry> userEntries = mSessionsByUid.get(uid); + if (userEntries == null) { + return null; + } + for (Entry entry : userEntries) { + if (sessionId.equals(entry.mSessionId)) { + return entry; + } + } + return null; + } + + /** + * Adds a pending session for the given user. + * + * @param uid The uid of the recovery agent who created the session. + * @param entry The session info. + * + * @hide + */ + public void add(int uid, Entry entry) { + if (mSessionsByUid.get(uid) == null) { + mSessionsByUid.put(uid, new ArrayList<>()); + } + mSessionsByUid.get(uid).add(entry); + } + + /** + * Removes all sessions associated with the given recovery agent uid. + * + * @param uid The uid of the recovery agent whose sessions to remove. + * + * @hide + */ + public void remove(int uid) { + ArrayList<Entry> entries = mSessionsByUid.get(uid); + if (entries == null) { + return; + } + for (Entry entry : entries) { + entry.destroy(); + } + mSessionsByUid.remove(uid); + } + + /** + * Returns the total count of pending sessions. + * + * @hide + */ + public int size() { + int size = 0; + int numberOfUsers = mSessionsByUid.size(); + for (int i = 0; i < numberOfUsers; i++) { + ArrayList<Entry> entries = mSessionsByUid.valueAt(i); + size += entries.size(); + } + return size; + } + + /** + * Wipes the memory of any sensitive information (i.e., lock screen hashes and key claimants). + * + * @hide + */ + @Override + public void destroy() { + int numberOfUids = mSessionsByUid.size(); + for (int i = 0; i < numberOfUids; i++) { + ArrayList<Entry> entries = mSessionsByUid.valueAt(i); + for (Entry entry : entries) { + entry.destroy(); + } + } + } + + /** + * Information about a recovery session. + * + * @hide + */ + public static class Entry implements Destroyable { + private final byte[] mLskfHash; + private final byte[] mKeyClaimant; + private final String mSessionId; + + /** + * @hide + */ + public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant) { + this.mLskfHash = lskfHash; + this.mSessionId = sessionId; + this.mKeyClaimant = keyClaimant; + } + + /** + * Returns the hash of the lock screen associated with the recovery attempt. + * + * @hide + */ + public byte[] getLskfHash() { + return mLskfHash; + } + + /** + * Returns the key generated for this recovery attempt (used to decrypt data returned by + * the server). + * + * @hide + */ + public byte[] getKeyClaimant() { + return mKeyClaimant; + } + + /** + * Overwrites the memory for the lskf hash and key claimant. + * + * @hide + */ + @Override + public void destroy() { + Arrays.fill(mLskfHash, (byte) 0); + Arrays.fill(mKeyClaimant, (byte) 0); + } + } +} diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 0b089fbc6129..384efdda88d7 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -21,6 +21,9 @@ import com.android.server.Watchdog; import android.annotation.NonNull; import android.app.ActivityManager; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -96,21 +99,23 @@ public final class MediaRouterService extends IMediaRouterService.Stub private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<IBinder, ClientRecord>(); private int mCurrentUserId = -1; - private boolean mGlobalBluetoothA2dpOn = false; private final IAudioService mAudioService; private final AudioPlayerStateMonitor mAudioPlayerStateMonitor; private final Handler mHandler = new Handler(); - private final AudioRoutesInfo mAudioRoutesInfo = new AudioRoutesInfo(); private final IntArray mActivePlayerMinPriorityQueue = new IntArray(); private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray(); + private final BroadcastReceiver mReceiver = new MediaRouterServiceBroadcastReceiver(); + BluetoothDevice mBluetoothDevice; + int mAudioRouteMainType = AudioRoutesInfo.MAIN_SPEAKER; + boolean mGlobalBluetoothA2dpOn = false; + public MediaRouterService(Context context) { mContext = context; Watchdog.getInstance().addMonitor(this); mAudioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); - mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(); mAudioPlayerStateMonitor.registerListener( new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() { @@ -170,44 +175,30 @@ public final class MediaRouterService extends IMediaRouterService.Stub @Override public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { synchronized (mLock) { - if (newRoutes.mainType != mAudioRoutesInfo.mainType) { + if (newRoutes.mainType != mAudioRouteMainType) { if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET | AudioRoutesInfo.MAIN_HEADPHONES | AudioRoutesInfo.MAIN_USB)) == 0) { // headset was plugged out. - mGlobalBluetoothA2dpOn = newRoutes.bluetoothName != null; + mGlobalBluetoothA2dpOn = mBluetoothDevice != null; } else { // headset was plugged in. mGlobalBluetoothA2dpOn = false; } - mAudioRoutesInfo.mainType = newRoutes.mainType; - } - if (!TextUtils.equals( - newRoutes.bluetoothName, mAudioRoutesInfo.bluetoothName)) { - if (newRoutes.bluetoothName == null) { - // BT was disconnected. - mGlobalBluetoothA2dpOn = false; - } else { - // BT was connected or changed. - mGlobalBluetoothA2dpOn = true; - } - mAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName; + mAudioRouteMainType = newRoutes.mainType; } - // Although a Bluetooth device is connected before a new audio playback is - // started, dispatchAudioRoutChanged() can be called after - // onAudioPlayerActiveStateChanged(). That causes restoreBluetoothA2dp() - // is called before mGlobalBluetoothA2dpOn is updated. - // Calling restoreBluetoothA2dp() here could prevent that. - restoreBluetoothA2dp(); + // The new audio routes info could be delivered with several seconds delay. + // In order to avoid such delay, Bluetooth device info will be updated + // via MediaRouterServiceBroadcastReceiver. } } }); } catch (RemoteException e) { Slog.w(TAG, "RemoteException in the audio service."); } - synchronized (mLock) { - mGlobalBluetoothA2dpOn = (audioRoutes != null && audioRoutes.bluetoothName != null); - } + + IntentFilter intentFilter = new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null); } public void systemRunning() { @@ -415,14 +406,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub void restoreBluetoothA2dp() { try { - boolean btConnected = false; boolean a2dpOn = false; synchronized (mLock) { - btConnected = mAudioRoutesInfo.bluetoothName != null; a2dpOn = mGlobalBluetoothA2dpOn; } // We don't need to change a2dp status when bluetooth is not connected. - if (btConnected) { + if (mBluetoothDevice != null) { Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")"); mAudioService.setBluetoothA2dpOn(a2dpOn); } @@ -661,6 +650,25 @@ public final class MediaRouterService extends IMediaRouterService.Stub return false; } + final class MediaRouterServiceBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, + BluetoothProfile.STATE_DISCONNECTED); + if (state == BluetoothProfile.STATE_DISCONNECTED) { + mGlobalBluetoothA2dpOn = false; + mBluetoothDevice = null; + } else if (state == BluetoothProfile.STATE_CONNECTED) { + mGlobalBluetoothA2dpOn = true; + mBluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + // To ensure that BT A2DP is on, call restoreBluetoothA2dp(). + restoreBluetoothA2dp(); + } + } + } + } + /** * Information about a particular client of the media router. * The contents of this object is guarded by mLock. diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 03cd4f1d3269..768eb8f37549 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -27,6 +27,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityManagerNative; +import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IStopUserCallback; import android.app.KeyguardManager; @@ -38,6 +39,7 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; @@ -386,7 +388,7 @@ public class UserManagerService extends IUserManager.Stub { /** * Start an {@link IntentSender} when user is unlocked after disabling quiet mode. * - * @see {@link #trySetQuietModeDisabled(int, IntentSender)} + * @see {@link #trySetQuietModeEnabled(String, boolean, int, IntentSender)} */ private class DisableQuietModeUserUnlockedCallback extends IProgressListener.Stub { private final IntentSender mTarget; @@ -784,48 +786,114 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public void setQuietModeEnabled(int userHandle, boolean enableQuietMode, IntentSender target) { - checkManageUsersPermission("silence profile"); - boolean changed = false; - UserInfo profile, parent; - synchronized (mPackagesLock) { - synchronized (mUsersLock) { - profile = getUserInfoLU(userHandle); - parent = getProfileParentLU(userHandle); + public boolean trySetQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode, + int userHandle, @Nullable IntentSender target) { + Preconditions.checkNotNull(callingPackage); + + if (enableQuietMode && target != null) { + throw new IllegalArgumentException( + "target should only be specified when we are disabling quiet mode."); + } + if (!isAllowedToSetWorkMode(callingPackage, Binder.getCallingUid())) { + throw new SecurityException("Not allowed to call trySetQuietModeEnabled, " + + "caller is foreground default launcher " + + "nor with MANAGE_USERS/MODIFY_QUIET_MODE permission"); + } + + final long identity = Binder.clearCallingIdentity(); + try { + if (enableQuietMode) { + setQuietModeEnabled(userHandle, true /* enableQuietMode */, target); + return true; + } else { + boolean needToShowConfirmCredential = + mLockPatternUtils.isSecure(userHandle) + && !StorageManager.isUserKeyUnlocked(userHandle); + if (needToShowConfirmCredential) { + showConfirmCredentialToDisableQuietMode(userHandle, target); + return false; + } else { + setQuietModeEnabled(userHandle, false /* enableQuietMode */, target); + return true; + } } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * An app can modify quiet mode if the caller meets one of the condition: + * <ul> + * <li>Has system UID or root UID</li> + * <li>Has {@link Manifest.permission#MODIFY_QUIET_MODE}</li> + * <li>Has {@link Manifest.permission#MANAGE_USERS}</li> + * </ul> + */ + private boolean isAllowedToSetWorkMode(String callingPackage, int callingUid) { + if (hasManageUsersPermission()) { + return true; + } + + final boolean hasModifyQuietModePermission = ActivityManager.checkComponentPermission( + Manifest.permission.MODIFY_QUIET_MODE, + callingUid, -1, true) == PackageManager.PERMISSION_GRANTED; + if (hasModifyQuietModePermission) { + return true; + } + + final ShortcutServiceInternal shortcutInternal = + LocalServices.getService(ShortcutServiceInternal.class); + if (shortcutInternal != null) { + boolean isForegroundLauncher = + shortcutInternal.isForegroundDefaultLauncher(callingPackage, callingUid); + if (isForegroundLauncher) { + return true; + } + } + return false; + } + + private void setQuietModeEnabled( + int userHandle, boolean enableQuietMode, IntentSender target) { + final UserInfo profile, parent; + final UserData profileUserData; + synchronized (mUsersLock) { + profile = getUserInfoLU(userHandle); + parent = getProfileParentLU(userHandle); + if (profile == null || !profile.isManagedProfile()) { throw new IllegalArgumentException("User " + userHandle + " is not a profile"); } - if (profile.isQuietModeEnabled() != enableQuietMode) { - profile.flags ^= UserInfo.FLAG_QUIET_MODE; - writeUserLP(getUserDataLU(profile.id)); - changed = true; + if (profile.isQuietModeEnabled() == enableQuietMode) { + Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode); + return; } + profile.flags ^= UserInfo.FLAG_QUIET_MODE; + profileUserData = getUserDataLU(profile.id); } - if (changed) { - long identity = Binder.clearCallingIdentity(); - try { - if (enableQuietMode) { - ActivityManager.getService().stopUser(userHandle, /* force */true, null); - LocalServices.getService(ActivityManagerInternal.class) - .killForegroundAppsForUser(userHandle); - } else { - IProgressListener callback = target != null - ? new DisableQuietModeUserUnlockedCallback(target) - : null; - ActivityManager.getService().startUserInBackgroundWithListener( - userHandle, callback); - } - } catch (RemoteException e) { - Slog.e(LOG_TAG, "fail to start/stop user for quiet mode", e); - } finally { - Binder.restoreCallingIdentity(identity); + synchronized (mPackagesLock) { + writeUserLP(profileUserData); + } + try { + if (enableQuietMode) { + ActivityManager.getService().stopUser(userHandle, /* force */true, null); + LocalServices.getService(ActivityManagerInternal.class) + .killForegroundAppsForUser(userHandle); + } else { + IProgressListener callback = target != null + ? new DisableQuietModeUserUnlockedCallback(target) + : null; + ActivityManager.getService().startUserInBackgroundWithListener( + userHandle, callback); } - - broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(), - enableQuietMode); + } catch (RemoteException e) { + // Should not happen, same process. + e.rethrowAsRuntimeException(); } + broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(), + enableQuietMode); } @Override @@ -842,54 +910,42 @@ public class UserManagerService extends IUserManager.Stub { } } - @Override - public boolean trySetQuietModeDisabled( + /** + * Show confirm credential screen to unlock user in order to turn off quiet mode. + */ + private void showConfirmCredentialToDisableQuietMode( @UserIdInt int userHandle, @Nullable IntentSender target) { - checkManageUsersPermission("silence profile"); - if (StorageManager.isUserKeyUnlocked(userHandle) - || !mLockPatternUtils.isSecure(userHandle)) { - // if the user is already unlocked, no need to show a profile challenge - setQuietModeEnabled(userHandle, false, target); - return true; - } - - long identity = Binder.clearCallingIdentity(); - try { - // otherwise, we show a profile challenge to trigger decryption of the user - final KeyguardManager km = (KeyguardManager) mContext.getSystemService( - Context.KEYGUARD_SERVICE); - // We should use userHandle not credentialOwnerUserId here, as even if it is unified - // lock, confirm screenlock page will know and show personal challenge, and unlock - // work profile when personal challenge is correct - final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, - userHandle); - if (unlockIntent == null) { - return false; - } - final Intent callBackIntent = new Intent( - ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK); - if (target != null) { - callBackIntent.putExtra(Intent.EXTRA_INTENT, target); - } - callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle); - callBackIntent.setPackage(mContext.getPackageName()); - callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - final PendingIntent pendingIntent = PendingIntent.getBroadcast( - mContext, - 0, - callBackIntent, - PendingIntent.FLAG_CANCEL_CURRENT | - PendingIntent.FLAG_ONE_SHOT | - PendingIntent.FLAG_IMMUTABLE); - // After unlocking the challenge, it will disable quiet mode and run the original - // intentSender - unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender()); - unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - mContext.startActivity(unlockIntent); - } finally { - Binder.restoreCallingIdentity(identity); + // otherwise, we show a profile challenge to trigger decryption of the user + final KeyguardManager km = (KeyguardManager) mContext.getSystemService( + Context.KEYGUARD_SERVICE); + // We should use userHandle not credentialOwnerUserId here, as even if it is unified + // lock, confirm screenlock page will know and show personal challenge, and unlock + // work profile when personal challenge is correct + final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, + userHandle); + if (unlockIntent == null) { + return; } - return false; + final Intent callBackIntent = new Intent( + ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK); + if (target != null) { + callBackIntent.putExtra(Intent.EXTRA_INTENT, target); + } + callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle); + callBackIntent.setPackage(mContext.getPackageName()); + callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, + 0, + callBackIntent, + PendingIntent.FLAG_CANCEL_CURRENT | + PendingIntent.FLAG_ONE_SHOT | + PendingIntent.FLAG_IMMUTABLE); + // After unlocking the challenge, it will disable quiet mode and run the original + // intentSender + unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender()); + unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + mContext.startActivity(unlockIntent); } @Override diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index 21c688907dc1..de723c6701d3 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.ArrayMap; 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 @@ public class VrManagerService extends SystemService @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 7b0ed0d06739..8e916ad3180e 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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final SparseArray<Boolean> mUserRestorecon = new SparseArray<Boolean>(); int mCurrentUserId; + boolean mInAmbientMode; static class WallpaperData { @@ -953,6 +954,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } 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 class WallpaperManagerService extends IWallpaperManager.Stub } } + 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/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 2bda80d99328..163b1600e0bd 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; @@ -1068,8 +1069,11 @@ final class AccessibilityController { continue; } - // If the window is not touchable - ignore. - if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { + // Ignore non-touchable windows, except the split-screen divider, which is + // occasionally non-touchable but still useful for identifying split-screen + // mode. + if (((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) + && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) { continue; } diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp index 39cc95368d71..02ad6c71b586 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 @@ static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) 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 5b9e3a1e70eb..e55d4ea35739 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 android.security.keystore.ParcelableKeyGenParameterSpec; 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 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { 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 bead31fc675e..387818be6480 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return new LockPatternUtils(mContext); } + PasswordBlacklist newPasswordBlacklist(File file) { + return new PasswordBlacklist(file); + } + boolean storageManagerIsFileBasedEncryptionEnabled() { return StorageManager.isFileEncryptedNativeOnly(); } @@ -2589,11 +2606,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + /* @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @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 4a6bee5c7365..f91f959d9938 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.LongSparseArray; 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 @@ final class NetworkLoggingHandler extends Handler { } }; + @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 @@ final class NetworkLoggingHandler extends Handler { 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 @@ final class NetworkLoggingHandler extends Handler { 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 000000000000..6a9b53a0a2df --- /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 7aa628af4621..4310a98d8000 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -26,6 +26,7 @@ import android.content.Intent; 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 @@ public final class SystemServer { // 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 @@ public final class SystemServer { 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 c14f74cf7183..0462b1430496 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 @@ public class AccessibilityServiceConnectionTest { @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 45ecbfb891a5..8853db2a8558 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 @@ public class UiAutomationManagerTest { @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 d168479a6b00..0650acb45c47 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 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi 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 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @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 ca918c6fa81c..4779474faf9b 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 4ee5ba63a58e..8cb0459e042d 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 class MockSystemServices { 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 @@ public class MockSystemServices { 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 a92d1db2616b..c698312eedec 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 000000000000..1b3fc2c1f207 --- /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 000000000000..e20f664d14cf --- /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 c13c779f2934..3012931a7167 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 junit.framework.Assert.fail; + 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 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"; - - @Mock RecoverableKeyStorage mRecoverableKeyStorage; - - @Captor ArgumentCaptor<KeyProtection> mKeyProtectionArgumentCaptor; + 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; private PlatformEncryptionKey mPlatformKey; - private SecretKey mKeyHandle; + private PlatformDecryptionKey mDecryptKey; + private RecoverableKeyStoreDb mRecoverableKeyStoreDb; + private File mDatabaseFile; private RecoverableKeyGenerator mRecoverableKeyGenerator; @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey()); - mKeyHandle = generateKey(); - mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance( - mPlatformKey, mRecoverableKeyStorage); - - when(mRecoverableKeyStorage.loadFromAndroidKeyStore(any())).thenReturn(mKeyHandle); + Context context = InstrumentationRegistry.getTargetContext(); + mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); + mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); + + 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 @@ public class RecoverableKeyGeneratorTest { KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER); keyStore.load(/*param=*/ null); keyStore.deleteEntry(WRAPPING_KEY_ALIAS); - } - - @Test - public void generateAndStoreKey_setsKeyInKeyStore() throws Exception { - mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS); - verify(mRecoverableKeyStorage, times(1)) - .importIntoAndroidKeyStore(eq(TEST_ALIAS), any(), any()); + mRecoverableKeyStoreDb.close(); + mDatabaseFile.delete(); } @Test - public void generateAndStoreKey_storesKeyEnabledForEncryptDecrypt() throws Exception { - mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS); + public void generateAndStoreKey_setsKeyInKeyStore() 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); + assertTrue(keyStore.containsAlias(TEST_ALIAS)); } @Test - public void generateAndStoreKey_storesKeyEnabledForGCM() throws Exception { - mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS); - - KeyProtection keyProtection = getKeyProtectionUsed(); - assertArrayEquals(new String[] { KeyProperties.BLOCK_MODE_GCM }, - keyProtection.getBlockModes()); + public void generateAndStoreKey_storesKeyEnabledForAesGcmNoPaddingEncryptDecrypt() + throws Exception { + mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS); + + 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_storesKeyEnabledForNoPadding() throws Exception { - mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS); - - KeyProtection keyProtection = getKeyProtectionUsed(); - assertArrayEquals(new String[] { KeyProperties.ENCRYPTION_PADDING_NONE }, - keyProtection.getEncryptionPaddings()); + public void generateAndStoreKey_storesKeyDisabledForOtherModes() throws Exception { + mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS); + + KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF); + SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null); + Cipher cipher = Cipher.getInstance(UNSUPPORTED_CIPHER_ALGORITHM); + + 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); - - verify(mRecoverableKeyStorage, times(1)).persistToDisk(eq(TEST_ALIAS), any()); - } - - @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(); + mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS); + + 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); + + // 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 @@ public class RecoverableKeyGeneratorTest { .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 fb4e75e59d8f..000000000000 --- 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/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java new file mode 100644 index 000000000000..35b18b14ad3c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -0,0 +1,186 @@ +/* + * 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 android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN; +import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD; + +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 android.content.Context; +import android.os.RemoteException; +import android.security.recoverablekeystore.KeyDerivationParameters; +import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; +import android.security.recoverablekeystore.RecoverableKeyStoreLoader; +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.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; + +import com.google.common.collect.ImmutableList; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RecoverableKeyStoreManagerTest { + private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; + + private static final String TEST_SESSION_ID = "karlin"; + private static final byte[] TEST_PUBLIC_KEY = new byte[] { + (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, + (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06, + (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x03, + (byte) 0x01, (byte) 0x07, (byte) 0x03, (byte) 0x42, (byte) 0x00, (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}; + private static final byte[] TEST_SALT = getUtf8Bytes("salt"); + private static final byte[] TEST_SECRET = getUtf8Bytes("password1234"); + private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge"); + private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params"); + private static final int TEST_USER_ID = 10009; + private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; + + @Mock private Context mMockContext; + + private RecoverableKeyStoreDb mRecoverableKeyStoreDb; + private File mDatabaseFile; + private RecoverableKeyStoreManager mRecoverableKeyStoreManager; + private RecoverySessionStorage mRecoverySessionStorage; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + Context context = InstrumentationRegistry.getTargetContext(); + mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); + mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); + mRecoverySessionStorage = new RecoverySessionStorage(); + mRecoverableKeyStoreManager = new RecoverableKeyStoreManager( + mMockContext, + mRecoverableKeyStoreDb, + mRecoverySessionStorage); + } + + @After + public void tearDown() { + mRecoverableKeyStoreDb.close(); + mDatabaseFile.delete(); + } + + @Test + public void startRecoverySession_checksPermissionFirst() throws Exception { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + TEST_PUBLIC_KEY, + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), + TEST_USER_ID); + + verify(mMockContext, times(1)).enforceCallingOrSelfPermission( + eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE), + any()); + } + + @Test + public void startRecoverySession_storesTheSessionInfo() throws Exception { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + TEST_PUBLIC_KEY, + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), + TEST_USER_ID); + + assertEquals(1, mRecoverySessionStorage.size()); + RecoverySessionStorage.Entry entry = mRecoverySessionStorage.get( + TEST_USER_ID, TEST_SESSION_ID); + assertArrayEquals(TEST_SECRET, entry.getLskfHash()); + assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length); + } + + @Test + public void startRecoverySession_throwsIfBadNumberOfSecrets() throws Exception { + try { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + TEST_PUBLIC_KEY, + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(), + TEST_USER_ID); + } catch (RemoteException e) { + assertEquals("Only a single KeyStoreRecoveryMetadata is supported", + e.getMessage()); + } + } + + @Test + public void startRecoverySession_throwsIfBadKey() throws Exception { + try { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + getUtf8Bytes("0"), + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), + TEST_USER_ID); + } catch (RemoteException e) { + assertEquals("Not a valid X509 key", + e.getMessage()); + } + } + + private static byte[] getUtf8Bytes(String s) { + return s.getBytes(StandardCharsets.UTF_8); + } +} 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 000000000000..72b69f046b33 --- /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 c056160e7f11..56122a7d6d4c 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 @@ -117,7 +117,8 @@ public class WrappedKeyTest { keysByAlias.put(alias, wrappedKey); Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys( - new PlatformDecryptionKey(GENERATION_ID, platformKey), keysByAlias); + new PlatformDecryptionKey(GENERATION_ID, generateAndroidKeyStoreKey()), + keysByAlias); assertEquals(0, unwrappedKeys.size()); } @@ -161,4 +162,12 @@ public class WrappedKeyTest { .build()); return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); } + + private PlatformDecryptionKey generatePlatformDecryptionKey() throws Exception { + return generatePlatformDecryptionKey(GENERATION_ID); + } + + private PlatformDecryptionKey generatePlatformDecryptionKey(int generationId) throws Exception { + return new PlatformDecryptionKey(generationId, generateAndroidKeyStoreKey()); + } } 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 new file mode 100644 index 000000000000..d5ad9597bf1c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java @@ -0,0 +1,208 @@ +/* + * 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.storage; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import android.content.Context; +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; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RecoverableKeyStoreDbTest { + private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; + + private RecoverableKeyStoreDb mRecoverableKeyStoreDb; + private File mDatabaseFile; + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getTargetContext(); + mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); + mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); + } + + @After + public void tearDown() { + mRecoverableKeyStoreDb.close(); + mDatabaseFile.delete(); + } + + @Test + public void insertKey_replacesOldKey() { + int userId = 12; + String alias = "test"; + WrappedKey oldWrappedKey = new WrappedKey( + getUtf8Bytes("nonce1"), + getUtf8Bytes("keymaterial1"), + /*platformKeyGenerationId=*/ 1); + mRecoverableKeyStoreDb.insertKey( + userId, alias, oldWrappedKey); + byte[] nonce = getUtf8Bytes("nonce2"); + byte[] keyMaterial = getUtf8Bytes("keymaterial2"); + WrappedKey newWrappedKey = new WrappedKey( + nonce, keyMaterial, /*platformKeyGenerationId=*/2); + + mRecoverableKeyStoreDb.insertKey( + 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 + public void getKey_returnsNullIfNoKey() { + WrappedKey key = mRecoverableKeyStoreDb.getKey( + /*userId=*/ 1, /*alias=*/ "hello"); + + assertNull(key); + } + + @Test + public void getKey_returnsInsertedKey() { + int userId = 12; + int generationId = 6; + String alias = "test"; + byte[] nonce = getUtf8Bytes("nonce"); + byte[] keyMaterial = getUtf8Bytes("keymaterial"); + 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 + public void getAllKeys_getsKeysWithUserIdAndGenerationId() { + int userId = 12; + int generationId = 6; + String alias = "test"; + byte[] nonce = getUtf8Bytes("nonce"); + byte[] keyMaterial = getUtf8Bytes("keymaterial"); + WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId); + mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey); + + Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, generationId); + + assertEquals(1, keys.size()); + assertTrue(keys.containsKey(alias)); + 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"), + /*platformKeyGenerationId=*/ 5); + mRecoverableKeyStoreDb.insertKey( + userId, /*alias=*/ "test", wrappedKey); + + Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys( + userId, /*generationId=*/ 7); + + assertTrue(keys.isEmpty()); + } + + @Test + public void getAllKeys_doesNotReturnKeysWithBadUserId() { + int generationId = 12; + WrappedKey wrappedKey = new WrappedKey( + getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial"), generationId); + mRecoverableKeyStoreDb.insertKey( + /*userId=*/ 1, /*alias=*/ "test", wrappedKey); + + Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys( + /*userId=*/ 2, generationId); + + 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/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java new file mode 100644 index 000000000000..6aeff2892b9c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java @@ -0,0 +1,132 @@ +/* + * 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.storage; + +import static junit.framework.Assert.fail; + +import static org.junit.Assert.assertEquals; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RecoverySessionStorageTest { + + private static final String TEST_SESSION_ID = "peter"; + private static final int TEST_USER_ID = 696; + private static final byte[] TEST_LSKF_HASH = getUtf8Bytes("lskf"); + private static final byte[] TEST_KEY_CLAIMANT = getUtf8Bytes("0000111122223333"); + + @Test + public void size_isZeroForEmpty() { + assertEquals(0, new RecoverySessionStorage().size()); + } + + @Test + public void size_incrementsAfterAdd() { + RecoverySessionStorage storage = new RecoverySessionStorage(); + storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry( + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture())); + + assertEquals(1, storage.size()); + } + + @Test + public void size_decrementsAfterRemove() { + RecoverySessionStorage storage = new RecoverySessionStorage(); + storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry( + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture())); + storage.remove(TEST_USER_ID); + + assertEquals(0, storage.size()); + } + + @Test + public void remove_overwritesLskfHashMemory() { + RecoverySessionStorage storage = new RecoverySessionStorage(); + RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + storage.add(TEST_USER_ID, entry); + + storage.remove(TEST_USER_ID); + + assertZeroedOut(entry.getLskfHash()); + } + + @Test + public void remove_overwritesKeyClaimantMemory() { + RecoverySessionStorage storage = new RecoverySessionStorage(); + RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + storage.add(TEST_USER_ID, entry); + + storage.remove(TEST_USER_ID); + + assertZeroedOut(entry.getKeyClaimant()); + } + + @Test + public void destroy_overwritesLskfHashMemory() { + RecoverySessionStorage storage = new RecoverySessionStorage(); + RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + storage.add(TEST_USER_ID, entry); + + storage.destroy(); + + assertZeroedOut(entry.getLskfHash()); + } + + @Test + public void destroy_overwritesKeyClaimantMemory() { + RecoverySessionStorage storage = new RecoverySessionStorage(); + RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + storage.add(TEST_USER_ID, entry); + + storage.destroy(); + + assertZeroedOut(entry.getKeyClaimant()); + } + + private static void assertZeroedOut(byte[] bytes) { + for (byte b : bytes) { + if (b != (byte) 0) { + fail("Bytes were not all zeroed out."); + } + } + } + + private static byte[] lskfHashFixture() { + return Arrays.copyOf(TEST_LSKF_HASH, TEST_LSKF_HASH.length); + } + + private static byte[] keyClaimantFixture() { + return Arrays.copyOf(TEST_KEY_CLAIMANT, TEST_KEY_CLAIMANT.length); + } + + 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 7f48b8e1759a..0c35fcb05da6 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 @@ package com.android.server.net; 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.support.test.uiautomator.UiDevice; 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 @@ public class ConnOnActivityStartTest { 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 @@ public class ConnOnActivityStartTest { } 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 @@ public class ConnOnActivityStartTest { } } + 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 337fd50a88d4..0959df2b78bd 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 @@ public class WindowFrameTests extends WindowTestsBase { 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 b26cc7fd0130..f8d1d03cd7a6 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 @@ public class ConnTestActivity extends Activity { 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 07c860b54413..cdce44872cc0 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 @@ public class UsageStatsService extends SystemService implements 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 c0685f927398..44f55511f940 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.content.PackageMonitor; 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 class VoiceInteractionManagerService extends SystemService { } 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 dedce1702dfe..6a47d05018d6 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1717,6 +1717,13 @@ public class CarrierConfigManager { 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 @@ public class CarrierConfigManager { 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 @@ public class CarrierConfigManager { } /** + * 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 710eff0e0110..a6dbc06647d0 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -19,6 +19,7 @@ package android.telephony; 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 @@ public class SmsMessage { /** * 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 28ae10bf85f5..9e77992d5d5e 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1176,12 +1176,14 @@ public class TelephonyManager { } /** - * 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 @@ public class TelephonyManager { /** {@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 @@ public class TelephonyManager { * @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 */ + /** + * @hide + * @deprecated use {@link #setUserMobileDataEnabled(boolean)} instead. + */ @SystemApi + @Deprecated @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); - } + setUserMobileDataEnabled(subId, enable); } - /** - * @deprecated use {@link #isDataEnabled()} instead. + * @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 @@ public class TelephonyManager { * @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 @@ public class TelephonyManager { } 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 2507cfee0b56..973df316d280 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -168,12 +168,10 @@ public class EuiccManager { 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 class EuiccManager { 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 @@ public class EuiccManager { return null; } try { - return mController.getEid(); + return getIEuiccController().getEid(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -232,7 +230,7 @@ public class EuiccManager { return; } try { - mController.downloadSubscription(subscription, switchAfterDownload, + getIEuiccController().downloadSubscription(subscription, switchAfterDownload, mContext.getOpPackageName(), callbackIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -296,7 +294,7 @@ public class EuiccManager { return; } try { - mController.continueOperation(resolutionIntent, resolutionExtras); + getIEuiccController().continueOperation(resolutionIntent, resolutionExtras); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -328,7 +326,7 @@ public class EuiccManager { return; } try { - mController.getDownloadableSubscriptionMetadata( + getIEuiccController().getDownloadableSubscriptionMetadata( subscription, mContext.getOpPackageName(), callbackIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -358,7 +356,7 @@ public class EuiccManager { return; } try { - mController.getDefaultDownloadableSubscriptionList( + getIEuiccController().getDefaultDownloadableSubscriptionList( mContext.getOpPackageName(), callbackIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -377,7 +375,7 @@ public class EuiccManager { return null; } try { - return mController.getEuiccInfo(); + return getIEuiccController().getEuiccInfo(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -402,7 +400,7 @@ public class EuiccManager { return; } try { - mController.deleteSubscription( + getIEuiccController().deleteSubscription( subscriptionId, mContext.getOpPackageName(), callbackIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -429,7 +427,7 @@ public class EuiccManager { return; } try { - mController.switchToSubscription( + getIEuiccController().switchToSubscription( subscriptionId, mContext.getOpPackageName(), callbackIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -455,7 +453,8 @@ public class EuiccManager { return; } try { - mController.updateSubscriptionNickname(subscriptionId, nickname, callbackIntent); + getIEuiccController().updateSubscriptionNickname( + subscriptionId, nickname, callbackIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -477,7 +476,7 @@ public class EuiccManager { return; } try { - mController.eraseSubscriptions(callbackIntent); + getIEuiccController().eraseSubscriptions(callbackIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -507,7 +506,7 @@ public class EuiccManager { return; } try { - mController.retainSubscriptionsForFactoryReset(callbackIntent); + getIEuiccController().retainSubscriptionsForFactoryReset(callbackIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -520,4 +519,8 @@ public class EuiccManager { // 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 a9f8f45f6c0d..8ea53a594cc2 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -870,16 +870,35 @@ interface ITelephony { * * @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 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. * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN * @param callingPackage The package making the call. diff --git a/test-mock/Android.mk b/test-mock/Android.mk index a761a070fa26..7926a77ccb01 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_SOURCE_FILES_ALL_GENERATED := true 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 @@ update-android-test-mock-api: $(ANDROID_TEST_MOCK_OUTPUT_API_FILE) | $(ACP) @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 3aa350bd9b77..48a1f80f3379 100644 --- a/test-mock/api/android-test-mock-current.txt +++ b/test-mock/api/android-test-mock-current.txt @@ -10,7 +10,6 @@ package android.test.mock { 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 @@ package android.test.mock { 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 @@ package android.test.mock { 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 @@ package android.test.mock { 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 @@ package android.test.mock { 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 @@ package android.test.mock { 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 @@ package android.test.mock { 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 @@ package android.test.mock { 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 @@ package android.test.mock { 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 @@ package android.test.mock { 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 5b358cfdbf59..bd109a887933 100644 --- a/test-mock/api/android-test-mock-removed.txt +++ b/test-mock/api/android-test-mock-removed.txt @@ -8,6 +8,7 @@ package android.test.mock { 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 000000000000..20401a50b6a2 --- /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 000000000000..e69de29bb2d1 --- /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 000000000000..3a97117f3ea1 --- /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 09739e5e074a..917293fa266b 100644 --- a/tests/AppLaunch/Android.mk +++ b/tests/AppLaunch/Android.mk @@ -13,6 +13,8 @@ LOCAL_JAVA_LIBRARIES := android.test.base android.test.runner 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 73181ec36e17..b69e3e4fdd4b 100644 --- a/tests/Internal/Android.mk +++ b/tests/Internal/Android.mk @@ -14,6 +14,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := junit \ 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 a2c95fbbfc0b..e5a56949fe4e 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 000000000000..dbb0e4761cba --- /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 000000000000..9d26270fb96a --- /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 000000000000..f7ce2c72d37e --- /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 000000000000..084c036bea26 --- /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 8683c12e192e..5d1e10eab572 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.assertTrue; 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.IpSecTransform; 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 java.util.List; 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 @@ public class IpSecServiceTest { 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 543c652dc24a..a76616f179dd 100644 --- a/tests/utils/testutils/Android.mk +++ b/tests/utils/testutils/Android.mk @@ -24,9 +24,12 @@ LOCAL_MODULE_TAG := tests 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 0f6fb50aba48..5831875680ac 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -294,36 +294,42 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& 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 3c1ee4c0cdba..6209a04c3c2d 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -29,6 +29,7 @@ namespace aapt { 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 4cc60a8dbb07..24b28dd7d970 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 @@ struct ParsedResource { 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 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed 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->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->value) { + 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 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, // 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 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser, 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 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, 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 @@ bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource 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 @@ bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out return false; } - out_resource->symbol_state = SymbolState::kPrivate; + out_resource->visibility_level = Visibility::Level::kPrivate; return true; } @@ -920,8 +938,12 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource 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 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource 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 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, 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 9a5cd3edb47f..618c8ed4afd1 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::IsNull; using ::testing::NotNull; using ::testing::Pointee; using ::testing::SizeIs; +using ::testing::StrEq; namespace aapt { @@ -482,7 +483,7 @@ TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { 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 @@ TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { 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 @@ TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) 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 @@ TEST_F(ResourceParserTest, ParseOverlayableTagWithSystemPolicy) { <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 0304e21698df..3172892d7172 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 @@ static bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const Stri 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 @@ ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) { 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 @@ std::vector<ResourceConfigValue*> ResourceEntry::FindValuesIf( 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 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist 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 @@ bool ResourceTable::AddResource(const ResourceNameRef& name, 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 @@ bool ResourceTable::AddFileReference(const ResourceNameRef& name, 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 @@ bool ResourceTable::AddFileReferenceImpl(const ResourceNameRef& name, 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 @@ bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceI 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 @@ bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name, const Resour 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; +} + +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; } -Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) { +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 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const { 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 d5db67e77f51..eaa2d0b8af7d 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, + }; -/** - * The Public status of a resource. - */ -struct Symbol { - SymbolState state = SymbolState::kUndefined; + Level level = Level::kUndefined; Source source; + std::string comment; +}; - // Whether this entry (originating from an overlay) can be added as a new resource. - bool allow_new = false; +// Represents <add-resource> in an overlay. +struct AllowNew { + Source source; + std::string comment; +}; +// 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 ResourceConfigValue { */ 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; + + Maybe<AllowNew> allow_new; - /** - * The resource's values for each configuration. - */ + 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 @@ class ResourceEntry { 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 ResourceTableType { 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 @@ class ResourceTablePackage { 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 @@ class ResourceTable { 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); - - /** - * 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); - - bool AddResourceAllowMangled(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 SetSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId& res_id, - const Symbol& symbol, 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 AddResourceMangled(const ResourceNameRef& name, 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 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 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 @@ class ResourceTable { 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 @@ class ResourceTable { 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 @@ class ResourceTable { 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 2a3c131f7b4b..eb75f947e0be 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, FailToAddResourceWithBadName) { 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 @@ TEST(ResourceTableTest, ProductVaryingValues) { 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 7e7c86d53d24..8552195d338d 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -93,11 +93,10 @@ message Type { 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 @@ message SymbolStatus { 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 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; - // 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; + // 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 @@ message Entry { // 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 83512b9126da..7c1e96e88fee 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 @@ static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, 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 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, 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 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, 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 @@ int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { 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 fc1f1d6342ae..12113ed8a48a 100644 --- a/tools/aapt2/cmd/Diff.cpp +++ b/tools/aapt2/cmd/Diff.cpp @@ -75,14 +75,14 @@ static void EmitDiffLine(const Source& source, const StringPiece& message) { 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 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a, 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 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a, 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 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a, 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 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a, 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 bc7f5a86b043..3d2fb556cf59 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -69,15 +69,13 @@ static void DumpCompiledFile(const ResourceFile& file, const Source& source, off 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 @@ class DumpContext : public IAaptContext { // 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 @@ int Dump(const std::vector<StringPiece>& args) { 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 d782de55f66a..72e07dc23725 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -631,9 +631,9 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv 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 @@ class LinkCommand { 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 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { &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 d8bb9992c152..9c76119f9504 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -377,44 +377,10 @@ int Optimize(const std::vector<StringPiece>& args) { } 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 @@ int Optimize(const std::vector<StringPiece>& args) { 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 ebc523f096db..eabeb47fccec 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -49,13 +49,15 @@ using ::aapt::configuration::AndroidSdk; 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 @@ bool CopyXmlReferences(const Maybe<std::string>& name, const Group<T>& groups, 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 @@ xml::XmlNodeAction::ActionFuncWithDiag Bind(configuration::PostProcessingConfigu }; } -/** 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 @@ Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact, 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 @@ Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact, 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 @@ Maybe<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format, return {}; } - if (!ReplacePlaceholder("${sdk}", android_sdk_group, &result, diag)) { + if (!ReplacePlaceholder("${sdk}", android_sdk, &result, diag)) { return {}; } @@ -414,47 +419,37 @@ Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) 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 @@ namespace handler { 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 @@ bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_eleme << attr.value); } } - - artifact.version = (version) ? version.value() : current_version + 1; - config->artifacts.push_back(artifact); return true; }; @@ -523,9 +509,19 @@ bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_eleme 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 @@ bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_eleme 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 @@ bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* 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 @@ bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_el 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 @@ bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_el 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()}; - } - } + if (entry.min_sdk_version == -1) { + diag->Error(DiagMessage() << "android-sdk is missing minSdkVersion attribute"); + valid = false; + } - config->android_sdk_groups[label] = entry; - if (found) { - 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; } - found = true; + entry.manifest = {AndroidManifest()}; } } + config->android_sdks[entry.label] = entry; return valid; }; @@ -691,7 +728,7 @@ bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root 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 @@ bool DeviceFeatureGroupTagHandler(PostProcessingConfiguration* config, Element* 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 ca5891025c92..7f1d4453f9b3 100644 --- a/tools/aapt2/configuration/ConfigurationParser.h +++ b/tools/aapt2/configuration/ConfigurationParser.h @@ -71,7 +71,8 @@ struct AndroidManifest { }; 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 @@ struct OutputArtifact { 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 @@ class ConfigurationParser { 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 @@ class ConfigurationParser { * 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 @@ class ConfigurationParser { 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 7657ebd70a8b..a583057427e6 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 @@ struct ConfiguredArtifact { /** 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 ScreenDensityGroupTagHandler(configuration::PostProcessingConfiguration* co 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 3f356d78bbfe..0329846a5bb5 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -30,11 +30,33 @@ namespace aapt { 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::AndroidManifest; 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 ::aapt::xml::NodeCast; 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 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-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 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-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 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> <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 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> 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 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> 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 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> class ConfigurationParserTest : public ConfigurationParser, public ::testing::Test { public: - ConfigurationParserTest() : ConfigurationParser("") {} + ConfigurationParserTest() : ConfigurationParser("", "config.xml") { + } protected: StdErrDiagnostics diag_; @@ -145,8 +180,31 @@ TEST_F(ConfigurationParserTest, ForPath_NoFile) { 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 @@ TEST_F(ConfigurationParserTest, ValidateFile) { 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_F(ConfigurationParserTest, ValidateFile) { 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 @@ TEST_F(ConfigurationParserTest, ValidateFile) { 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_)); - - 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->root.get()), &diag_)); - ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_)); - EXPECT_THAT(config.artifacts, SizeIs(2ul)); - EXPECT_EQ(2, config.artifacts.back().version); - } + EXPECT_THAT(config.artifacts, SizeIs(1ul)); - { - // 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 @@ TEST_F(ConfigurationParserTest, AbiGroupAction) { 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 @@ TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) { 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 @@ TEST_F(ConfigurationParserTest, LocaleGroupAction) { 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 @@ TEST_F(ConfigurationParserTest, LocaleGroupAction) { 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) { 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 @@ TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) { 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 @@ TEST_F(ConfigurationParserTest, GlTextureGroupAction) { 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 @@ TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) { 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 @@ TEST(ArtifactTest, Complex) { 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 134153a017f8..fb2f49bae7b7 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 ce31e61b2d62..d8aba09e836d 100644 --- a/tools/aapt2/configuration/example/config.xml +++ b/tools/aapt2/configuration/example/config.xml @@ -1,6 +1,41 @@ <?xml version="1.0" encoding="utf-8" ?> <post-process xmlns="http://schemas.android.com/tools/aapt"> - <groups> + <artifacts> + <artifact-format> + ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release + </artifact-format> + + <artifact + abi-group="arm" + screen-density-group="large" + locale-group="europe" + android-sdk-group="19" + gl-texture-group="dxt1" + device-feature-group="low-latency"/> + + <artifact + abi-group="other" + screen-density-group="alldpi" + locale-group="north-america" + android-sdk-group="19" + gl-texture-group="dxt1" + 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> @@ -10,7 +45,9 @@ <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> @@ -25,7 +62,9 @@ <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"/> @@ -42,49 +81,20 @@ <locale-group label="all"> <locale compressed="true"/> </locale-group> + </locale-groups> - <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-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> - - <artifacts> - <artifact-format> - ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release - </artifact-format> + </device-feature-groups> - <artifact - abi-group="arm" - screen-density-group="large" - locale-group="europe" - android-sdk-group="19" - gl-texture-group="dxt1" - device-feature-group="low-latency"/> - - <artifact - abi-group="other" - screen-density-group="alldpi" - locale-group="north-america" - android-sdk-group="19" - gl-texture-group="dxt1" - device-feature-group="low-latency"/> - - </artifacts> </post-process> diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index 5078678e08a3..8d079ffecb63 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -223,7 +223,7 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { break; case android::RES_TABLE_TYPE_SPEC_TYPE: - if (!ParseTypeSpec(parser.chunk())) { + if (!ParseTypeSpec(package, parser.chunk())) { return false; } break; @@ -260,7 +260,8 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { 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 @@ bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) { 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 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, 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 052f806e3b95..a1f9f83edfb6 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.h +++ b/tools/aapt2/format/binary/BinaryResourceParser.h @@ -50,7 +50,7 @@ class BinaryResourceParser { 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 @@ class BinaryResourceParser { // 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 a3034df91d82..24a4112e181d 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -283,7 +283,7 @@ class PackageFlattener { 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 @@ class PackageFlattener { // 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 f0b80d22a9b4..51ccdc7359b2 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 @@ static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries( 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 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { 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 0f0bce8bf5a7..3d6975d8d559 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -358,16 +358,16 @@ static void DeserializeSourceFromPb(const pb::Source& pb_source, const ResString 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 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr } // 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 97ce01a9de13..78f12814389d 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -43,16 +43,16 @@ void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* } } -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 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table } 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 9649a4de2fe4..d7f83fd9ecdc 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -44,14 +44,15 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { .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(ProtoSerializeTest, SerializeSinglePackage) { 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 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { 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 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { 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 8c8c2549609a..6b07b1e96261 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -191,14 +191,14 @@ JavaClassGenerator::JavaClassGenerator(IAaptContext* context, 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 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso 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 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso 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 4992f077c566..853120b3cb98 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -82,7 +82,7 @@ class JavaClassGenerator { 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 02f4cb14eb41..5beb594bd92f 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -139,8 +139,8 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { .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 a68df1dbc998..da05dc328f7f 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -430,7 +430,10 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { 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 f5715f605b04..0caa52eaf650 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -57,6 +57,11 @@ struct ManifestFixerOptions { // 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 1320dcd2a170..c6f895bb52dd 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -112,7 +112,9 @@ TEST_F(ManifestFixerTest, AllowMetaData) { } 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, UseDefaultSdkVersionsIfNonePresent) { } 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 @@ TEST_F(ManifestFixerTest, InsertCompileSdkVersions) { 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 eee4b60c1769..675b02a7e161 100644 --- a/tools/aapt2/link/PrivateAttributeMover.cpp +++ b/tools/aapt2/link/PrivateAttributeMover.cpp @@ -62,7 +62,7 @@ bool PrivateAttributeMover::Consume(IAaptContext* context, ResourceTable* table) 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 @@ bool PrivateAttributeMover::Consume(IAaptContext* context, ResourceTable* table) 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 7fcf6e7ab5cd..168234b36e4a 100644 --- a/tools/aapt2/link/PrivateAttributeMover_test.cpp +++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp @@ -30,9 +30,9 @@ TEST(PrivateAttributeMoverTest, MovePrivateAttributes) { .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 @@ TEST(PrivateAttributeMoverTest, DoNotCreatePrivateAttrsIfNoneExist) { 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 ad7d8b65350d..b8f880427c71 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -363,8 +363,8 @@ bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) { 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 58d0607ed7b3..e819f51a5634 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -83,44 +83,58 @@ bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_n 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 @@ bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table, } 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 @@ bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table, 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 6aab8ded24a5..34461c6b467d 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -182,14 +182,12 @@ TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) { 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 @@ TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) { 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 @@ TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) { 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 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlay) { 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 16898d6f3e03..991faadcafc4 100644 --- a/tools/aapt2/optimize/MultiApkGenerator.cpp +++ b/tools/aapt2/optimize/MultiApkGenerator.cpp @@ -120,8 +120,6 @@ MultiApkGenerator::MultiApkGenerator(LoadedApk* apk, IAaptContext* context) } 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 @@ std::unique_ptr<ResourceTable> MultiApkGenerator::FilterTable(IAaptContext* cont 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 @@ bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact, 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 0cfc0bdfaaa6..3cae0e8ae462 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -190,7 +190,7 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( 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 9d49ca6c0aa9..e99174302d26 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -233,13 +233,13 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { 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 88897a8afac3..495a48a830f7 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 @@ ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, 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 @@ ArtifactBuilder& ArtifactBuilder::SetName(const std::string& name) { 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 @@ configuration::OutputArtifact ArtifactBuilder::Build() { 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 2f83b78ffb2a..0d7451b5a6da 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -68,7 +68,7 @@ class ResourceTableBuilder { 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 @@ class ArtifactBuilder { 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 @@ class ArtifactBuilder { 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 602a902bfc3e..cb844f085ecc 100644 --- a/tools/aapt2/xml/XmlActionExecutor.cpp +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -66,7 +66,7 @@ bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPi 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 @@ bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPi 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 df70100e882a..f689b2a3eaa8 100644 --- a/tools/aapt2/xml/XmlActionExecutor.h +++ b/tools/aapt2/xml/XmlActionExecutor.h @@ -34,10 +34,15 @@ enum class XmlActionExecutorPolicy { // 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 4e9391db9a5b..1121eadca967 100644 --- a/tools/streaming_proto/Android.bp +++ b/tools/streaming_proto/Android.bp @@ -32,26 +32,6 @@ cc_defaults { 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 @@ cc_binary_host { ], 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 745b3dc51181..d6b9d81137ac 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 android::stream_proto; 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 @@ write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& ind << " = " << 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 @@ write_field(stringstream& text, const FieldDescriptorProto& field, const string& 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 @@ write_message(stringstream& text, const DescriptorProto& message, const string& // 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_message(stringstream& text, const DescriptorProto& message, const string& 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 @@ write_header_file(CodeGeneratorResponse* response, const FileDescriptorProto& fi 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 e9b24a8a5824..000000000000 --- 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; -} |