Merge "Add default alarm back into AudioPackage"
diff --git a/Android.bp b/Android.bp
index e23d9c2..2dcbc92 100644
--- a/Android.bp
+++ b/Android.bp
@@ -473,6 +473,7 @@
"telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl",
"telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl",
"telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl",
+ "telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl",
"telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl",
"telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl",
"telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl",
@@ -493,13 +494,11 @@
"telephony/java/com/android/ims/internal/IImsService.aidl",
"telephony/java/com/android/ims/internal/IImsServiceController.aidl",
"telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl",
- "telephony/java/com/android/ims/internal/IImsSmsFeature.aidl",
"telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl",
"telephony/java/com/android/ims/internal/IImsUt.aidl",
"telephony/java/com/android/ims/internal/IImsUtListener.aidl",
"telephony/java/com/android/ims/internal/IImsVideoCallCallback.aidl",
"telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl",
- "telephony/java/com/android/ims/internal/ISmsListener.aidl",
"telephony/java/com/android/ims/internal/uce/uceservice/IUceService.aidl",
"telephony/java/com/android/ims/internal/uce/uceservice/IUceListener.aidl",
"telephony/java/com/android/ims/internal/uce/options/IOptionsService.aidl",
diff --git a/Android.mk b/Android.mk
index 995630b..1f37326 100644
--- a/Android.mk
+++ b/Android.mk
@@ -277,9 +277,6 @@
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)
-
framework_base_android_test_runner_src_files := \
$(call all-java-files-under, test-runner/src/junit)
@@ -311,6 +308,7 @@
$(files_to_check_apis) \
$(call find-other-java-files,\
test-base/src \
+ test-mock/src \
test-runner/src)
# These are relative to frameworks/base
@@ -331,7 +329,6 @@
# 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) \
$(common_src_files) \
@@ -359,7 +356,6 @@
icu4j \
framework \
voip-common \
- android.test.mock \
# Platform docs can refer to Support Library APIs, but we don't actually build
# them as part of the docs target, so we need to include them on the classpath.
diff --git a/api/current.txt b/api/current.txt
index 689f4d4..47a338b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6648,6 +6648,7 @@
public static final class SecurityLog.SecurityEvent implements android.os.Parcelable {
method public int describeContents();
method public java.lang.Object getData();
+ method public long getId();
method public int getTag();
method public long getTimeNanos();
method public void writeToParcel(android.os.Parcel, int);
@@ -7035,10 +7036,12 @@
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";
@@ -8226,7 +8229,7 @@
method public void onAppStatusChanged(android.bluetooth.BluetoothDevice, boolean);
method public void onConnectionStateChanged(android.bluetooth.BluetoothDevice, int);
method public void onGetReport(android.bluetooth.BluetoothDevice, byte, byte, int);
- method public void onIntrData(android.bluetooth.BluetoothDevice, byte, byte[]);
+ method public void onInterruptData(android.bluetooth.BluetoothDevice, byte, byte[]);
method public void onSetProtocol(android.bluetooth.BluetoothDevice, byte);
method public void onSetReport(android.bluetooth.BluetoothDevice, byte, byte, byte[]);
method public void onVirtualCableUnplug(android.bluetooth.BluetoothDevice);
@@ -11089,6 +11092,7 @@
field public static final java.lang.String FEATURE_TELEPHONY = "android.hardware.telephony";
field public static final java.lang.String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
field public static final java.lang.String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+ field public static final java.lang.String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
field public static final deprecated java.lang.String FEATURE_TELEVISION = "android.hardware.type.television";
field public static final java.lang.String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
field public static final java.lang.String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
@@ -21641,7 +21645,9 @@
method public int getRingerMode();
method public deprecated int getRouting(int);
method public int getStreamMaxVolume(int);
+ method public int getStreamMinVolume(int);
method public int getStreamVolume(int);
+ method public float getStreamVolumeDb(int, int, int);
method public deprecated int getVibrateSetting(int);
method public deprecated boolean isBluetoothA2dpOn();
method public boolean isBluetoothScoAvailableOffCall();
@@ -26224,7 +26230,7 @@
method public static android.net.MacAddress fromString(java.lang.String);
method public boolean isLocallyAssigned();
method public byte[] toByteArray();
- method public java.lang.String toSafeString();
+ method public java.lang.String toOuiString();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.net.MacAddress BROADCAST_ADDRESS;
field public static final android.os.Parcelable.Creator<android.net.MacAddress> CREATOR;
@@ -32262,6 +32268,7 @@
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";
@@ -33783,6 +33790,7 @@
field public static final java.lang.String FEATURES = "features";
field public static final int FEATURES_HD_CALL = 4; // 0x4
field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2
+ field public static final int FEATURES_RTT = 16; // 0x10
field public static final int FEATURES_VIDEO = 1; // 0x1
field public static final int FEATURES_WIFI = 8; // 0x8
field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
@@ -40677,19 +40685,19 @@
}
public class MbmsDownloadSession implements java.lang.AutoCloseable {
- method public void cancelDownload(android.telephony.mbms.DownloadRequest);
+ method public int cancelDownload(android.telephony.mbms.DownloadRequest);
method public void close();
method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, android.os.Handler);
method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, int, android.os.Handler);
- method public void download(android.telephony.mbms.DownloadRequest);
+ method public int download(android.telephony.mbms.DownloadRequest);
method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo);
method public java.io.File getTempFileRootDirectory();
method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads();
- method public void registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler);
+ method public int registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler);
method public void requestUpdateFileServices(java.util.List<java.lang.String>);
method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest);
method public void setTempFileRootDirectory(java.io.File);
- method public void unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback);
+ method public int unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback);
field public static final java.lang.String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
field public static final java.lang.String EXTRA_MBMS_COMPLETED_FILE_URI = "android.telephony.extra.MBMS_COMPLETED_FILE_URI";
field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_REQUEST = "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
@@ -40737,6 +40745,34 @@
field public static final int UNKNOWN_RSSI = 99; // 0x63
}
+ public class NetworkScan {
+ method public void stop() throws android.os.RemoteException;
+ field public static final int ERROR_INTERRUPTED = 10002; // 0x2712
+ field public static final int ERROR_INVALID_SCAN = 2; // 0x2
+ field public static final int ERROR_INVALID_SCANID = 10001; // 0x2711
+ field public static final int ERROR_MODEM_ERROR = 1; // 0x1
+ field public static final int ERROR_MODEM_UNAVAILABLE = 3; // 0x3
+ field public static final int ERROR_RADIO_INTERFACE_ERROR = 10000; // 0x2710
+ field public static final int ERROR_UNSUPPORTED = 4; // 0x4
+ field public static final int SUCCESS = 0; // 0x0
+ }
+
+ public final class NetworkScanRequest implements android.os.Parcelable {
+ ctor public NetworkScanRequest(int, android.telephony.RadioAccessSpecifier[], int, int, boolean, int, java.util.ArrayList<java.lang.String>);
+ method public int describeContents();
+ method public boolean getIncrementalResults();
+ method public int getIncrementalResultsPeriodicity();
+ method public int getMaxSearchTime();
+ method public java.util.ArrayList<java.lang.String> getPlmns();
+ method public int getScanType();
+ method public int getSearchPeriodicity();
+ method public android.telephony.RadioAccessSpecifier[] getSpecifiers();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.telephony.NetworkScanRequest> CREATOR;
+ field public static final int SCAN_TYPE_ONE_SHOT = 0; // 0x0
+ field public static final int SCAN_TYPE_PERIODIC = 1; // 0x1
+ }
+
public class PhoneNumberFormattingTextWatcher implements android.text.TextWatcher {
ctor public PhoneNumberFormattingTextWatcher();
ctor public PhoneNumberFormattingTextWatcher(java.lang.String);
@@ -40829,6 +40865,121 @@
field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
}
+ public final class RadioAccessSpecifier implements android.os.Parcelable {
+ ctor public RadioAccessSpecifier(int, int[], int[]);
+ method public int describeContents();
+ method public int[] getBands();
+ method public int[] getChannels();
+ method public int getRadioAccessNetwork();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.telephony.RadioAccessSpecifier> CREATOR;
+ }
+
+ public final class RadioNetworkConstants {
+ ctor public RadioNetworkConstants();
+ }
+
+ public static final class RadioNetworkConstants.EutranBands {
+ ctor public RadioNetworkConstants.EutranBands();
+ field public static final int BAND_1 = 1; // 0x1
+ field public static final int BAND_10 = 10; // 0xa
+ field public static final int BAND_11 = 11; // 0xb
+ field public static final int BAND_12 = 12; // 0xc
+ field public static final int BAND_13 = 13; // 0xd
+ field public static final int BAND_14 = 14; // 0xe
+ field public static final int BAND_17 = 17; // 0x11
+ field public static final int BAND_18 = 18; // 0x12
+ field public static final int BAND_19 = 19; // 0x13
+ field public static final int BAND_2 = 2; // 0x2
+ field public static final int BAND_20 = 20; // 0x14
+ field public static final int BAND_21 = 21; // 0x15
+ field public static final int BAND_22 = 22; // 0x16
+ field public static final int BAND_23 = 23; // 0x17
+ field public static final int BAND_24 = 24; // 0x18
+ field public static final int BAND_25 = 25; // 0x19
+ field public static final int BAND_26 = 26; // 0x1a
+ field public static final int BAND_27 = 27; // 0x1b
+ field public static final int BAND_28 = 28; // 0x1c
+ field public static final int BAND_3 = 3; // 0x3
+ field public static final int BAND_30 = 30; // 0x1e
+ field public static final int BAND_31 = 31; // 0x1f
+ field public static final int BAND_33 = 33; // 0x21
+ field public static final int BAND_34 = 34; // 0x22
+ field public static final int BAND_35 = 35; // 0x23
+ field public static final int BAND_36 = 36; // 0x24
+ field public static final int BAND_37 = 37; // 0x25
+ field public static final int BAND_38 = 38; // 0x26
+ field public static final int BAND_39 = 39; // 0x27
+ field public static final int BAND_4 = 4; // 0x4
+ field public static final int BAND_40 = 40; // 0x28
+ field public static final int BAND_41 = 41; // 0x29
+ field public static final int BAND_42 = 42; // 0x2a
+ field public static final int BAND_43 = 43; // 0x2b
+ field public static final int BAND_44 = 44; // 0x2c
+ field public static final int BAND_45 = 45; // 0x2d
+ field public static final int BAND_46 = 46; // 0x2e
+ field public static final int BAND_47 = 47; // 0x2f
+ field public static final int BAND_48 = 48; // 0x30
+ field public static final int BAND_5 = 5; // 0x5
+ field public static final int BAND_6 = 6; // 0x6
+ field public static final int BAND_65 = 65; // 0x41
+ field public static final int BAND_66 = 66; // 0x42
+ field public static final int BAND_68 = 68; // 0x44
+ field public static final int BAND_7 = 7; // 0x7
+ field public static final int BAND_70 = 70; // 0x46
+ field public static final int BAND_8 = 8; // 0x8
+ field public static final int BAND_9 = 9; // 0x9
+ }
+
+ public static final class RadioNetworkConstants.GeranBands {
+ ctor public RadioNetworkConstants.GeranBands();
+ field public static final int BAND_450 = 3; // 0x3
+ field public static final int BAND_480 = 4; // 0x4
+ field public static final int BAND_710 = 5; // 0x5
+ field public static final int BAND_750 = 6; // 0x6
+ field public static final int BAND_850 = 8; // 0x8
+ field public static final int BAND_DCS1800 = 12; // 0xc
+ field public static final int BAND_E900 = 10; // 0xa
+ field public static final int BAND_ER900 = 14; // 0xe
+ field public static final int BAND_P900 = 9; // 0x9
+ field public static final int BAND_PCS1900 = 13; // 0xd
+ field public static final int BAND_R900 = 11; // 0xb
+ field public static final int BAND_T380 = 1; // 0x1
+ field public static final int BAND_T410 = 2; // 0x2
+ field public static final int BAND_T810 = 7; // 0x7
+ }
+
+ public static final class RadioNetworkConstants.RadioAccessNetworks {
+ ctor public RadioNetworkConstants.RadioAccessNetworks();
+ field public static final int EUTRAN = 3; // 0x3
+ field public static final int GERAN = 1; // 0x1
+ field public static final int UTRAN = 2; // 0x2
+ }
+
+ public static final class RadioNetworkConstants.UtranBands {
+ ctor public RadioNetworkConstants.UtranBands();
+ field public static final int BAND_1 = 1; // 0x1
+ field public static final int BAND_10 = 10; // 0xa
+ field public static final int BAND_11 = 11; // 0xb
+ field public static final int BAND_12 = 12; // 0xc
+ field public static final int BAND_13 = 13; // 0xd
+ field public static final int BAND_14 = 14; // 0xe
+ field public static final int BAND_19 = 19; // 0x13
+ field public static final int BAND_2 = 2; // 0x2
+ field public static final int BAND_20 = 20; // 0x14
+ field public static final int BAND_21 = 21; // 0x15
+ field public static final int BAND_22 = 22; // 0x16
+ field public static final int BAND_25 = 25; // 0x19
+ field public static final int BAND_26 = 26; // 0x1a
+ field public static final int BAND_3 = 3; // 0x3
+ field public static final int BAND_4 = 4; // 0x4
+ field public static final int BAND_5 = 5; // 0x5
+ field public static final int BAND_6 = 6; // 0x6
+ field public static final int BAND_7 = 7; // 0x7
+ field public static final int BAND_8 = 8; // 0x8
+ field public static final int BAND_9 = 9; // 0x9
+ }
+
public class ServiceState implements android.os.Parcelable {
ctor public ServiceState();
ctor public ServiceState(android.telephony.ServiceState);
@@ -41113,12 +41264,15 @@
method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
method public boolean isWorldPhone();
method public void listen(android.telephony.PhoneStateListener, int);
+ method public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, android.telephony.TelephonyScanManager.NetworkScanCallback);
method public void sendDialerSpecialCode(java.lang.String);
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 deprecated void setDataEnabled(boolean);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
+ method public void setNetworkSelectionModeAutomatic();
+ method public boolean setNetworkSelectionModeManual(java.lang.String, boolean);
method public boolean setOperatorBrandOverride(java.lang.String);
method public boolean setPreferredNetworkTypeToGlobal();
method public void setUserMobileDataEnabled(boolean);
@@ -41216,6 +41370,17 @@
method public void onReceiveUssdResponseFailed(android.telephony.TelephonyManager, java.lang.String, int);
}
+ public final class TelephonyScanManager {
+ ctor public TelephonyScanManager();
+ }
+
+ public static abstract class TelephonyScanManager.NetworkScanCallback {
+ ctor public TelephonyScanManager.NetworkScanCallback();
+ method public void onComplete();
+ method public void onError(int);
+ method public void onResults(java.util.List<android.telephony.CellInfo>);
+ }
+
public abstract class VisualVoicemailService extends android.app.Service {
ctor public VisualVoicemailService();
method public android.os.IBinder onBind(android.content.Intent);
@@ -41517,299 +41682,6 @@
}
-package android.test.mock {
-
- public deprecated class MockApplication extends android.app.Application {
- ctor public MockApplication();
- }
-
- public class MockContentProvider extends android.content.ContentProvider {
- ctor protected MockContentProvider();
- ctor public MockContentProvider(android.content.Context);
- 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 java.lang.String getType(android.net.Uri);
- method public android.net.Uri insert(android.net.Uri, android.content.ContentValues);
- method public boolean onCreate();
- method public android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle);
- method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
- method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
- }
-
- public class MockContentResolver extends android.content.ContentResolver {
- ctor public MockContentResolver();
- ctor public MockContentResolver(android.content.Context);
- method public void addProvider(java.lang.String, android.content.ContentProvider);
- }
-
- public class MockContext extends android.content.Context {
- ctor public MockContext();
- method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
- 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 checkSelfPermission(java.lang.String);
- method public int checkUriPermission(android.net.Uri, int, int, int);
- 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 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 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 java.lang.String[] databaseList();
- method public boolean deleteDatabase(java.lang.String);
- method public boolean deleteFile(java.lang.String);
- method public boolean deleteSharedPreferences(java.lang.String);
- method public void enforceCallingOrSelfPermission(java.lang.String, java.lang.String);
- method public void enforceCallingOrSelfUriPermission(android.net.Uri, int, java.lang.String);
- method public void enforceCallingPermission(java.lang.String, java.lang.String);
- method public void enforceCallingUriPermission(android.net.Uri, int, java.lang.String);
- method public void enforcePermission(java.lang.String, int, int, java.lang.String);
- method public void enforceUriPermission(android.net.Uri, int, int, int, java.lang.String);
- method public void enforceUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int, java.lang.String);
- method public java.lang.String[] fileList();
- method public android.content.Context getApplicationContext();
- method public android.content.pm.ApplicationInfo getApplicationInfo();
- method public android.content.res.AssetManager getAssets();
- method public java.io.File getCacheDir();
- method public java.lang.ClassLoader getClassLoader();
- method public java.io.File getCodeCacheDir();
- method public android.content.ContentResolver getContentResolver();
- 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 java.io.File getExternalCacheDir();
- method public java.io.File[] getExternalCacheDirs();
- method public java.io.File getExternalFilesDir(java.lang.String);
- method public java.io.File[] getExternalFilesDirs(java.lang.String);
- method public java.io.File[] getExternalMediaDirs();
- method public java.io.File getFileStreamPath(java.lang.String);
- method public java.io.File getFilesDir();
- method public android.os.Looper getMainLooper();
- method public java.io.File getNoBackupFilesDir();
- method public java.io.File getObbDir();
- method public java.io.File[] getObbDirs();
- 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 android.content.res.Resources getResources();
- method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
- method public java.lang.Object getSystemService(java.lang.String);
- method public java.lang.String getSystemServiceName(java.lang.Class<?>);
- method public android.content.res.Resources.Theme getTheme();
- 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 isDeviceProtectedStorage();
- method public boolean moveDatabaseFrom(android.content.Context, java.lang.String);
- method public boolean moveSharedPreferencesFrom(android.content.Context, java.lang.String);
- method public java.io.FileInputStream openFileInput(java.lang.String) throws java.io.FileNotFoundException;
- method public java.io.FileOutputStream openFileOutput(java.lang.String, int) throws java.io.FileNotFoundException;
- method public android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, int, android.database.sqlite.SQLiteDatabase.CursorFactory);
- method public android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, int, android.database.sqlite.SQLiteDatabase.CursorFactory, android.database.DatabaseErrorHandler);
- method public android.graphics.drawable.Drawable peekWallpaper();
- method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter);
- 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 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 sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
- method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, 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 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 sendStickyBroadcast(android.content.Intent);
- method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
- 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);
- method public void setWallpaper(android.graphics.Bitmap) throws java.io.IOException;
- method public void setWallpaper(java.io.InputStream) throws java.io.IOException;
- method public void startActivities(android.content.Intent[]);
- method public void startActivities(android.content.Intent[], android.os.Bundle);
- 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 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 boolean stopService(android.content.Intent);
- method public void unbindService(android.content.ServiceConnection);
- method public void unregisterReceiver(android.content.BroadcastReceiver);
- }
-
- public deprecated class MockCursor implements android.database.Cursor {
- ctor public MockCursor();
- method public void close();
- method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
- method public deprecated void deactivate();
- method public byte[] getBlob(int);
- method public int getColumnCount();
- method public int getColumnIndex(java.lang.String);
- method public int getColumnIndexOrThrow(java.lang.String);
- method public java.lang.String getColumnName(int);
- method public java.lang.String[] getColumnNames();
- method public int getCount();
- method public double getDouble(int);
- method public android.os.Bundle getExtras();
- method public float getFloat(int);
- method public int getInt(int);
- method public long getLong(int);
- method public android.net.Uri getNotificationUri();
- method public int getPosition();
- method public short getShort(int);
- method public java.lang.String getString(int);
- method public int getType(int);
- method public boolean getWantsAllOnMoveCalls();
- method public boolean isAfterLast();
- method public boolean isBeforeFirst();
- method public boolean isClosed();
- method public boolean isFirst();
- method public boolean isLast();
- method public boolean isNull(int);
- method public boolean move(int);
- method public boolean moveToFirst();
- method public boolean moveToLast();
- method public boolean moveToNext();
- method public boolean moveToPosition(int);
- method public boolean moveToPrevious();
- method public void registerContentObserver(android.database.ContentObserver);
- method public void registerDataSetObserver(android.database.DataSetObserver);
- method public deprecated boolean requery();
- method public android.os.Bundle respond(android.os.Bundle);
- method public void setExtras(android.os.Bundle);
- method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
- method public void unregisterContentObserver(android.database.ContentObserver);
- method public void unregisterDataSetObserver(android.database.DataSetObserver);
- }
-
- public deprecated class MockDialogInterface implements android.content.DialogInterface {
- ctor public MockDialogInterface();
- method public void cancel();
- method public void dismiss();
- }
-
- public deprecated class MockPackageManager extends android.content.pm.PackageManager {
- ctor public MockPackageManager();
- method public void addPackageToPreferred(java.lang.String);
- method public boolean addPermission(android.content.pm.PermissionInfo);
- method public boolean addPermissionAsync(android.content.pm.PermissionInfo);
- method public void addPreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
- method public boolean canRequestPackageInstalls();
- method public java.lang.String[] canonicalToCurrentPackageNames(java.lang.String[]);
- 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 clearInstantAppCookie();
- method public void clearPackagePreferredActivities(java.lang.String);
- method public java.lang.String[] currentToCanonicalPackageNames(java.lang.String[]);
- method public void extendVerificationTimeout(int, int, long);
- 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;
- method public android.graphics.drawable.Drawable getActivityIcon(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.ActivityInfo getActivityInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.graphics.drawable.Drawable getActivityLogo(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.graphics.drawable.Drawable getActivityLogo(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
- method public java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
- method public java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int);
- 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 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 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 android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
- method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
- method public java.lang.String getInstallerPackageName(java.lang.String);
- method public byte[] getInstantAppCookie();
- method public int getInstantAppCookieMaxBytes();
- method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
- method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
- method public java.lang.String getNameForUid(int);
- 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.PackageInstaller getPackageInstaller();
- method public int getPackageUid(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 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 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.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
- 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 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.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public boolean hasSystemFeature(java.lang.String);
- method public boolean hasSystemFeature(java.lang.String, int);
- method public boolean isInstantApp();
- method public boolean isInstantApp(java.lang.String);
- method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
- method public boolean isSafeMode();
- method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, 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> 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> queryIntentServices(android.content.Intent, int);
- method public java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public void removePackageFromPreferred(java.lang.String);
- method public void removePermission(java.lang.String);
- method public android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int);
- method public android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
- method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
- method public void setApplicationCategoryHint(java.lang.String, int);
- method public void setApplicationEnabledSetting(java.lang.String, int, int);
- method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
- method public void setInstallerPackageName(java.lang.String, java.lang.String);
- method public void updateInstantAppCookie(byte[]);
- method public void verifyPendingInstall(int, int);
- }
-
- public deprecated class MockResources extends android.content.res.Resources {
- ctor public MockResources();
- method public int getColor(int) throws android.content.res.Resources.NotFoundException;
- method public android.content.res.ColorStateList getColorStateList(int) throws android.content.res.Resources.NotFoundException;
- method public android.graphics.drawable.Drawable getDrawable(int) throws android.content.res.Resources.NotFoundException;
- method public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
- }
-
-}
-
package android.text {
public class AlteredCharSequence implements java.lang.CharSequence android.text.GetChars {
@@ -48633,6 +48505,7 @@
method public void cancel();
method public void commit();
method public void disableAutofillServices();
+ method public android.content.ComponentName getAutofillServiceComponentName();
method public android.service.autofill.UserData getUserData();
method public boolean hasEnabledAutofillServices();
method public boolean isAutofillSupported();
diff --git a/api/removed.txt b/api/removed.txt
index be4d5be..2aab223 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -429,21 +429,6 @@
}
-package android.test.mock {
-
- public class MockContext extends android.content.Context {
- method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
- method public java.io.File getSharedPreferencesPath(java.lang.String);
- }
-
- 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[]);
- }
-
-}
-
package android.text.format {
public class DateFormat {
@@ -506,14 +491,6 @@
}
-package android.view.accessibility {
-
- public final class AccessibilityWindowInfo implements android.os.Parcelable {
- method public boolean inPictureInPicture();
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/api/system-current.txt b/api/system-current.txt
index 30530a3..3e78167 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -103,6 +103,7 @@
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";
@@ -4143,11 +4144,13 @@
method public java.lang.String getCdmaPrlVersion();
method public int getCurrentPhoneType();
method public int getCurrentPhoneType(int);
+ method public int getDataActivationState();
method public deprecated boolean getDataEnabled();
method public deprecated boolean getDataEnabled(int);
method public boolean getEmergencyCallbackMode();
method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
method public android.os.Bundle getVisualVoicemailSettings();
+ method public int getVoiceActivationState();
method public boolean handlePinMmi(java.lang.String);
method public boolean handlePinMmiForSubscriber(int, java.lang.String);
method public boolean isDataConnectivityPossible();
@@ -4159,10 +4162,12 @@
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 setDataActivationState(int);
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);
+ method public void setVoiceActivationState(int);
method public deprecated void silenceRinger();
method public boolean supplyPin(java.lang.String);
method public int[] supplyPinReportResult(java.lang.String);
@@ -4176,6 +4181,11 @@
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
field public static final java.lang.String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL";
field public static final java.lang.String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING";
+ field public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2; // 0x2
+ field public static final int SIM_ACTIVATION_STATE_ACTIVATING = 1; // 0x1
+ field public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3; // 0x3
+ field public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4; // 0x4
+ field public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0; // 0x0
}
public abstract class VisualVoicemailService extends android.app.Service {
@@ -4347,44 +4357,6 @@
}
-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>);
- }
-
-}
-
package android.util {
public class EventLog {
diff --git a/api/test-current.txt b/api/test-current.txt
index e64c320..d67e997 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -114,6 +114,10 @@
field public static final java.lang.String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
}
+ public static final class SecurityLog.SecurityEvent implements android.os.Parcelable {
+ ctor public SecurityLog.SecurityEvent(long, byte[]);
+ }
+
}
package android.app.usage {
@@ -439,6 +443,10 @@
field public static final java.lang.String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled";
field public static final java.lang.String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification";
field public static final java.lang.String AUTOFILL_SERVICE = "autofill_service";
+ field public static final java.lang.String AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE = "autofill_user_data_max_field_classification_size";
+ field public static final java.lang.String AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE = "autofill_user_data_max_user_data_size";
+ field public static final java.lang.String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH = "autofill_user_data_max_value_length";
+ field public static final java.lang.String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH = "autofill_user_data_min_value_length";
field public static final java.lang.String DISABLED_PRINT_SERVICES = "disabled_print_services";
field public static final deprecated java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
field public static final java.lang.String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
@@ -685,21 +693,6 @@
}
-package android.test.mock {
-
- public class MockContext extends android.content.Context {
- method public int getUserId();
- }
-
- public deprecated class MockPackageManager extends android.content.pm.PackageManager {
- method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
- method public int getInstallReason(java.lang.String, android.os.UserHandle);
- method public java.lang.String getPermissionControllerPackageName();
- method public boolean isPermissionReviewModeEnabled();
- }
-
-}
-
package android.text {
public static final class Selection.MemoryTextWatcher implements android.text.TextWatcher {
@@ -984,6 +977,10 @@
method public boolean isPopupShowing();
}
+ public class TextClock extends android.widget.TextView {
+ method public void disableClockTick();
+ }
+
public class TimePicker extends android.widget.FrameLayout {
method public android.view.View getAmView();
method public android.view.View getHourView();
diff --git a/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
index 23393da0..7b0ac0b 100644
--- a/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
+++ b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
@@ -149,12 +149,25 @@
SystemPropertiesProto::Ro::Product::_FIELD_COUNT);
Message product(&productTable);
- Table vendorTable(SystemPropertiesProto::Ro::Product::Vendor::_FIELD_NAMES,
+ Table pVendorTable(SystemPropertiesProto::Ro::Product::Vendor::_FIELD_NAMES,
SystemPropertiesProto::Ro::Product::Vendor::_FIELD_IDS,
SystemPropertiesProto::Ro::Product::Vendor::_FIELD_COUNT);
- Message vendor(&vendorTable);
- product.addSubMessage(SystemPropertiesProto::Ro::Product::VENDOR, &vendor);
+ Message pVendor(&pVendorTable);
+ product.addSubMessage(SystemPropertiesProto::Ro::Product::VENDOR, &pVendor);
ro.addSubMessage(SystemPropertiesProto::Ro::PRODUCT, &product);
+
+ Table telephonyTable(SystemPropertiesProto::Ro::Telephony::_FIELD_NAMES,
+ SystemPropertiesProto::Ro::Telephony::_FIELD_IDS,
+ SystemPropertiesProto::Ro::Telephony::_FIELD_COUNT);
+ Message telephony(&telephonyTable);
+ ro.addSubMessage(SystemPropertiesProto::Ro::TELEPHONY, &telephony);
+
+ Table vendorTable(SystemPropertiesProto::Ro::Vendor::_FIELD_NAMES,
+ SystemPropertiesProto::Ro::Vendor::_FIELD_IDS,
+ SystemPropertiesProto::Ro::Vendor::_FIELD_COUNT);
+ Message vendor(&vendorTable);
+ ro.addSubMessage(SystemPropertiesProto::Ro::VENDOR, &vendor);
+
sysProp.addSubMessage(SystemPropertiesProto::RO, &ro);
Table sysTable(SystemPropertiesProto::Sys::_FIELD_NAMES,
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 3e517bb..f98ee3d 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -54,7 +54,7 @@
src/storage/StorageManager.cpp \
src/StatsLogProcessor.cpp \
src/StatsService.cpp \
- src/stats_util.cpp \
+ src/HashableDimensionKey.cpp \
src/guardrail/MemoryLeakTrackUtil.cpp \
src/guardrail/StatsdStats.cpp
@@ -174,7 +174,8 @@
tests/metrics/EventMetricProducer_test.cpp \
tests/metrics/ValueMetricProducer_test.cpp \
tests/metrics/GaugeMetricProducer_test.cpp \
- tests/guardrail/StatsdStats_test.cpp
+ tests/guardrail/StatsdStats_test.cpp \
+ tests/metrics/metrics_test_helper.cpp
LOCAL_STATIC_LIBRARIES := \
$(statsd_common_static_libraries) \
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
new file mode 100644
index 0000000..0b6f8f2
--- /dev/null
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -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.
+ */
+#include "HashableDimensionKey.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::string;
+
+string HashableDimensionKey::toString() const {
+ string flattened;
+ for (const auto& pair : mKeyValuePairs) {
+ flattened += std::to_string(pair.key());
+ flattened += ":";
+ switch (pair.value_case()) {
+ case KeyValuePair::ValueCase::kValueStr:
+ flattened += pair.value_str();
+ break;
+ case KeyValuePair::ValueCase::kValueInt:
+ flattened += std::to_string(pair.value_int());
+ break;
+ case KeyValuePair::ValueCase::kValueLong:
+ flattened += std::to_string(pair.value_long());
+ break;
+ case KeyValuePair::ValueCase::kValueBool:
+ flattened += std::to_string(pair.value_bool());
+ break;
+ case KeyValuePair::ValueCase::kValueFloat:
+ flattened += std::to_string(pair.value_float());
+ break;
+ default:
+ break;
+ }
+ flattened += "|";
+ }
+ return flattened;
+}
+
+bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const {
+ const auto& keyValue2 = that.getKeyValuePairs();
+ if (mKeyValuePairs.size() != keyValue2.size()) {
+ return false;
+ }
+
+ for (size_t i = 0; i < keyValue2.size(); i++) {
+ const auto& kv1 = mKeyValuePairs[i];
+ const auto& kv2 = keyValue2[i];
+ if (kv1.key() != kv2.key()) {
+ return false;
+ }
+
+ if (kv1.value_case() != kv2.value_case()) {
+ return false;
+ }
+
+ switch (kv1.value_case()) {
+ case KeyValuePair::ValueCase::kValueStr:
+ if (kv1.value_str() != kv2.value_str()) {
+ return false;
+ }
+ break;
+ case KeyValuePair::ValueCase::kValueInt:
+ if (kv1.value_int() != kv2.value_int()) {
+ return false;
+ }
+ break;
+ case KeyValuePair::ValueCase::kValueLong:
+ if (kv1.value_long() != kv2.value_long()) {
+ return false;
+ }
+ break;
+ case KeyValuePair::ValueCase::kValueBool:
+ if (kv1.value_bool() != kv2.value_bool()) {
+ return false;
+ }
+ break;
+ case KeyValuePair::ValueCase::kValueFloat: {
+ if (kv1.value_float() != kv2.value_float()) {
+ return false;
+ }
+ break;
+ }
+ case KeyValuePair::ValueCase::VALUE_NOT_SET:
+ break;
+ }
+ }
+ return true;
+};
+
+bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const {
+ return toString().compare(that.toString()) < 0;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
new file mode 100644
index 0000000..85215552
--- /dev/null
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utils/JenkinsHash.h>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class HashableDimensionKey {
+public:
+ explicit HashableDimensionKey(const std::vector<KeyValuePair>& keyValuePairs)
+ : mKeyValuePairs(keyValuePairs){};
+
+ HashableDimensionKey(){};
+
+ HashableDimensionKey(const HashableDimensionKey& that)
+ : mKeyValuePairs(that.getKeyValuePairs()){};
+
+ HashableDimensionKey& operator=(const HashableDimensionKey& from) = default;
+
+ std::string toString() const;
+
+ inline const std::vector<KeyValuePair>& getKeyValuePairs() const {
+ return mKeyValuePairs;
+ }
+
+ bool operator==(const HashableDimensionKey& that) const;
+
+ bool operator<(const HashableDimensionKey& that) const;
+
+ inline const char* c_str() const {
+ return toString().c_str();
+ }
+
+private:
+ std::vector<KeyValuePair> mKeyValuePairs;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+namespace std {
+
+using android::os::statsd::HashableDimensionKey;
+using android::os::statsd::KeyValuePair;
+
+template <>
+struct hash<HashableDimensionKey> {
+ std::size_t operator()(const HashableDimensionKey& key) const {
+ android::hash_t hash = 0;
+ for (const auto& pair : key.getKeyValuePairs()) {
+ hash = android::JenkinsHashMix(hash, android::hash_type(pair.key()));
+ hash = android::JenkinsHashMix(
+ hash, android::hash_type(static_cast<int32_t>(pair.value_case())));
+ switch (pair.value_case()) {
+ case KeyValuePair::ValueCase::kValueStr:
+ hash = android::JenkinsHashMix(
+ hash,
+ static_cast<uint32_t>(std::hash<std::string>()(pair.value_str())));
+ break;
+ case KeyValuePair::ValueCase::kValueInt:
+ hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_int()));
+ break;
+ case KeyValuePair::ValueCase::kValueLong:
+ hash = android::JenkinsHashMix(
+ hash, android::hash_type(static_cast<int64_t>(pair.value_long())));
+ break;
+ case KeyValuePair::ValueCase::kValueBool:
+ hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_bool()));
+ break;
+ case KeyValuePair::ValueCase::kValueFloat: {
+ float floatVal = pair.value_float();
+ hash = android::JenkinsHashMixBytes(hash, (uint8_t*)&floatVal, sizeof(float));
+ break;
+ }
+ case KeyValuePair::ValueCase::VALUE_NOT_SET:
+ break;
+ }
+ }
+ return hash;
+ }
+};
+
+} // namespace std
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index f6caca8..0c078d5 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -114,7 +114,8 @@
void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) {
ALOGD("Updated configuration for key %s", key.ToString().c_str());
- unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(key, config, mTimeBaseSec);
+
+ sp<MetricsManager> newMetricsManager = new MetricsManager(key, config, mTimeBaseSec, mUidMap);
auto it = mMetricsManagers.find(key);
if (it == mMetricsManagers.end() && mMetricsManagers.size() > StatsdStats::kMaxConfigCount) {
@@ -125,7 +126,12 @@
if (newMetricsManager->isConfigValid()) {
mUidMap->OnConfigUpdated(key);
newMetricsManager->setAnomalyMonitor(mAnomalyMonitor);
- mMetricsManagers[key] = std::move(newMetricsManager);
+ if (config.log_source().package().size() > 0) {
+ // We have to add listener after the MetricsManager is constructed because it's
+ // not safe to create wp or sp from this pointer inside its constructor.
+ mUidMap->addListener(newMetricsManager.get());
+ }
+ mMetricsManagers[key] = newMetricsManager;
// Why doesn't this work? mMetricsManagers.insert({key, std::move(newMetricsManager)});
VLOG("StatsdConfig valid");
} else {
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 7ec4e4b..1e5c426 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -56,7 +56,7 @@
private:
mutable mutex mBroadcastTimesMutex;
- std::unordered_map<ConfigKey, std::unique_ptr<MetricsManager>> mMetricsManagers;
+ std::unordered_map<ConfigKey, sp<MetricsManager>> mMetricsManagers;
std::unordered_map<ConfigKey, long> mLastBroadcastTimes;
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 4dd2539..dab3880 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -201,7 +201,7 @@
}
if (!args[0].compare(String8("print-uid-map"))) {
- return cmd_print_uid_map(out);
+ return cmd_print_uid_map(out, args);
}
if (!args[0].compare(String8("dump-report"))) {
@@ -248,9 +248,10 @@
fprintf(out, " # adb shell start\n");
fprintf(out, "\n");
fprintf(out, "\n");
- fprintf(out, "usage: adb shell cmd stats print-uid-map \n");
+ fprintf(out, "usage: adb shell cmd stats print-uid-map [PKG]\n");
fprintf(out, "\n");
fprintf(out, " Prints the UID, app name, version mapping.\n");
+ fprintf(out, " PKG Optional package name to print the uids of the package\n");
fprintf(out, "\n");
fprintf(out, "\n");
fprintf(out, "usage: adb shell cmd stats pull-source [int] \n");
@@ -497,8 +498,19 @@
return NO_ERROR;
}
-status_t StatsService::cmd_print_uid_map(FILE* out) {
- mUidMap->printUidMap(out);
+status_t StatsService::cmd_print_uid_map(FILE* out, const Vector<String8>& args) {
+ if (args.size() > 1) {
+ string pkg;
+ pkg.assign(args[1].c_str(), args[1].size());
+ auto uids = mUidMap->getAppUid(pkg);
+ fprintf(out, "%s -> [ ", pkg.c_str());
+ for (const auto& uid : uids) {
+ fprintf(out, "%d ", uid);
+ }
+ fprintf(out, "]\n");
+ } else {
+ mUidMap->printUidMap(out);
+ }
return NO_ERROR;
}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index e434f65..08fcdac 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -158,7 +158,7 @@
/**
* Print the mapping of uids to package names.
*/
- status_t cmd_print_uid_map(FILE* out);
+ status_t cmd_print_uid_map(FILE* out, const Vector<String8>& args);
/**
* Flush the data to disk.
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index 18b93ee..a63bc04 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -279,7 +279,7 @@
}
// outputKey is the output key values. e.g, uid:1234
- const HashableDimensionKey outputKey = getHashableKey(getDimensionKey(event, mOutputDimension));
+ const HashableDimensionKey outputKey(getDimensionKey(event, mOutputDimension));
handleConditionEvent(outputKey, matchedState == 1, conditionCache, conditionChangedCache);
}
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
index ff0e3bc..53ef9d5 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -109,7 +109,7 @@
kv.set_key(link.key_in_condition(i).key());
}
- return getHashableKey(dimensionKey);
+ return HashableDimensionKey(dimensionKey);
}
} // namespace statsd
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index a6d2719..cb3f3d6 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -248,6 +248,12 @@
details->add_section(12);
details->add_section(13);*/
+ AllowedLogSource* logSource = config.mutable_log_source();
+ logSource->add_uid(1000);
+ logSource->add_uid(0);
+ logSource->add_package("com.android.statsd.dogfood");
+ logSource->add_package("com.android.bluetooth");
+
// Count process state changes, slice by uid.
metric = config.add_count_metric();
metric->set_name("METRIC_2");
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 4cf168e..cb868e1f 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -46,6 +46,8 @@
const static int kMaxTimestampCount = 20;
+ const static int kMaxLogSourceCount = 50;
+
// Max memory allowed for storing metrics per configuration. When this limit is approached,
// statsd will send a broadcast so that the client can fetch the data and clear this memory.
static const size_t kMaxMetricsBytesPerConfig = 128 * 1024;
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 01487f0..d660b5f 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -33,6 +33,7 @@
mContext =
create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t));
mTimestampNs = msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec;
+ mLogUid = msg.entry_v4.uid;
init(mContext);
}
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 6ff6b87..d3f38de 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -62,6 +62,10 @@
*/
int GetTagId() const { return mTagId; }
+ uint32_t GetUid() const {
+ return mLogUid;
+ }
+
/**
* Get the nth value, starting at 1.
*
@@ -133,6 +137,8 @@
uint64_t mTimestampNs;
int mTagId;
+
+ uint32_t mLogUid;
};
} // namespace statsd
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index 41b24bc..4ce4768 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -105,7 +105,7 @@
// Set up the binder
sp<ProcessState> ps(ProcessState::self());
- ps->setThreadPoolMaxThreadCount(1); // everything is oneway, let it queue and save ram
+ ps->setThreadPoolMaxThreadCount(9);
ps->startThreadPool();
ps->giveThreadPoolName();
IPCThreadState::self()->disableBackgroundScheduling(true);
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index bc12a78..9031ed0 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -107,17 +107,14 @@
for (const auto& counter : mPastBuckets) {
const HashableDimensionKey& hashableKey = counter.first;
+ const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
VLOG(" dimension key %s", hashableKey.c_str());
- auto it = mDimensionKeyMap.find(hashableKey);
- if (it == mDimensionKeyMap.end()) {
- ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
- continue;
- }
+
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
- for (const auto& kv : it->second) {
+ for (const auto& kv : kvs) {
long long dimensionToken = protoOutput->start(
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 59995d2..e32fc06 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -48,12 +48,6 @@
virtual ~CountMetricProducer();
- // TODO: Implement this later.
- virtual void notifyAppUpgrade(const string& apk, const int uid, const int64_t version)
- override{};
- // TODO: Implement this later.
- virtual void notifyAppRemoved(const string& apk, const int uid) override{};
-
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 220861d..1c8f422 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -159,18 +159,14 @@
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
+ const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
VLOG(" dimension key %s", hashableKey.c_str());
- auto it = mDimensionKeyMap.find(hashableKey);
- if (it == mDimensionKeyMap.end()) {
- ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
- continue;
- }
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
- for (const auto& kv : it->second) {
+ for (const auto& kv : kvs) {
long long dimensionToken = protoOutput->start(
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
@@ -260,7 +256,7 @@
return;
}
- HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
+ HashableDimensionKey atomKey(getDimensionKey(event, mInternalDimension));
if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
if (hitGuardRailLocked(eventKey)) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index e7aca7f..7044b4b 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -47,12 +47,6 @@
virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) override;
- // TODO: Implement this later.
- virtual void notifyAppUpgrade(const string& apk, const int uid, const int64_t version)
- override{};
- // TODO: Implement this later.
- virtual void notifyAppRemoved(const string& apk, const int uid) override{};
-
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index d720ead..6120ad8 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -40,12 +40,6 @@
virtual ~EventMetricProducer();
- // TODO: Implement this later.
- virtual void notifyAppUpgrade(const string& apk, const int uid, const int64_t version)
- override{};
- // TODO: Implement this later.
- virtual void notifyAppRemoved(const string& apk, const int uid) override{};
-
protected:
void startNewProtoOutputStreamLocked();
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 6402633..47cca0e 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -128,18 +128,14 @@
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
- auto it = mDimensionKeyMap.find(hashableKey);
- if (it == mDimensionKeyMap.end()) {
- ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
- continue;
- }
+ const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
VLOG(" dimension key %s", hashableKey.c_str());
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
- for (const auto& kv : it->second) {
+ for (const auto& kv : kvs) {
long long dimensionToken = protoOutput->start(
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 4a037ff..19d51e8 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -45,8 +45,6 @@
// producer always reports the guage at the earliest time of the bucket when the condition is met.
class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
public:
- // TODO: Pass in the start time from MetricsManager, it should be consistent
- // for all metrics.
GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& countMetric,
const int conditionIndex, const sp<ConditionWizard>& wizard,
const int pullTagId, const int atomTagId, const int64_t startTimeNs);
@@ -56,12 +54,6 @@
// Handles when the pulled data arrives.
void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
- // TODO: Implement this later.
- virtual void notifyAppUpgrade(const string& apk, const int uid, const int64_t version)
- override{};
- // TODO: Implement this later.
- virtual void notifyAppRemoved(const string& apk, const int uid) override{};
-
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index f38f3df..5286908 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -32,12 +32,7 @@
if (mDimension.size() > 0) {
vector<KeyValuePair> key = getDimensionKey(event, mDimension);
- eventKey = getHashableKey(key);
- // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport
- // expects vector<KeyValuePair>.
- if (mDimensionKeyMap.find(eventKey) == mDimensionKeyMap.end()) {
- mDimensionKeyMap[eventKey] = key;
- }
+ eventKey = HashableDimensionKey(key);
} else {
eventKey = DEFAULT_DIMENSION_KEY;
}
@@ -58,7 +53,6 @@
} else {
condition = mCondition;
}
-
onMatchedLogEventInternalLocked(matcherIndex, eventKey, conditionKeys, condition, event);
}
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index d4a2195..85ef4ad 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -27,6 +27,7 @@
#include <log/logprint.h>
#include <utils/RefBase.h>
+#include <unordered_map>
namespace android {
namespace os {
@@ -48,12 +49,21 @@
mCondition(conditionIndex >= 0 ? false : true),
mConditionSliced(false),
mWizard(wizard),
- mConditionTrackerIndex(conditionIndex) {
- // reuse the same map for non-sliced metrics too. this way, we avoid too many if-else.
- mDimensionKeyMap[DEFAULT_DIMENSION_KEY] = std::vector<KeyValuePair>();
- };
+ mConditionTrackerIndex(conditionIndex){};
virtual ~MetricProducer(){};
+ void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override{
+ // TODO: Implement me.
+ };
+
+ void notifyAppRemoved(const string& apk, const int uid) override{
+ // TODO: Implement me.
+ };
+
+ void onUidMapReceived() override{
+ // TODO: Implement me.
+ };
+
// Consume the parsed stats log entry that already matched the "what" of the metric.
void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
std::lock_guard<std::mutex> lock(mMutex);
@@ -132,10 +142,6 @@
std::vector<KeyMatcher> mDimension; // The dimension defined in statsd_config
- // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
- // that StatsLogReport wants.
- std::unordered_map<HashableDimensionKey, std::vector<KeyValuePair>> mDimensionKeyMap;
-
std::vector<MetricConditionLink> mConditionLinks;
std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index a5900f4..231bd8e 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -16,6 +16,7 @@
#define DEBUG true // STOPSHIP if true
#include "Log.h"
#include "MetricsManager.h"
+#include "statslog.h"
#include "CountMetricProducer.h"
#include "condition/CombinationConditionTracker.h"
@@ -44,12 +45,37 @@
const int FIELD_ID_METRICS = 1;
-MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec) : mConfigKey(key) {
+MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config,
+ const long timeBaseSec, sp<UidMap> uidMap)
+ : mConfigKey(key), mUidMap(uidMap) {
mConfigValid =
initStatsdConfig(key, config, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
mTrackerToMetricMap, mTrackerToConditionMap);
+ if (!config.has_log_source()) {
+ // TODO(b/70794411): uncomment the following line and remove the hard coded log source
+ // after all configs have the log source added.
+ // mConfigValid = false;
+ // ALOGE("Log source white list is empty! This config won't get any data.");
+
+ mAllowedUid.push_back(1000);
+ mAllowedUid.push_back(0);
+ mAllowedLogSources.insert(mAllowedUid.begin(), mAllowedUid.end());
+ } else {
+ mAllowedUid.insert(mAllowedUid.begin(), config.log_source().uid().begin(),
+ config.log_source().uid().end());
+ mAllowedPkg.insert(mAllowedPkg.begin(), config.log_source().package().begin(),
+ config.log_source().package().end());
+
+ if (mAllowedUid.size() + mAllowedPkg.size() > StatsdStats::kMaxLogSourceCount) {
+ ALOGE("Too many log sources. This is likely to be an error in the config.");
+ mConfigValid = false;
+ } else {
+ initLogSourceWhiteList();
+ }
+ }
+
// Guardrail. Reject the config if it's too big.
if (mAllMetricProducers.size() > StatsdStats::kMaxMetricCountPerConfig ||
mAllConditionTrackers.size() > StatsdStats::kMaxConditionCountPerConfig ||
@@ -69,10 +95,53 @@
VLOG("~MetricsManager()");
}
+void MetricsManager::initLogSourceWhiteList() {
+ std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
+ mAllowedLogSources.clear();
+ mAllowedLogSources.insert(mAllowedUid.begin(), mAllowedUid.end());
+
+ for (const auto& pkg : mAllowedPkg) {
+ auto uids = mUidMap->getAppUid(pkg);
+ mAllowedLogSources.insert(uids.begin(), uids.end());
+ }
+ if (DEBUG) {
+ for (const auto& uid : mAllowedLogSources) {
+ VLOG("Allowed uid %d", uid);
+ }
+ }
+}
+
bool MetricsManager::isConfigValid() const {
return mConfigValid;
}
+void MetricsManager::notifyAppUpgrade(const string& apk, const int uid, const int64_t version) {
+ // check if we care this package
+ if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
+ return;
+ }
+ // We will re-initialize the whole list because we don't want to keep the multi mapping of
+ // UID<->pkg inside MetricsManager to reduce the memory usage.
+ initLogSourceWhiteList();
+}
+
+void MetricsManager::notifyAppRemoved(const string& apk, const int uid) {
+ // check if we care this package
+ if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
+ return;
+ }
+ // We will re-initialize the whole list because we don't want to keep the multi mapping of
+ // UID<->pkg inside MetricsManager to reduce the memory usage.
+ initLogSourceWhiteList();
+}
+
+void MetricsManager::onUidMapReceived() {
+ if (mAllowedPkg.size() == 0) {
+ return;
+ }
+ initLogSourceWhiteList();
+}
+
void MetricsManager::onDumpReport(ProtoOutputStream* protoOutput) {
VLOG("=========================Metric Reports Start==========================");
uint64_t dumpTimeStampNs = time(nullptr) * NS_PER_SEC;
@@ -92,6 +161,14 @@
return;
}
+ if (event.GetTagId() != android::util::APP_HOOK) {
+ std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
+ if (mAllowedLogSources.find(event.GetUid()) == mAllowedLogSources.end()) {
+ VLOG("log source %d not on the whitelist", event.GetUid());
+ return;
+ }
+ }
+
int tagId = event.GetTagId();
uint64_t eventTime = event.GetTimestampNs();
if (mTagIds.find(tagId) == mTagIds.end()) {
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 738adc9..8faa75d 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -24,6 +24,7 @@
#include "logd/LogEvent.h"
#include "matchers/LogMatchingTracker.h"
#include "metrics/MetricProducer.h"
+#include "packages/UidMap.h"
#include <unordered_map>
@@ -32,9 +33,10 @@
namespace statsd {
// A MetricsManager is responsible for managing metrics from one single config source.
-class MetricsManager {
+class MetricsManager : public PackageInfoListener {
public:
- MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, const long timeBaseSec);
+ MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, const long timeBaseSec,
+ sp<UidMap> uidMap);
virtual ~MetricsManager();
@@ -48,6 +50,12 @@
void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
+ void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override;
+
+ void notifyAppRemoved(const string& apk, const int uid) override;
+
+ void onUidMapReceived() override;
+
// Config source owner can call onDumpReport() to get all the metrics collected.
virtual void onDumpReport(android::util::ProtoOutputStream* protoOutput);
@@ -58,6 +66,23 @@
private:
const ConfigKey mConfigKey;
+ sp<UidMap> mUidMap;
+
+ bool mConfigValid = false;
+
+ // The uid log sources from StatsdConfig.
+ std::vector<int32_t> mAllowedUid;
+
+ // The pkg log sources from StatsdConfig.
+ std::vector<std::string> mAllowedPkg;
+
+ // The combined uid sources (after translating pkg name to uid).
+ // Logs from uids that are not in the list will be ignored to avoid spamming.
+ std::set<int32_t> mAllowedLogSources;
+
+ // To guard access to mAllowedLogSources
+ mutable std::mutex mAllowedLogSourcesMutex;
+
// All event tags that are interesting to my metrics.
std::set<int> mTagIds;
@@ -102,7 +127,7 @@
// maps from ConditionTracker to MetricProducer
std::unordered_map<int, std::vector<int>> mConditionToMetricMap;
- bool mConfigValid = false;
+ void initLogSourceWhiteList();
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 7efa6cd..40aed7b 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -132,16 +132,12 @@
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
VLOG(" dimension key %s", hashableKey.c_str());
- auto it = mDimensionKeyMap.find(hashableKey);
- if (it == mDimensionKeyMap.end()) {
- ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
- continue;
- }
+ const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
- for (const auto& kv : it->second) {
+ for (const auto& kv : kvs) {
long long dimensionToken = protoOutput->start(
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 51af83d..2f27e4e 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -47,12 +47,6 @@
void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
- // TODO: Implement this later.
- virtual void notifyAppUpgrade(const string& apk, const int uid, const int64_t version)
- override{};
- // TODO: Implement this later.
- virtual void notifyAppRemoved(const string& apk, const int uid) override{};
-
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 95c8a59..6050f43 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -42,7 +42,7 @@
// 1. Report the tuple count if the tuple count > soft limit
if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mInfos.size() + 1;
- StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey,
+ StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
newTupleCount);
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 36e25edf..5c43096 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -45,7 +45,7 @@
}
if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mConditionKeyMap.size() + 1;
- StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey,
+ StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
newTupleCount);
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
diff --git a/cmds/statsd/src/packages/PackageInfoListener.h b/cmds/statsd/src/packages/PackageInfoListener.h
index bc8b0de..df29eb0 100644
--- a/cmds/statsd/src/packages/PackageInfoListener.h
+++ b/cmds/statsd/src/packages/PackageInfoListener.h
@@ -32,6 +32,9 @@
// Notify interested listeners that the given apk and uid combination no longer exits.
virtual void notifyAppRemoved(const std::string& apk, const int uid) = 0;
+
+ // Notify the listener that the UidMap snapshot is available.
+ virtual void onUidMapReceived() = 0;
};
} // namespace statsd
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 6e7a613..21a9cf3 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -31,10 +31,9 @@
namespace os {
namespace statsd {
-UidMap::UidMap() : mBytesUsed(0) {
-}
-UidMap::~UidMap() {
-}
+UidMap::UidMap() : mBytesUsed(0) {}
+
+UidMap::~UidMap() {}
bool UidMap::hasApp(int uid, const string& packageName) const {
lock_guard<mutex> lock(mMutex);
@@ -48,6 +47,27 @@
return false;
}
+string UidMap::normalizeAppName(const string& appName) const {
+ string normalizedName = appName;
+ std::transform(normalizedName.begin(), normalizedName.end(), normalizedName.begin(), ::tolower);
+ return normalizedName;
+}
+
+std::set<string> UidMap::getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const {
+ lock_guard<mutex> lock(mMutex);
+ return getAppNamesFromUidLocked(uid,returnNormalized);
+}
+
+std::set<string> UidMap::getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const {
+ std::set<string> names;
+ auto range = mMap.equal_range(uid);
+ for (auto it = range.first; it != range.second; ++it) {
+ names.insert(returnNormalized ?
+ normalizeAppName(it->second.packageName) : it->second.packageName);
+ }
+ return names;
+}
+
int64_t UidMap::getAppVersion(int uid, const string& packageName) const {
lock_guard<mutex> lock(mMutex);
@@ -67,26 +87,41 @@
void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
const vector<int64_t>& versionCode, const vector<String16>& packageName) {
- lock_guard<mutex> lock(mMutex); // Exclusively lock for updates.
+ vector<wp<PackageInfoListener>> broadcastList;
+ {
+ lock_guard<mutex> lock(mMutex); // Exclusively lock for updates.
- mMap.clear();
- for (size_t j = 0; j < uid.size(); j++) {
- mMap.insert(make_pair(uid[j],
- AppData(string(String8(packageName[j]).string()), versionCode[j])));
- }
+ mMap.clear();
+ for (size_t j = 0; j < uid.size(); j++) {
+ mMap.insert(make_pair(
+ uid[j], AppData(string(String8(packageName[j]).string()), versionCode[j])));
+ }
- auto snapshot = mOutput.add_snapshots();
- snapshot->set_timestamp_nanos(timestamp);
- for (size_t j = 0; j < uid.size(); j++) {
- auto t = snapshot->add_package_info();
- t->set_name(string(String8(packageName[j]).string()));
- t->set_version(int(versionCode[j]));
- t->set_uid(uid[j]);
+ auto snapshot = mOutput.add_snapshots();
+ snapshot->set_timestamp_nanos(timestamp);
+ for (size_t j = 0; j < uid.size(); j++) {
+ auto t = snapshot->add_package_info();
+ t->set_name(string(String8(packageName[j]).string()));
+ t->set_version(int(versionCode[j]));
+ t->set_uid(uid[j]);
+ }
+ mBytesUsed += snapshot->ByteSize();
+ StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
+ StatsdStats::getInstance().setUidMapSnapshots(mOutput.snapshots_size());
+ ensureBytesUsedBelowLimit();
+ getListenerListCopyLocked(&broadcastList);
}
- mBytesUsed += snapshot->ByteSize();
- StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
- StatsdStats::getInstance().setUidMapSnapshots(mOutput.snapshots_size());
- ensureBytesUsedBelowLimit();
+ // To avoid invoking callback while holding the internal lock. we get a copy of the listener
+ // list and invoke the callback. It's still possible that after we copy the list, a
+ // listener removes itself before we call it. It's then the listener's job to handle it (expect
+ // the callback to be called after listener is removed, and the listener should properly
+ // ignore it).
+ for (auto weakPtr : broadcastList) {
+ auto strongPtr = weakPtr.promote();
+ if (strongPtr != NULL) {
+ strongPtr->onUidMapReceived();
+ }
+ }
}
void UidMap::updateApp(const String16& app_16, const int32_t& uid, const int64_t& versionCode) {
@@ -95,38 +130,45 @@
void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid,
const int64_t& versionCode) {
- lock_guard<mutex> lock(mMutex);
+ vector<wp<PackageInfoListener>> broadcastList;
+ string appName = string(String8(app_16).string());
+ {
+ lock_guard<mutex> lock(mMutex);
- string app = string(String8(app_16).string());
+ auto log = mOutput.add_changes();
+ log->set_deletion(false);
+ log->set_timestamp_nanos(timestamp);
+ log->set_app(appName);
+ log->set_uid(uid);
+ log->set_version(versionCode);
+ mBytesUsed += log->ByteSize();
+ StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
+ StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
+ ensureBytesUsedBelowLimit();
- // Notify any interested producers that this app has updated
- for (auto it : mSubscribers) {
- it->notifyAppUpgrade(app, uid, versionCode);
- }
-
- auto log = mOutput.add_changes();
- log->set_deletion(false);
- log->set_timestamp_nanos(timestamp);
- log->set_app(app);
- log->set_uid(uid);
- log->set_version(versionCode);
- mBytesUsed += log->ByteSize();
- StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
- StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
- ensureBytesUsedBelowLimit();
-
- auto range = mMap.equal_range(int(uid));
- for (auto it = range.first; it != range.second; ++it) {
- if (it->second.packageName == app) {
- it->second.versionCode = versionCode;
- return;
+ auto range = mMap.equal_range(int(uid));
+ bool found = false;
+ for (auto it = range.first; it != range.second; ++it) {
+ // If we find the exact same app name and uid, update the app version directly.
+ if (it->second.packageName == appName) {
+ it->second.versionCode = versionCode;
+ found = true;
+ break;
+ }
}
- VLOG("updateApp failed to find the app %s with uid %i to update", app.c_str(), uid);
- return;
+ if (!found) {
+ // Otherwise, we need to add an app at this uid.
+ mMap.insert(make_pair(uid, AppData(appName, versionCode)));
+ }
+ getListenerListCopyLocked(&broadcastList);
}
- // Otherwise, we need to add an app at this uid.
- mMap.insert(make_pair(uid, AppData(app, versionCode)));
+ for (auto weakPtr : broadcastList) {
+ auto strongPtr = weakPtr.promote();
+ if (strongPtr != NULL) {
+ strongPtr->notifyAppUpgrade(appName, uid, versionCode);
+ }
+ }
}
void UidMap::ensureBytesUsedBelowLimit() {
@@ -154,42 +196,60 @@
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);
- string app = string(String8(app_16).string());
-
- for (auto it : mSubscribers) {
- it->notifyAppRemoved(app, uid);
- }
-
- auto log = mOutput.add_changes();
- log->set_deletion(true);
- log->set_timestamp_nanos(timestamp);
- log->set_app(app);
- log->set_uid(uid);
- mBytesUsed += log->ByteSize();
- StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
- StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
- ensureBytesUsedBelowLimit();
-
- auto range = mMap.equal_range(int(uid));
- for (auto it = range.first; it != range.second; ++it) {
- if (it->second.packageName == app) {
- mMap.erase(it);
- return;
+void UidMap::getListenerListCopyLocked(vector<wp<PackageInfoListener>>* output) {
+ for (auto weakIt = mSubscribers.begin(); weakIt != mSubscribers.end();) {
+ auto strongPtr = weakIt->promote();
+ if (strongPtr != NULL) {
+ output->push_back(*weakIt);
+ weakIt++;
+ } else {
+ weakIt = mSubscribers.erase(weakIt);
+ VLOG("The UidMap listener is gone, remove it now");
}
}
- VLOG("removeApp failed to find the app %s with uid %i to remove", app.c_str(), uid);
- return;
}
-void UidMap::addListener(sp<PackageInfoListener> producer) {
+void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
+ vector<wp<PackageInfoListener>> broadcastList;
+ string app = string(String8(app_16).string());
+ {
+ lock_guard<mutex> lock(mMutex);
+
+ auto log = mOutput.add_changes();
+ log->set_deletion(true);
+ log->set_timestamp_nanos(timestamp);
+ log->set_app(app);
+ log->set_uid(uid);
+ mBytesUsed += log->ByteSize();
+ StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
+ StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
+ ensureBytesUsedBelowLimit();
+
+ auto range = mMap.equal_range(int(uid));
+ for (auto it = range.first; it != range.second; ++it) {
+ if (it->second.packageName == app) {
+ mMap.erase(it);
+ break;
+ }
+ }
+ getListenerListCopyLocked(&broadcastList);
+ }
+
+ for (auto weakPtr : broadcastList) {
+ auto strongPtr = weakPtr.promote();
+ if (strongPtr != NULL) {
+ strongPtr->notifyAppRemoved(app, uid);
+ }
+ }
+}
+
+void UidMap::addListener(wp<PackageInfoListener> producer) {
lock_guard<mutex> lock(mMutex); // Lock for updates
mSubscribers.insert(producer);
}
-void UidMap::removeListener(sp<PackageInfoListener> producer) {
+void UidMap::removeListener(wp<PackageInfoListener> producer) {
lock_guard<mutex> lock(mMutex); // Lock for updates
mSubscribers.erase(producer);
}
@@ -250,7 +310,7 @@
return m;
}
-size_t UidMap::getBytesUsed() {
+size_t UidMap::getBytesUsed() const {
return mBytesUsed;
}
@@ -296,7 +356,7 @@
return ret;
}
-void UidMap::printUidMap(FILE* out) {
+void UidMap::printUidMap(FILE* out) const {
lock_guard<mutex> lock(mMutex);
for (auto it : mMap) {
@@ -330,6 +390,18 @@
mLastUpdatePerConfigKey.erase(key);
}
+set<int32_t> UidMap::getAppUid(const string& package) const {
+ lock_guard<mutex> lock(mMutex);
+
+ set<int32_t> results;
+ for (const auto& pair : mMap) {
+ if (pair.second.packageName == package) {
+ results.insert(pair.first);
+ }
+ }
+ return results;
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 9e1ad69..a9aec94 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,18 +65,21 @@
// 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:
// adb shell cmd stats print-uid-map
- void printUidMap(FILE* out);
+ void printUidMap(FILE* out) const;
// Commands for indicating to the map that a producer should be notified if an app is updated.
// This allows the metric producer to distinguish when the same uid or app represents a
// different version of an app.
- void addListener(sp<PackageInfoListener> producer);
+ void addListener(wp<PackageInfoListener> producer);
// Remove the listener from the set of metric producers that subscribe to updates.
- void removeListener(sp<PackageInfoListener> producer);
+ void removeListener(wp<PackageInfoListener> producer);
// Informs uid map that a config is added/updated. Used for keeping mConfigKeys up to date.
void OnConfigUpdated(const ConfigKey& key);
@@ -100,9 +102,14 @@
void clearOutput();
// Get currently cached value of memory used by UID map.
- size_t getBytesUsed();
+ size_t getBytesUsed() const;
+
+ std::set<int32_t> getAppUid(const string& package) const;
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);
@@ -112,6 +119,8 @@
UidMapping getOutput(const int64_t& timestamp, const ConfigKey& key);
+ void getListenerListCopyLocked(std::vector<wp<PackageInfoListener>>* output);
+
// TODO: Use shared_mutex for improved read-locking if a library can be found in Android.
mutable mutex mMutex;
mutable mutex mIsolatedMutex;
@@ -128,7 +137,7 @@
UidMapping mOutput;
// Metric producers that should be notified if there's an upgrade in any app.
- set<sp<PackageInfoListener>> mSubscribers;
+ set<wp<PackageInfoListener>> mSubscribers;
// Mapping of config keys we're aware of to the epoch time they last received an update. This
// lets us know it's safe to delete events older than the oldest update. The value is nanosec.
@@ -160,4 +169,3 @@
} // namespace os
} // namespace android
-#endif // STATSD_UIDMAP_H
diff --git a/cmds/statsd/src/stats_util.cpp b/cmds/statsd/src/stats_util.cpp
deleted file mode 100644
index 7527a64..0000000
--- a/cmds/statsd/src/stats_util.cpp
+++ /dev/null
@@ -1,57 +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.
- */
-
-#include "stats_util.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-// There is no existing hash function for the dimension key ("repeated KeyValuePair").
-// Temporarily use a string concatenation as the hashable key.
-// TODO: Find a better hash function for std::vector<KeyValuePair>.
-HashableDimensionKey getHashableKey(std::vector<KeyValuePair> keys) {
- std::string flattened;
- for (const KeyValuePair& pair : keys) {
- flattened += std::to_string(pair.key());
- flattened += ":";
- switch (pair.value_case()) {
- case KeyValuePair::ValueCase::kValueStr:
- flattened += pair.value_str();
- break;
- case KeyValuePair::ValueCase::kValueInt:
- flattened += std::to_string(pair.value_int());
- break;
- case KeyValuePair::ValueCase::kValueLong:
- flattened += std::to_string(pair.value_long());
- break;
- case KeyValuePair::ValueCase::kValueBool:
- flattened += std::to_string(pair.value_bool());
- break;
- case KeyValuePair::ValueCase::kValueFloat:
- flattened += std::to_string(pair.value_float());
- break;
- default:
- break;
- }
- flattened += "|";
- }
- return flattened;
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 8fd1ea8c..1cdf031 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -16,8 +16,9 @@
#pragma once
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include <sstream>
+#include "HashableDimensionKey.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "logd/LogReader.h"
#include <unordered_map>
@@ -26,13 +27,11 @@
namespace os {
namespace statsd {
-#define DEFAULT_DIMENSION_KEY ""
+const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(vector<KeyValuePair>());
// Minimum bucket size in seconds
const long kMinBucketSizeSec = 5 * 60;
-typedef std::string HashableDimensionKey;
-
typedef std::map<std::string, HashableDimensionKey> ConditionKey;
typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
@@ -67,13 +66,6 @@
} EventKV;
typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<EventKV>> DimToEventKVMap;
-
-EventMetricData parse(log_msg msg);
-
-int getTagId(log_msg msg);
-
-std::string getHashableKey(std::vector<KeyValuePair> key);
-
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index a30b5f8..4729f6a 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -222,6 +222,11 @@
optional int64 trigger_if_sum_gt = 6;
}
+message AllowedLogSource {
+ repeated int32 uid = 1;
+ repeated string package = 2;
+}
+
message StatsdConfig {
optional string name = 1;
@@ -240,4 +245,6 @@
repeated Predicate predicate = 8;
repeated Alert alert = 9;
+
+ optional AllowedLogSource log_source = 10;
}
diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp
index b1924ea..3d923e2 100644
--- a/cmds/statsd/tests/ConfigManager_test.cpp
+++ b/cmds/statsd/tests/ConfigManager_test.cpp
@@ -62,8 +62,8 @@
}
TEST(ConfigManagerTest, TestFakeConfig) {
- auto metricsManager =
- std::make_unique<MetricsManager>(ConfigKey(0, "test"), build_fake_config(), 1000);
+ auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, "test"),
+ build_fake_config(), 1000, new UidMap());
EXPECT_TRUE(metricsManager->isConfigValid());
}
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index a5c8875..9b96bb7 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -41,7 +41,7 @@
*/
class MockMetricsManager : public MetricsManager {
public:
- MockMetricsManager() : MetricsManager(ConfigKey(1, "key"), StatsdConfig(), 1000) {
+ MockMetricsManager() : MetricsManager(ConfigKey(1, "key"), StatsdConfig(), 1000, new UidMap()) {
}
MOCK_METHOD0(byteSize, size_t());
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 5b2cedd..3fa96d3 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -74,6 +74,14 @@
EXPECT_TRUE(m.hasApp(1000, kApp1));
EXPECT_TRUE(m.hasApp(1000, kApp2));
EXPECT_FALSE(m.hasApp(1000, "not.app"));
+
+ std::set<string> name_set = m.getAppNamesFromUid(1000u, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ name_set = m.getAppNamesFromUid(12345, true /* returnNormalized */);
+ EXPECT_TRUE(name_set.empty());
}
TEST(UidMapTest, TestAddAndRemove) {
@@ -90,12 +98,59 @@
versions.push_back(5);
m.updateMap(uids, versions, apps);
+ std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ // Update the app1 version.
m.updateApp(String16(kApp1.c_str()), 1000, 40);
EXPECT_EQ(40, m.getAppVersion(1000, kApp1));
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
m.removeApp(String16(kApp1.c_str()), 1000);
EXPECT_FALSE(m.hasApp(1000, kApp1));
EXPECT_TRUE(m.hasApp(1000, kApp2));
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 1u);
+ EXPECT_TRUE(name_set.find(kApp1) == name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ // Remove app2.
+ m.removeApp(String16(kApp2.c_str()), 1000);
+ EXPECT_FALSE(m.hasApp(1000, kApp1));
+ EXPECT_FALSE(m.hasApp(1000, kApp2));
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_TRUE(name_set.empty());
+}
+
+TEST(UidMapTest, TestUpdateApp) {
+ UidMap m;
+ m.updateMap({1000, 1000}, {4, 5}, {String16(kApp1.c_str()), String16(kApp2.c_str())});
+ std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ // Adds a new name for uid 1000.
+ m.updateApp(String16("NeW_aPP1_NAmE"), 1000, 40);
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 3u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+ EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
+ EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
+
+ // This name is also reused by another uid 2000.
+ m.updateApp(String16("NeW_aPP1_NAmE"), 2000, 1);
+ name_set = m.getAppNamesFromUid(2000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 1u);
+ EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
+ EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
}
TEST(UidMapTest, TestClearingOutput) {
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index f385763..f62171d 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -13,6 +13,7 @@
// limitations under the License.
#include "src/anomaly/AnomalyTracker.h"
+#include "../metrics/metrics_test_helper.h"
#include <gtest/gtest.h>
#include <stdio.h>
@@ -32,7 +33,18 @@
const ConfigKey kConfigKey(0, "test");
-void AddValueToBucket(const std::vector<std::pair<string, long>>& key_value_pair_list,
+HashableDimensionKey getMockDimensionKey(int key, string value) {
+ KeyValuePair pair;
+ pair.set_key(key);
+ pair.set_value_str(value);
+
+ vector<KeyValuePair> pairs;
+ pairs.push_back(pair);
+
+ return HashableDimensionKey(pairs);
+}
+
+void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list,
std::shared_ptr<DimToValMap> bucket) {
for (auto itr = key_value_pair_list.begin(); itr != key_value_pair_list.end(); itr++) {
(*bucket)[itr->first] += itr->second;
@@ -40,7 +52,7 @@
}
std::shared_ptr<DimToValMap> MockBucket(
- const std::vector<std::pair<string, long>>& key_value_pair_list) {
+ const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list) {
std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
AddValueToBucket(key_value_pair_list, bucket);
return bucket;
@@ -54,20 +66,23 @@
alert.set_trigger_if_sum_gt(2);
AnomalyTracker anomalyTracker(alert, kConfigKey);
+ HashableDimensionKey keyA = getMockDimensionKey(1, "a");
+ HashableDimensionKey keyB = getMockDimensionKey(1, "b");
+ HashableDimensionKey keyC = getMockDimensionKey(1, "c");
- std::shared_ptr<DimToValMap> bucket0 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
+ std::shared_ptr<DimToValMap> bucket0 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
int64_t eventTimestamp0 = 10;
- std::shared_ptr<DimToValMap> bucket1 = MockBucket({{"a", 1}});
+ std::shared_ptr<DimToValMap> bucket1 = MockBucket({{keyA, 1}});
int64_t eventTimestamp1 = bucketSizeNs + 11;
- std::shared_ptr<DimToValMap> bucket2 = MockBucket({{"b", 1}});
+ std::shared_ptr<DimToValMap> bucket2 = MockBucket({{keyB, 1}});
int64_t eventTimestamp2 = 2 * bucketSizeNs + 12;
- std::shared_ptr<DimToValMap> bucket3 = MockBucket({{"a", 2}});
+ std::shared_ptr<DimToValMap> bucket3 = MockBucket({{keyA, 2}});
int64_t eventTimestamp3 = 3 * bucketSizeNs + 13;
- std::shared_ptr<DimToValMap> bucket4 = MockBucket({{"b", 1}});
+ std::shared_ptr<DimToValMap> bucket4 = MockBucket({{keyB, 1}});
int64_t eventTimestamp4 = 4 * bucketSizeNs + 14;
- std::shared_ptr<DimToValMap> bucket5 = MockBucket({{"a", 2}});
+ std::shared_ptr<DimToValMap> bucket5 = MockBucket({{keyA, 2}});
int64_t eventTimestamp5 = 5 * bucketSizeNs + 15;
- std::shared_ptr<DimToValMap> bucket6 = MockBucket({{"a", 2}});
+ std::shared_ptr<DimToValMap> bucket6 = MockBucket({{keyA, 2}});
int64_t eventTimestamp6 = 6 * bucketSizeNs + 16;
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u);
@@ -79,9 +94,9 @@
// Adds past bucket #0
anomalyTracker.addPastBucket(bucket0, 0);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1);
@@ -90,9 +105,9 @@
// Adds past bucket #0 again. The sum does not change.
anomalyTracker.addPastBucket(bucket0, 0);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1);
@@ -102,9 +117,9 @@
anomalyTracker.addPastBucket(bucket1, 1);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
@@ -113,9 +128,9 @@
anomalyTracker.addPastBucket(bucket1, 1);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
@@ -124,8 +139,8 @@
anomalyTracker.addPastBucket(bucket2, 2);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 2L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(3, *bucket3));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3);
// Within refractory period.
@@ -135,8 +150,8 @@
anomalyTracker.addPastBucket(bucket3, 3L);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 3L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(4, *bucket4));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
@@ -145,8 +160,8 @@
anomalyTracker.addPastBucket(bucket4, 4);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(5, *bucket5));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
@@ -155,8 +170,8 @@
anomalyTracker.addPastBucket(bucket5, 5);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(6, *bucket6));
// Within refractory period.
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6);
@@ -171,13 +186,18 @@
alert.set_trigger_if_sum_gt(2);
AnomalyTracker anomalyTracker(alert, kConfigKey);
+ HashableDimensionKey keyA = getMockDimensionKey(1, "a");
+ HashableDimensionKey keyB = getMockDimensionKey(1, "b");
+ HashableDimensionKey keyC = getMockDimensionKey(1, "c");
+ HashableDimensionKey keyD = getMockDimensionKey(1, "d");
+ HashableDimensionKey keyE = getMockDimensionKey(1, "e");
- std::shared_ptr<DimToValMap> bucket9 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
- std::shared_ptr<DimToValMap> bucket16 = MockBucket({{"b", 4}});
- std::shared_ptr<DimToValMap> bucket18 = MockBucket({{"b", 1}, {"c", 1}});
- std::shared_ptr<DimToValMap> bucket20 = MockBucket({{"b", 3}, {"c", 1}});
- std::shared_ptr<DimToValMap> bucket25 = MockBucket({{"d", 1}});
- std::shared_ptr<DimToValMap> bucket28 = MockBucket({{"e", 2}});
+ std::shared_ptr<DimToValMap> bucket9 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
+ std::shared_ptr<DimToValMap> bucket16 = MockBucket({{keyB, 4}});
+ std::shared_ptr<DimToValMap> bucket18 = MockBucket({{keyB, 1}, {keyC, 1}});
+ std::shared_ptr<DimToValMap> bucket20 = MockBucket({{keyB, 3}, {keyC, 1}});
+ std::shared_ptr<DimToValMap> bucket25 = MockBucket({{keyD, 1}});
+ std::shared_ptr<DimToValMap> bucket28 = MockBucket({{keyE, 2}});
int64_t eventTimestamp1 = bucketSizeNs * 8 + 1;
int64_t eventTimestamp2 = bucketSizeNs * 15 + 11;
@@ -196,9 +216,9 @@
anomalyTracker.addPastBucket(bucket9, 9);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 9L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(16, *bucket16));
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
@@ -211,27 +231,27 @@
anomalyTracker.addPastBucket(bucket16, 16);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(18, *bucket18));
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
// Within refractory period.
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
// Add past bucket #18
anomalyTracker.addPastBucket(bucket18, 18);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 18L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
@@ -239,12 +259,12 @@
anomalyTracker.addPastBucket(bucket18, 18);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20);
// Within refractory period.
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
@@ -253,8 +273,8 @@
anomalyTracker.addPastBucket(bucket20, 20);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 20L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 3LL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 3LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(25, *bucket25));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
@@ -265,7 +285,7 @@
anomalyTracker.addPastBucket(bucket25, 25);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
- EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("d"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyD), 1LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(28, *bucket28));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
@@ -274,7 +294,7 @@
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
// Updates current bucket #28.
- (*bucket28)["e"] = 5;
+ (*bucket28)[keyE] = 5;
EXPECT_TRUE(anomalyTracker.detectAnomaly(28, *bucket28));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 01ba82d..eb0fafe 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -63,7 +63,7 @@
vector<KeyValuePair> kv_list;
kv_list.push_back(kv1);
map<string, HashableDimensionKey> queryKey;
- queryKey[conditionName] = getHashableKey(kv_list);
+ queryKey[conditionName] = HashableDimensionKey(kv_list);
return queryKey;
}
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 51eabd5..eec94539 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "metrics_test_helper.h"
#include "src/metrics/CountMetricProducer.h"
+#include "metrics_test_helper.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -150,13 +150,13 @@
event1.write("111"); // uid
event1.init();
ConditionKey key1;
- key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
+ key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111");
LogEvent event2(1, bucketStartTimeNs + 10);
event2.write("222"); // uid
event2.init();
ConditionKey key2;
- key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
+ key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222");
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index e4fc67f..baaac67 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "metrics_test_helper.h"
#include "src/metrics/EventMetricProducer.h"
+#include "metrics_test_helper.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -100,13 +100,13 @@
event1.write("111"); // uid
event1.init();
ConditionKey key1;
- key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
+ key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111");
LogEvent event2(1, bucketStartTimeNs + 10);
event2.write("222"); // uid
event2.init();
ConditionKey key2;
- key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
+ key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222");
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 68b7dcb..5204834 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "src/metrics/GaugeMetricProducer.h"
#include "logd/LogEvent.h"
#include "metrics_test_helper.h"
-#include "src/metrics/GaugeMetricProducer.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 4e5e0d6..7dac0fb 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "src/metrics/duration_helper/MaxDurationTracker.h"
#include "metrics_test_helper.h"
#include "src/condition/ConditionWizard.h"
-#include "src/metrics/duration_helper/MaxDurationTracker.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -37,13 +37,18 @@
namespace statsd {
const ConfigKey kConfigKey(0, "test");
-const string eventKey = "event";
+
+const HashableDimensionKey eventKey = getMockedDimensionKey(0, "1");
+const HashableDimensionKey conditionKey = getMockedDimensionKey(4, "1");
+const HashableDimensionKey key1 = getMockedDimensionKey(1, "1");
+const HashableDimensionKey key2 = getMockedDimensionKey(1, "2");
TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
- ConditionKey key1;
+ ConditionKey conditionKey1;
+ conditionKey1["condition"] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -51,15 +56,15 @@
MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
bucketSizeNs, {});
- tracker.noteStart("1", true, bucketStartTimeNs, key1);
+ tracker.noteStart(key1, true, bucketStartTimeNs, conditionKey1);
// Event starts again. This would not change anything as it already starts.
- tracker.noteStart("1", true, bucketStartTimeNs + 3, key1);
+ tracker.noteStart(key1, true, bucketStartTimeNs + 3, conditionKey1);
// Stopped.
- tracker.noteStop("1", bucketStartTimeNs + 10, false);
+ tracker.noteStop(key1, bucketStartTimeNs + 10, false);
// Another event starts in this bucket.
- tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
- tracker.noteStop("2", bucketStartTimeNs + 40, false /*stop all*/);
+ tracker.noteStart(key2, true, bucketStartTimeNs + 20, conditionKey1);
+ tracker.noteStop(key2, bucketStartTimeNs + 40, false /*stop all*/);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -71,7 +76,8 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
- ConditionKey key1;
+ ConditionKey conditionKey1;
+ conditionKey1["condition"] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -79,10 +85,10 @@
MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
bucketSizeNs, {});
- tracker.noteStart("1", true, bucketStartTimeNs + 1, key1);
+ tracker.noteStart(key1, true, bucketStartTimeNs + 1, conditionKey1);
// Another event starts in this bucket.
- tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
+ tracker.noteStart(key2, true, bucketStartTimeNs + 20, conditionKey1);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, &buckets);
tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
EXPECT_TRUE(tracker.mInfos.empty());
@@ -101,7 +107,8 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
- ConditionKey key1;
+ ConditionKey conditionKey1;
+ conditionKey1["condition"] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -110,14 +117,16 @@
bucketSizeNs, {});
// The event starts.
- tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
+ tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, conditionKey1);
// Starts again. Does not change anything.
- tracker.noteStart("", true, bucketStartTimeNs + bucketSizeNs + 1, key1);
+ tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1,
+ conditionKey1);
// The event stops at early 4th bucket.
tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, &buckets);
- tracker.noteStop("", bucketStartTimeNs + (3 * bucketSizeNs) + 20, false /*stop all*/);
+ tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (3 * bucketSizeNs) + 20,
+ false /*stop all*/);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
EXPECT_EQ(3u, buckets[eventKey].size());
EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[eventKey][0].mDuration);
@@ -129,7 +138,8 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
- ConditionKey key1;
+ ConditionKey conditionKey1;
+ conditionKey1["condition"] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -138,10 +148,10 @@
bucketSizeNs, {});
// 2 starts
- tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
- tracker.noteStart("", true, bucketStartTimeNs + 10, key1);
+ tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, conditionKey1);
+ tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 10, conditionKey1);
// one stop
- tracker.noteStop("", bucketStartTimeNs + 20, false /*stop all*/);
+ tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/);
tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1, &buckets);
@@ -151,7 +161,7 @@
EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
// real stop now.
- tracker.noteStop("", bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
+ tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets);
EXPECT_EQ(3u, buckets[eventKey].size());
@@ -163,10 +173,11 @@
TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
- ConditionKey key1;
- key1["APP_BACKGROUND"] = "1:maps|";
+ ConditionKey conditionKey1;
+ HashableDimensionKey eventKey = getMockedDimensionKey(2, "maps");
+ conditionKey1["APP_BACKGROUND"] = conditionKey;
- EXPECT_CALL(*wizard, query(_, key1)) // #4
+ EXPECT_CALL(*wizard, query(_, conditionKey1)) // #4
.WillOnce(Return(ConditionState::kFalse));
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -180,11 +191,11 @@
bucketSizeNs, {});
EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
- tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+ tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
- tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
+ tracker.noteStop(key1, eventStartTimeNs + durationTimeNs, false);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -195,15 +206,15 @@
TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
Alert alert;
alert.set_name("alert");
- alert.set_metric_name("1");
+ alert.set_metric_name("metric");
alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
alert.set_number_of_buckets(2);
alert.set_refractory_period_secs(1);
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
- ConditionKey key1;
- key1["APP_BACKGROUND"] = "1:maps|";
+ ConditionKey conditionKey1;
+ conditionKey1["APP_BACKGROUND"] = conditionKey;
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
@@ -212,14 +223,14 @@
MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs,
bucketSizeNs, {anomalyTracker});
- tracker.noteStart("1", true, eventStartTimeNs, key1);
- tracker.noteStop("1", eventStartTimeNs + 10, false);
+ tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
+ tracker.noteStop(key1, eventStartTimeNs + 10, false);
EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
EXPECT_EQ(10LL, tracker.mDuration);
- tracker.noteStart("2", true, eventStartTimeNs + 20, key1);
+ tracker.noteStart(key2, true, eventStartTimeNs + 20, conditionKey1);
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, &buckets);
- tracker.noteStop("2", eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
+ tracker.noteStop(key2, eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration);
EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs,
(long long)(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC));
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 99d3e05..9ec302f 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "src/metrics/duration_helper/OringDurationTracker.h"
#include "metrics_test_helper.h"
#include "src/condition/ConditionWizard.h"
-#include "src/metrics/duration_helper/OringDurationTracker.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -35,13 +35,17 @@
namespace statsd {
const ConfigKey kConfigKey(0, "test");
-const string eventKey = "event";
+const HashableDimensionKey eventKey = getMockedDimensionKey(0, "event");
+
+const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(1, "maps");
+const HashableDimensionKey kEventKey1 = getMockedDimensionKey(2, "maps");
+const HashableDimensionKey kEventKey2 = getMockedDimensionKey(3, "maps");
TEST(OringDurationTrackerTest, TestDurationOverlap) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = "1:maps|";
+ key1["APP_BACKGROUND"] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -53,12 +57,12 @@
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
bucketStartTimeNs, bucketSizeNs, {});
- tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
- tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, key1); // overlapping wl
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
- tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
+ tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -70,7 +74,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = "1:maps|";
+ key1["APP_BACKGROUND"] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -81,11 +85,11 @@
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
- tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
- tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, key1); // overlapping wl
- tracker.noteStop("2:maps", eventStartTimeNs + 2000, false);
- tracker.noteStop("2:maps", eventStartTimeNs + 2003, false);
+ tracker.noteStop(kEventKey1, eventStartTimeNs + 2000, false);
+ tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -97,7 +101,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = "1:maps|";
+ key1["APP_BACKGROUND"] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -108,8 +112,8 @@
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
- tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
- tracker.noteStart("3:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
+ tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, key1); // overlapping wl
tracker.noteStopAll(eventStartTimeNs + 2003);
@@ -123,7 +127,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = "1:maps|";
+ key1["APP_BACKGROUND"] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -135,18 +139,18 @@
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
- tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs, &buckets);
- tracker.noteStart("2:maps", true, eventStartTimeNs + 2 * bucketSizeNs, key1);
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, key1);
EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
EXPECT_EQ(2u, buckets[eventKey].size());
EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
- tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 10, false);
- tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 12, false);
+ tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 10, false);
+ tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 12, false);
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
EXPECT_EQ(2u, buckets[eventKey].size());
@@ -158,7 +162,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = "1:maps|";
+ key1["APP_BACKGROUND"] = kConditionKey1;
EXPECT_CALL(*wizard, query(_, key1)) // #4
.WillOnce(Return(ConditionState::kFalse));
@@ -173,11 +177,11 @@
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
bucketStartTimeNs, bucketSizeNs, {});
- tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
- tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
+ tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -189,7 +193,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = "1:maps|";
+ key1["APP_BACKGROUND"] = kConditionKey1;
EXPECT_CALL(*wizard, query(_, key1))
.Times(2)
@@ -206,13 +210,13 @@
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
bucketStartTimeNs, bucketSizeNs, {});
- tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
// condition to false; record duration 5n
tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
// condition to true.
tracker.onSlicedConditionMayChange(eventStartTimeNs + 1000);
// 2nd duration: 1000ns
- tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
+ tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -224,7 +228,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = "1:maps|";
+ key1["APP_BACKGROUND"] = kConditionKey1;
EXPECT_CALL(*wizard, query(_, key1)) // #4
.WillOnce(Return(ConditionState::kFalse));
@@ -238,14 +242,14 @@
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
- tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
- tracker.noteStart("2:maps", true, eventStartTimeNs + 2, key1);
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
+ tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
- tracker.noteStop("2:maps", eventStartTimeNs + 3, false);
+ tracker.noteStop(kEventKey1, eventStartTimeNs + 3, false);
tracker.onSlicedConditionMayChange(eventStartTimeNs + 15);
- tracker.noteStop("2:maps", eventStartTimeNs + 2003, false);
+ tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -264,7 +268,7 @@
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = "1:maps|";
+ key1["APP_BACKGROUND"] = kConditionKey1;
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
@@ -274,22 +278,22 @@
bucketSizeNs, {anomalyTracker});
// Nothing in the past bucket.
- tracker.noteStart("", true, eventStartTimeNs, key1);
+ tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, key1);
EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs),
tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
- tracker.noteStop("", eventStartTimeNs + 3, false);
+ tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 3, false);
EXPECT_EQ(0u, buckets[eventKey].size());
uint64_t event1StartTimeNs = eventStartTimeNs + 10;
- tracker.noteStart("1", true, event1StartTimeNs, key1);
+ tracker.noteStart(kEventKey1, true, event1StartTimeNs, key1);
// No past buckets. The anomaly will happen in bucket #0.
EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3),
tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs));
uint64_t event1StopTimeNs = eventStartTimeNs + bucketSizeNs + 10;
tracker.flushIfNeeded(event1StopTimeNs, &buckets);
- tracker.noteStop("1", event1StopTimeNs, false);
+ tracker.noteStop(kEventKey1, event1StopTimeNs, false);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
EXPECT_EQ(1u, buckets[eventKey].size());
@@ -301,16 +305,16 @@
// One past buckets. The anomaly will happen in bucket #1.
uint64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15;
- tracker.noteStart("1", true, event2StartTimeNs, key1);
+ tracker.noteStart(kEventKey1, true, event2StartTimeNs, key1);
EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration -
bucket1Duration),
tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
- tracker.noteStop("1", event2StartTimeNs + 1, false);
+ tracker.noteStop(kEventKey1, event2StartTimeNs + 1, false);
// Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in
// bucket #2.
uint64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC;
- tracker.noteStart("1", true, event3StartTimeNs, key1);
+ tracker.noteStart(kEventKey1, true, event3StartTimeNs, key1);
EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL),
tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
}
@@ -326,7 +330,7 @@
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = "1:maps|";
+ key1["APP_BACKGROUND"] = kConditionKey1;
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
@@ -335,21 +339,21 @@
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true /*nesting*/,
bucketStartTimeNs, bucketSizeNs, {anomalyTracker});
- tracker.noteStart("", true, eventStartTimeNs, key1);
- tracker.noteStop("", eventStartTimeNs + 10, false);
+ tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, key1);
+ tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 10, false);
EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
EXPECT_TRUE(tracker.mStarted.empty());
EXPECT_EQ(10LL, tracker.mDuration);
EXPECT_EQ(0u, tracker.mStarted.size());
- tracker.noteStart("", true, eventStartTimeNs + 20, key1);
+ tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs + 20, key1);
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
EXPECT_EQ((long long)(51ULL * NS_PER_SEC),
(long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 25, &buckets);
- tracker.noteStop("", eventStartTimeNs + 2 * bucketSizeNs + 25, false);
- EXPECT_EQ(anomalyTracker->getSumOverPastBuckets("event"), (long long)(bucketSizeNs));
+ tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 2 * bucketSizeNs + 25, false);
+ EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs));
EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25),
anomalyTracker->mLastAlarmTimestampNs);
}
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.cpp b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
new file mode 100644
index 0000000..a0a854a
--- /dev/null
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
@@ -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.
+
+#include "metrics_test_helper.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+HashableDimensionKey getMockedDimensionKey(int key, string value) {
+ KeyValuePair pair;
+ pair.set_key(key);
+ pair.set_value_str(value);
+
+ vector<KeyValuePair> pairs;
+ pairs.push_back(pair);
+
+ return HashableDimensionKey(pairs);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index fa221aa..7cb3329 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -38,6 +38,8 @@
MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data));
};
+HashableDimensionKey getMockedDimensionKey(int key, std::string value);
+
} // namespace statsd
} // namespace os
-} // namespace android
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk
index 6b0531d..7bd15d7 100644
--- a/cmds/statsd/tools/dogfood/Android.mk
+++ b/cmds/statsd/tools/dogfood/Android.mk
@@ -21,12 +21,12 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += ../../src/stats_log.proto \
../../src/atoms.proto
+
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../../src/
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite
LOCAL_PROTOC_OPTIMIZE_TYPE := lite
-LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
LOCAL_DEX_PREOPT := false
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml
index cd76c9d..7bfde40 100644
--- a/cmds/statsd/tools/dogfood/AndroidManifest.xml
+++ b/cmds/statsd/tools/dogfood/AndroidManifest.xml
@@ -18,7 +18,6 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.statsd.dogfood"
- android:sharedUserId="android.uid.system"
android:versionCode="1"
android:versionName="1.0" >
diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
index d5b8fed..0329992 100644
--- a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
+++ b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
Binary files differ
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
index a0fb6ee..3a355d9 100644
--- a/core/java/android/app/DialogFragment.java
+++ b/core/java/android/app/DialogFragment.java
@@ -137,7 +137,9 @@
* {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
* embed}
*
- * @deprecated Use {@link android.support.v4.app.DialogFragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.DialogFragment} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public class DialogFragment extends Fragment
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index a92684b..4ff07f2 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -257,7 +257,9 @@
* pressing back will pop it to return the user to whatever previous state
* the activity UI was in.
*
- * @deprecated Use {@link android.support.v4.app.Fragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.Fragment} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener {
diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java
index a1dd32f..536c866 100644
--- a/core/java/android/app/FragmentContainer.java
+++ b/core/java/android/app/FragmentContainer.java
@@ -25,7 +25,8 @@
/**
* Callbacks to a {@link Fragment}'s container.
*
- * @deprecated Use {@link android.support.v4.app.FragmentContainer}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentContainer}.
*/
@Deprecated
public abstract class FragmentContainer {
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index cbb58d4..40bc248 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -38,7 +38,8 @@
* It is the responsibility of the host to take care of the Fragment's lifecycle.
* The methods provided by {@link FragmentController} are for that purpose.
*
- * @deprecated Use {@link android.support.v4.app.FragmentController}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentController}
*/
@Deprecated
public class FragmentController {
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index 1edc68e..b48817b 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -38,7 +38,8 @@
* host fragments, implement {@link FragmentHostCallback}, overriding the methods
* applicable to the host.
*
- * @deprecated Use {@link android.support.v4.app.FragmentHostCallback}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentHostCallback}
*/
@Deprecated
public abstract class FragmentHostCallback<E> extends FragmentContainer {
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 12e60b8..708450f 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -75,7 +75,9 @@
* <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
* Fragments For All</a> for more details.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentManager} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public abstract class FragmentManager {
@@ -90,7 +92,8 @@
* the identifier as returned by {@link #getId} is the only thing that
* will be persisted across activity instances.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager.BackStackEntry}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a> {@link android.support.v4.app.FragmentManager.BackStackEntry}
*/
@Deprecated
public interface BackStackEntry {
@@ -136,7 +139,9 @@
/**
* Interface to watch for changes to the back stack.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a>
+ * {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
*/
@Deprecated
public interface OnBackStackChangedListener {
@@ -438,7 +443,9 @@
* Callback interface for listening to fragment state changes that happen
* within a given FragmentManager.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a>
+ * {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
*/
@Deprecated
public abstract static class FragmentLifecycleCallbacks {
diff --git a/core/java/android/app/FragmentManagerNonConfig.java b/core/java/android/app/FragmentManagerNonConfig.java
index beb1a15..326438a 100644
--- a/core/java/android/app/FragmentManagerNonConfig.java
+++ b/core/java/android/app/FragmentManagerNonConfig.java
@@ -28,7 +28,8 @@
* {@link FragmentController#retainNonConfig()} and
* {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
*
- * @deprecated Use {@link android.support.v4.app.FragmentManagerNonConfig}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentManagerNonConfig}
*/
@Deprecated
public class FragmentManagerNonConfig {
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 1103649..713a559 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -22,7 +22,8 @@
* guide.</p>
* </div>
*
- * @deprecated Use {@link android.support.v4.app.FragmentTransaction}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentTransaction}
*/
@Deprecated
public abstract class FragmentTransaction {
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 41324d0..b469de5 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1209,6 +1209,11 @@
}
private AppComponentFactory getFactory(String pkg) {
+ if (mThread == null) {
+ Log.e(TAG, "Uninitialized ActivityThread, likely app-created Instrumentation,"
+ + " disabling AppComponentFactory", new Throwable());
+ return AppComponentFactory.DEFAULT;
+ }
LoadedApk loadedApk = mThread.peekLoadedApk(pkg, true);
// This is in the case of starting up "android".
if (loadedApk == null) loadedApk = mThread.getSystemContext().mLoadedApk;
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 90b77b3..7790f70 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -145,7 +145,9 @@
* @see #setListAdapter
* @see android.widget.ListView
*
- * @deprecated Use {@link android.support.v4.app.ListFragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.ListFragment} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public class ListFragment extends Fragment {
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 7969684..86d0fd6 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -55,14 +55,16 @@
* <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
* </div>
*
- * @deprecated Use {@link android.support.v4.app.LoaderManager}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.LoaderManager}
*/
@Deprecated
public abstract class LoaderManager {
/**
* Callback interface for a client to interact with the manager.
*
- * @deprecated Use {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a> {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
*/
@Deprecated
public interface LoaderCallbacks<D> {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9816297..70e1a96 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3213,23 +3213,6 @@
}
/**
- * Returns maximum time to lock that applied by all profiles in this user. We do this because we
- * do not have a separate timeout to lock for work challenge only.
- *
- * @hide
- */
- public long getMaximumTimeToLockForUserAndProfiles(int userHandle) {
- if (mService != null) {
- try {
- return mService.getMaximumTimeToLockForUserAndProfiles(userHandle);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- return 0;
- }
-
- /**
* Called by a device/profile owner to set the timeout after which unlocking with secondary, non
* strong auth (e.g. fingerprint, trust agents) times out, i.e. the user has to use a strong
* authentication method like password, pin or pattern.
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 05f6c2a..b692ffd9 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,6 +16,7 @@
package android.app.admin;
+import android.annotation.UserIdInt;
import android.content.Intent;
import java.util.List;
@@ -115,4 +116,11 @@
* device owner to be affiliated with.
*/
public abstract boolean isUserAffiliatedWithDevice(int userId);
+
+ /**
+ * Reports that a profile has changed to use a unified or separate credential.
+ *
+ * @param userId User ID of the profile.
+ */
+ public abstract void reportSeparateProfileChallengeChanged(@UserIdInt int userId);
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 5b02c22..7cf19ee 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -95,7 +95,6 @@
void setMaximumTimeToLock(in ComponentName who, long timeMs, boolean parent);
long getMaximumTimeToLock(in ComponentName who, int userHandle, boolean parent);
- long getMaximumTimeToLockForUserAndProfiles(int userHandle);
void setRequiredStrongAuthTimeout(in ComponentName who, long timeMs, boolean parent);
long getRequiredStrongAuthTimeout(in ComponentName who, int userId, boolean parent);
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 0f93c59..d3b66d0 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -17,6 +17,7 @@
package android.app.admin;
import android.annotation.IntDef;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
@@ -26,6 +27,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
+import java.util.Objects;
/**
* Definitions for working with security logs.
@@ -135,9 +137,28 @@
*/
public static final class SecurityEvent implements Parcelable {
private Event mEvent;
+ private long mId;
+
+ /**
+ * Constructor used by native classes to generate SecurityEvent instances.
+ * @hide
+ */
+ /* package */ SecurityEvent(byte[] data) {
+ this(0, data);
+ }
+
+ /**
+ * Constructor used by Parcelable.Creator to generate SecurityEvent instances.
+ * @hide
+ */
+ /* package */ SecurityEvent(Parcel source) {
+ this(source.readLong(), source.createByteArray());
+ }
/** @hide */
- /*package*/ SecurityEvent(byte[] data) {
+ @TestApi
+ public SecurityEvent(long id, byte[] data) {
+ mId = id;
mEvent = Event.fromBytes(data);
}
@@ -162,6 +183,21 @@
return mEvent.getData();
}
+ /**
+ * @hide
+ */
+ public void setId(long id) {
+ this.mId = id;
+ }
+
+ /**
+ * Returns the id of the event, where the id monotonically increases for each event. The id
+ * is reset when the device reboots, and when security logging is enabled.
+ */
+ public long getId() {
+ return mId;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -169,6 +205,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mId);
dest.writeByteArray(mEvent.getBytes());
}
@@ -176,7 +213,7 @@
new Parcelable.Creator<SecurityEvent>() {
@Override
public SecurityEvent createFromParcel(Parcel source) {
- return new SecurityEvent(source.createByteArray());
+ return new SecurityEvent(source);
}
@Override
@@ -193,7 +230,7 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SecurityEvent other = (SecurityEvent) o;
- return mEvent.equals(other.mEvent);
+ return mEvent.equals(other.mEvent) && mId == other.mId;
}
/**
@@ -201,7 +238,7 @@
*/
@Override
public int hashCode() {
- return mEvent.hashCode();
+ return Objects.hash(mEvent, mId);
}
}
/**
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index 1aff7e9..5c7f674 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -63,7 +63,7 @@
HINT_ACTIONS,
HINT_SELECTED,
HINT_NO_TINT,
- HINT_HIDDEN,
+ HINT_SHORTCUT,
HINT_TOGGLE,
HINT_HORIZONTAL,
HINT_PARTIAL,
@@ -118,12 +118,10 @@
*/
public static final String HINT_NO_TINT = "no_tint";
/**
- * Hint to indicate that this content should not be shown in larger renderings
- * of Slices. This content may be used to populate the shortcut/icon
- * format of the slice.
- * @hide
+ * Hint to indicate that this content should only be displayed if the slice is presented
+ * as a shortcut.
*/
- public static final String HINT_HIDDEN = "hidden";
+ public static final String HINT_SHORTCUT = "shortcut";
/**
* Hint indicating this content should be shown instead of the normal content when the slice
* is in small format.
@@ -182,6 +180,10 @@
* 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 f38e462..2fab305 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -85,7 +85,7 @@
*
* @see BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)
* @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
- * @see BluetoothHidDeviceCallback#onIntrData(BluetoothDevice, byte, byte[])
+ * @see BluetoothHidDeviceCallback#onInterruptData(BluetoothDevice, byte, byte[])
*/
public static final byte REPORT_TYPE_INPUT = (byte) 1;
public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
@@ -155,8 +155,8 @@
}
@Override
- public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
- mCallback.onIntrData(device, reportId, data);
+ public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+ mCallback.onInterruptData(device, reportId, data);
}
@Override
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
index bd19955..e71b00f 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -106,8 +106,8 @@
* @param reportId Report Id.
* @param data Report data.
*/
- public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
- Log.d(TAG, "onIntrData: device=" + device + " reportId=" + reportId);
+ public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+ Log.d(TAG, "onInterruptData: device=" + device + " reportId=" + reportId);
}
/**
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 6e9f09c..c44e356 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -50,7 +50,8 @@
*
* @param <D> the data type to be loaded.
*
- * @deprecated Use {@link android.support.v4.content.AsyncTaskLoader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.AsyncTaskLoader}
*/
@Deprecated
public abstract class AsyncTaskLoader<D> extends Loader<D> {
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 7f24c51..5a08636 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -39,7 +39,8 @@
* {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
* and {@link #setProjection(String[])}.
*
- * @deprecated Use {@link android.support.v4.content.CursorLoader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.CursorLoader}
*/
@Deprecated
public class CursorLoader extends AsyncTaskLoader<Cursor> {
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index 80f9a14..b0555d4 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -49,7 +49,8 @@
*
* @param <D> The result returned when the load is complete
*
- * @deprecated Use {@link android.support.v4.content.Loader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.Loader}
*/
@Deprecated
public class Loader<D> {
@@ -561,4 +562,4 @@
writer.print(" mReset="); writer.println(mReset);
}
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ff02c40..2d72632 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2075,6 +2075,13 @@
public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports cell-broadcast reception using the MBMS APIs.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports connecting to USB devices
* as the USB host.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 21e203b..77eb57f2 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1558,22 +1558,31 @@
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
- boolean systemDir = (parseFlags & PARSE_IS_SYSTEM_DIR) != 0;
int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME;
if (pkg.applicationInfo.isStaticSharedLibrary()) {
// must use v2 signing scheme
minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2;
}
- ApkSignatureVerifier.Result verified =
- ApkSignatureVerifier.verify(apkPath, minSignatureScheme, systemDir);
+ ApkSignatureVerifier.Result verified;
+ if ((parseFlags & PARSE_IS_SYSTEM_DIR) != 0) {
+ // systemDir APKs are already trusted, save time by not verifying
+ verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
+ apkPath, minSignatureScheme);
+ } else {
+ verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme);
+ }
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);
+ "No APK Signature Scheme v2 signature in ephemeral package " + apkPath);
}
}
+
+ // Verify that entries are signed consistently with the first pkg
+ // we encountered. Note that for splits, certificates may have
+ // already been populated during an earlier parse of a base APK.
if (pkg.mCertificates == null) {
pkg.mCertificates = verified.certs;
pkg.mSignatures = verified.sigs;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 0262ecb..77da2a5 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -21,15 +21,18 @@
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
+import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.utils.HashCodeHelpers;
import android.hardware.camera2.utils.TypeReference;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
import android.view.Surface;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -198,7 +201,24 @@
}
}
- private final HashSet<Surface> mSurfaceSet;
+ private final String TAG = "CaptureRequest-JV";
+
+ private final ArraySet<Surface> mSurfaceSet = new ArraySet<Surface>();
+
+ // Speed up sending CaptureRequest across IPC:
+ // mSurfaceConverted should only be set to true during capture request
+ // submission by {@link #convertSurfaceToStreamId}. The method will convert
+ // surfaces to stream/surface indexes based on passed in stream configuration at that time.
+ // This will save significant unparcel time for remote camera device.
+ // Once the request is submitted, camera device will call {@link #recoverStreamIdToSurface}
+ // to reset the capture request back to its original state.
+ private final Object mSurfacesLock = new Object();
+ private boolean mSurfaceConverted = false;
+ private int[] mStreamIdxArray;
+ private int[] mSurfaceIdxArray;
+
+ private static final ArraySet<Surface> mEmptySurfaceSet = new ArraySet<Surface>();
+
private final CameraMetadataNative mSettings;
private boolean mIsReprocess;
// If this request is part of constrained high speed request list that was created by
@@ -218,7 +238,6 @@
private CaptureRequest() {
mSettings = new CameraMetadataNative();
setNativeInstance(mSettings);
- mSurfaceSet = new HashSet<Surface>();
mIsReprocess = false;
mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE;
}
@@ -232,7 +251,7 @@
private CaptureRequest(CaptureRequest source) {
mSettings = new CameraMetadataNative(source.mSettings);
setNativeInstance(mSettings);
- mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone();
+ mSurfaceSet.addAll(source.mSurfaceSet);
mIsReprocess = source.mIsReprocess;
mIsPartOfCHSRequestList = source.mIsPartOfCHSRequestList;
mReprocessableSessionId = source.mReprocessableSessionId;
@@ -263,7 +282,6 @@
int reprocessableSessionId) {
mSettings = CameraMetadataNative.move(settings);
setNativeInstance(mSettings);
- mSurfaceSet = new HashSet<Surface>();
mIsReprocess = isReprocess;
if (isReprocess) {
if (reprocessableSessionId == CameraCaptureSession.SESSION_ID_NONE) {
@@ -463,22 +481,25 @@
private void readFromParcel(Parcel in) {
mSettings.readFromParcel(in);
setNativeInstance(mSettings);
-
- mSurfaceSet.clear();
-
- Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader());
-
- if (parcelableArray == null) {
- return;
- }
-
- for (Parcelable p : parcelableArray) {
- Surface s = (Surface) p;
- mSurfaceSet.add(s);
- }
-
mIsReprocess = (in.readInt() == 0) ? false : true;
mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE;
+
+ synchronized (mSurfacesLock) {
+ mSurfaceSet.clear();
+ Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader());
+ if (parcelableArray != null) {
+ for (Parcelable p : parcelableArray) {
+ Surface s = (Surface) p;
+ mSurfaceSet.add(s);
+ }
+ }
+ // Intentionally disallow java side readFromParcel to receive streamIdx/surfaceIdx
+ // Since there is no good way to convert indexes back to Surface
+ int streamSurfaceSize = in.readInt();
+ if (streamSurfaceSize != 0) {
+ throw new RuntimeException("Reading cached CaptureRequest is not supported");
+ }
+ }
}
@Override
@@ -489,8 +510,21 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
mSettings.writeToParcel(dest, flags);
- dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags);
dest.writeInt(mIsReprocess ? 1 : 0);
+
+ synchronized (mSurfacesLock) {
+ final ArraySet<Surface> surfaces = mSurfaceConverted ? mEmptySurfaceSet : mSurfaceSet;
+ dest.writeParcelableArray(surfaces.toArray(new Surface[surfaces.size()]), flags);
+ if (mSurfaceConverted) {
+ dest.writeInt(mStreamIdxArray.length);
+ for (int i = 0; i < mStreamIdxArray.length; i++) {
+ dest.writeInt(mStreamIdxArray[i]);
+ dest.writeInt(mSurfaceIdxArray[i]);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
}
/**
@@ -508,6 +542,67 @@
}
/**
+ * @hide
+ */
+ public void convertSurfaceToStreamId(
+ final SparseArray<OutputConfiguration> configuredOutputs) {
+ synchronized (mSurfacesLock) {
+ if (mSurfaceConverted) {
+ Log.v(TAG, "Cannot convert already converted surfaces!");
+ return;
+ }
+
+ mStreamIdxArray = new int[mSurfaceSet.size()];
+ mSurfaceIdxArray = new int[mSurfaceSet.size()];
+ int i = 0;
+ for (Surface s : mSurfaceSet) {
+ boolean streamFound = false;
+ for (int j = 0; j < configuredOutputs.size(); ++j) {
+ int streamId = configuredOutputs.keyAt(j);
+ OutputConfiguration outConfig = configuredOutputs.valueAt(j);
+ int surfaceId = 0;
+ for (Surface outSurface : outConfig.getSurfaces()) {
+ if (s == outSurface) {
+ streamFound = true;
+ mStreamIdxArray[i] = streamId;
+ mSurfaceIdxArray[i] = surfaceId;
+ i++;
+ break;
+ }
+ surfaceId++;
+ }
+ if (streamFound) {
+ break;
+ }
+ }
+ if (!streamFound) {
+ mStreamIdxArray = null;
+ mSurfaceIdxArray = null;
+ throw new IllegalArgumentException(
+ "CaptureRequest contains unconfigured Input/Output Surface!");
+ }
+ }
+ mSurfaceConverted = true;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void recoverStreamIdToSurface() {
+ synchronized (mSurfacesLock) {
+ if (!mSurfaceConverted) {
+ Log.v(TAG, "Cannot convert already converted surfaces!");
+ return;
+ }
+
+ mStreamIdxArray = null;
+ mSurfaceIdxArray = null;
+ mSurfaceConverted = false;
+ }
+ }
+
+ /**
* A builder for capture requests.
*
* <p>To obtain a builder instance, use the
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 6787d84..972a281 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -779,6 +779,7 @@
}
mRemoteDevice.updateOutputConfiguration(streamId, config);
+ mConfiguredOutputs.put(streamId, config);
}
}
@@ -828,6 +829,7 @@
+ " must have at least 1 surface");
}
mRemoteDevice.finalizeOutputConfigurations(streamId, config);
+ mConfiguredOutputs.put(streamId, config);
}
}
}
@@ -950,11 +952,20 @@
SubmitInfo requestInfo;
CaptureRequest[] requestArray = requestList.toArray(new CaptureRequest[requestList.size()]);
+ // Convert Surface to streamIdx and surfaceIdx
+ for (CaptureRequest request : requestArray) {
+ request.convertSurfaceToStreamId(mConfiguredOutputs);
+ }
+
requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating);
if (DEBUG) {
Log.v(TAG, "last frame number " + requestInfo.getLastFrameNumber());
}
+ for (CaptureRequest request : requestArray) {
+ request.recoverStreamIdToSurface();
+ }
+
if (callback != null) {
mCaptureCallbackMap.put(requestInfo.getRequestId(),
new CaptureCallbackHolder(
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 6da6fb7..b7ce875 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -291,7 +291,7 @@
public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
Log.e(TAG, "Received a query callback on a non-query request");
transaction.setResponse(new ContextHubTransaction.Response<Void>(
- ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+ ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
}
@Override
@@ -323,7 +323,7 @@
public void onTransactionComplete(int result) {
Log.e(TAG, "Received a non-query callback on a query request");
transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
- ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+ ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
}
};
}
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index 2a66cbc..ec1e68f 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -34,7 +34,7 @@
* through the ContextHubManager APIs. The caller can either retrieve the result
* synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or
* asynchronously through a user-defined callback
- * ({@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}).
+ * ({@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}).
*
* @param <T> the type of the contents in the transaction response
*
@@ -66,51 +66,51 @@
* Constants describing the result of a transaction or request through the Context Hub Service.
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "TRANSACTION_" }, value = {
- TRANSACTION_SUCCESS,
- TRANSACTION_FAILED_UNKNOWN,
- TRANSACTION_FAILED_BAD_PARAMS,
- TRANSACTION_FAILED_UNINITIALIZED,
- TRANSACTION_FAILED_PENDING,
- TRANSACTION_FAILED_AT_HUB,
- TRANSACTION_FAILED_TIMEOUT,
- TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE,
- TRANSACTION_FAILED_HAL_UNAVAILABLE
+ @IntDef(prefix = { "RESULT_" }, value = {
+ RESULT_SUCCESS,
+ RESULT_FAILED_UNKNOWN,
+ RESULT_FAILED_BAD_PARAMS,
+ RESULT_FAILED_UNINITIALIZED,
+ RESULT_FAILED_PENDING,
+ RESULT_FAILED_AT_HUB,
+ RESULT_FAILED_TIMEOUT,
+ RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
+ RESULT_FAILED_HAL_UNAVAILABLE
})
public @interface Result {}
- public static final int TRANSACTION_SUCCESS = 0;
+ public static final int RESULT_SUCCESS = 0;
/**
* Generic failure mode.
*/
- public static final int TRANSACTION_FAILED_UNKNOWN = 1;
+ public static final int RESULT_FAILED_UNKNOWN = 1;
/**
* Failure mode when the request parameters were not valid.
*/
- public static final int TRANSACTION_FAILED_BAD_PARAMS = 2;
+ public static final int RESULT_FAILED_BAD_PARAMS = 2;
/**
* Failure mode when the Context Hub is not initialized.
*/
- public static final int TRANSACTION_FAILED_UNINITIALIZED = 3;
+ public static final int RESULT_FAILED_UNINITIALIZED = 3;
/**
* Failure mode when there are too many transactions pending.
*/
- public static final int TRANSACTION_FAILED_PENDING = 4;
+ public static final int RESULT_FAILED_PENDING = 4;
/**
* Failure mode when the request went through, but failed asynchronously at the hub.
*/
- public static final int TRANSACTION_FAILED_AT_HUB = 5;
+ public static final int RESULT_FAILED_AT_HUB = 5;
/**
* Failure mode when the transaction has timed out.
*/
- public static final int TRANSACTION_FAILED_TIMEOUT = 6;
+ public static final int RESULT_FAILED_TIMEOUT = 6;
/**
* Failure mode when the transaction has failed internally at the service.
*/
- public static final int TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE = 7;
+ public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7;
/**
* Failure mode when the Context Hub HAL was not available.
*/
- public static final int TRANSACTION_FAILED_HAL_UNAVAILABLE = 8;
+ public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8;
/**
* A class describing the response for a ContextHubTransaction.
@@ -271,7 +271,7 @@
* A transaction can be invalidated if the process owning the transaction is no longer active
* and the reference to this object is lost.
*
- * This method or {@link #setCallbackOnComplete(ContextHubTransaction.Callback)} can only be
+ * This method or {@link #setOnCompleteCallback(ContextHubTransaction.Callback)} can only be
* invoked once, or an IllegalStateException will be thrown.
*
* @param callback the callback to be invoked upon completion
@@ -280,7 +280,7 @@
* @throws IllegalStateException if this method is called multiple times
* @throws NullPointerException if the callback or handler is null
*/
- public void setCallbackOnComplete(
+ public void setOnCompleteCallback(
@NonNull ContextHubTransaction.Callback<T> callback, @NonNull Handler handler) {
synchronized (this) {
if (callback == null) {
@@ -312,10 +312,10 @@
/**
* Sets a callback to be invoked when the transaction completes.
*
- * Equivalent to {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+ * Equivalent to {@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}
* with the handler being that of the main thread's Looper.
*
- * This method or {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+ * This method or {@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}
* can only be invoked once, or an IllegalStateException will be thrown.
*
* @param callback the callback to be invoked upon completion
@@ -323,8 +323,8 @@
* @throws IllegalStateException if this method is called multiple times
* @throws NullPointerException if the callback is null
*/
- public void setCallbackOnComplete(@NonNull ContextHubTransaction.Callback<T> callback) {
- setCallbackOnComplete(callback, new Handler(Looper.getMainLooper()));
+ public void setOnCompleteCallback(@NonNull ContextHubTransaction.Callback<T> callback) {
+ setOnCompleteCallback(callback, new Handler(Looper.getMainLooper()));
}
/**
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 9180112..95e7f60 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -67,4 +67,13 @@
/** Unregisters a callback on data usage. */
void unregisterUsageRequest(in DataUsageRequest request);
+ /** Get the uid stats information since boot */
+ long getUidStats(int uid, int type);
+
+ /** Get the iface stats information since boot */
+ long getIfaceStats(String iface, int type);
+
+ /** Get the total network stats information since boot */
+ long getTotalStats(int type);
+
}
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 3458861..d6992aa 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -130,11 +130,12 @@
}
/**
- * @return a String representation of the OUI part of this MacAddres,
- * with the lower 3 bytes constituting the NIC part replaced with 0.
+ * @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal
+ * numbers in [0,ff] joined by ':' characters.
*/
- public String toSafeString() {
- return stringAddrFromLongAddr(mAddr & OUI_MASK);
+ public String toOuiString() {
+ return String.format(
+ "%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff);
}
@Override
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index d701550..196a3bc 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -505,7 +505,12 @@
public static long getMobileTcpRxPackets() {
long total = 0;
for (String iface : getMobileIfaces()) {
- final long stat = nativeGetIfaceStat(iface, TYPE_TCP_RX_PACKETS);
+ long stat = UNSUPPORTED;
+ try {
+ stat = getStatsService().getIfaceStats(iface, TYPE_TCP_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
if (stat != UNSUPPORTED) {
total += stat;
}
@@ -517,7 +522,12 @@
public static long getMobileTcpTxPackets() {
long total = 0;
for (String iface : getMobileIfaces()) {
- final long stat = nativeGetIfaceStat(iface, TYPE_TCP_TX_PACKETS);
+ long stat = UNSUPPORTED;
+ try {
+ stat = getStatsService().getIfaceStats(iface, TYPE_TCP_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
if (stat != UNSUPPORTED) {
total += stat;
}
@@ -527,46 +537,78 @@
/** {@hide} */
public static long getTxPackets(String iface) {
- return nativeGetIfaceStat(iface, TYPE_TX_PACKETS);
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/** {@hide} */
public static long getRxPackets(String iface) {
- return nativeGetIfaceStat(iface, TYPE_RX_PACKETS);
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/** {@hide} */
public static long getTxBytes(String iface) {
- return nativeGetIfaceStat(iface, TYPE_TX_BYTES);
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/** {@hide} */
public static long getRxBytes(String iface) {
- return nativeGetIfaceStat(iface, TYPE_RX_BYTES);
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/** {@hide} */
@TestApi
public static long getLoopbackTxPackets() {
- return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_PACKETS);
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/** {@hide} */
@TestApi
public static long getLoopbackRxPackets() {
- return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_PACKETS);
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/** {@hide} */
@TestApi
public static long getLoopbackTxBytes() {
- return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_BYTES);
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/** {@hide} */
@TestApi
public static long getLoopbackRxBytes() {
- return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_BYTES);
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -579,7 +621,11 @@
* return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalTxPackets() {
- return nativeGetTotalStat(TYPE_TX_PACKETS);
+ try {
+ return getStatsService().getTotalStats(TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -592,7 +638,11 @@
* return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalRxPackets() {
- return nativeGetTotalStat(TYPE_RX_PACKETS);
+ try {
+ return getStatsService().getTotalStats(TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -605,7 +655,11 @@
* return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalTxBytes() {
- return nativeGetTotalStat(TYPE_TX_BYTES);
+ try {
+ return getStatsService().getTotalStats(TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -618,7 +672,11 @@
* return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalRxBytes() {
- return nativeGetTotalStat(TYPE_RX_BYTES);
+ try {
+ return getStatsService().getTotalStats(TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -644,7 +702,11 @@
// unsupported value. The real filtering is done at the kernel level.
final int callingUid = android.os.Process.myUid();
if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
- return nativeGetUidStat(uid, TYPE_TX_BYTES);
+ try {
+ return getStatsService().getUidStats(uid, TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
} else {
return UNSUPPORTED;
}
@@ -673,7 +735,11 @@
// unsupported value. The real filtering is done at the kernel level.
final int callingUid = android.os.Process.myUid();
if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
- return nativeGetUidStat(uid, TYPE_RX_BYTES);
+ try {
+ return getStatsService().getUidStats(uid, TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
} else {
return UNSUPPORTED;
}
@@ -702,7 +768,11 @@
// unsupported value. The real filtering is done at the kernel level.
final int callingUid = android.os.Process.myUid();
if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
- return nativeGetUidStat(uid, TYPE_TX_PACKETS);
+ try {
+ return getStatsService().getUidStats(uid, TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
} else {
return UNSUPPORTED;
}
@@ -731,7 +801,11 @@
// unsupported value. The real filtering is done at the kernel level.
final int callingUid = android.os.Process.myUid();
if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
- return nativeGetUidStat(uid, TYPE_RX_PACKETS);
+ try {
+ return getStatsService().getUidStats(uid, TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
} else {
return UNSUPPORTED;
}
@@ -859,8 +933,4 @@
private static final int TYPE_TX_PACKETS = 3;
private static final int TYPE_TCP_RX_PACKETS = 4;
private static final int TYPE_TCP_TX_PACKETS = 5;
-
- private static native long nativeGetTotalStat(int type);
- private static native long nativeGetIfaceStat(String iface, int type);
- private static native long nativeGetUidStat(int uid, int type);
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9513b9b..430c28b 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -16,6 +16,7 @@
package android.os;
+import android.app.ActivityManager;
import android.app.job.JobParameters;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -33,6 +34,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.Display;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
@@ -327,7 +329,8 @@
*
* Other types might include times spent in foreground, background etc.
*/
- private final String UID_TIMES_TYPE_ALL = "A";
+ @VisibleForTesting
+ public static final String UID_TIMES_TYPE_ALL = "A";
/**
* State for keeping track of counting information.
@@ -507,6 +510,31 @@
}
/**
+ * Maps the ActivityManager procstate into corresponding BatteryStats procstate.
+ */
+ public static int mapToInternalProcessState(int procState) {
+ if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ return ActivityManager.PROCESS_STATE_NONEXISTENT;
+ } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ return Uid.PROCESS_STATE_TOP;
+ } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ // Persistent and other foreground states go here.
+ return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+ } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ // Persistent and other foreground states go here.
+ return Uid.PROCESS_STATE_FOREGROUND;
+ } else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+ return Uid.PROCESS_STATE_BACKGROUND;
+ } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+ return Uid.PROCESS_STATE_TOP_SLEEPING;
+ } else if (procState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+ return Uid.PROCESS_STATE_HEAVY_WEIGHT;
+ } else {
+ return Uid.PROCESS_STATE_CACHED;
+ }
+ }
+
+ /**
* The statistics associated with a particular uid.
*/
public static abstract class Uid {
@@ -645,6 +673,15 @@
public abstract long[] getCpuFreqTimes(int which);
public abstract long[] getScreenOffCpuFreqTimes(int which);
+ /**
+ * Returns cpu times of an uid at a particular process state.
+ */
+ public abstract long[] getCpuFreqTimes(int which, int procState);
+ /**
+ * Returns cpu times of an uid while the screen if off at a particular process state.
+ */
+ public abstract long[] getScreenOffCpuFreqTimes(int which, int procState);
+
// Note: the following times are disjoint. They can be added together to find the
// total time a uid has had any processes running at all.
@@ -689,11 +726,32 @@
*/
public static final int NUM_PROCESS_STATE = 7;
+ // Used in dump
static final String[] PROCESS_STATE_NAMES = {
"Top", "Fg Service", "Foreground", "Background", "Top Sleeping", "Heavy Weight",
"Cached"
};
+ // Used in checkin dump
+ @VisibleForTesting
+ public static final String[] UID_PROCESS_TYPES = {
+ "T", // TOP
+ "FS", // FOREGROUND_SERVICE
+ "F", // FOREGROUND
+ "B", // BACKGROUND
+ "TS", // TOP_SLEEPING
+ "HW", // HEAVY_WEIGHT
+ "C" // CACHED
+ };
+
+ /**
+ * When the process exits one of these states, we need to make sure cpu time in this state
+ * is not attributed to any non-critical process states.
+ */
+ public static final int[] CRITICAL_PROC_STATES = {
+ PROCESS_STATE_TOP, PROCESS_STATE_FOREGROUND_SERVICE, PROCESS_STATE_FOREGROUND
+ };
+
public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
public abstract Timer getProcessStateTimer(int state);
@@ -4002,6 +4060,29 @@
dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA, UID_TIMES_TYPE_ALL,
cpuFreqTimeMs.length, sb.toString());
}
+
+ for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+ final long[] timesMs = u.getCpuFreqTimes(which, procState);
+ if (timesMs != null && timesMs.length == cpuFreqs.length) {
+ sb.setLength(0);
+ for (int i = 0; i < timesMs.length; ++i) {
+ sb.append((i == 0 ? "" : ",") + timesMs[i]);
+ }
+ final long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(
+ which, procState);
+ if (screenOffTimesMs != null) {
+ for (int i = 0; i < screenOffTimesMs.length; ++i) {
+ sb.append("," + screenOffTimesMs[i]);
+ }
+ } else {
+ for (int i = 0; i < timesMs.length; ++i) {
+ sb.append(",0");
+ }
+ }
+ dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA,
+ Uid.UID_PROCESS_TYPES[procState], timesMs.length, sb.toString());
+ }
+ }
}
final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
@@ -5612,6 +5693,30 @@
pw.println(sb.toString());
}
+ for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+ final long[] cpuTimes = u.getCpuFreqTimes(which, procState);
+ if (cpuTimes != null) {
+ sb.setLength(0);
+ sb.append(" Cpu times per freq at state "
+ + Uid.PROCESS_STATE_NAMES[procState] + ":");
+ for (int i = 0; i < cpuTimes.length; ++i) {
+ sb.append(" " + cpuTimes[i]);
+ }
+ pw.println(sb.toString());
+ }
+
+ final long[] screenOffCpuTimes = u.getScreenOffCpuFreqTimes(which, procState);
+ if (screenOffCpuTimes != null) {
+ sb.setLength(0);
+ sb.append(" Screen-off cpu times per freq at state "
+ + Uid.PROCESS_STATE_NAMES[procState] + ":");
+ for (int i = 0; i < screenOffCpuTimes.length; ++i) {
+ sb.append(" " + screenOffCpuTimes[i]);
+ }
+ pw.println(sb.toString());
+ }
+ }
+
final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
= u.getProcessStats();
for (int ipr=processStats.size()-1; ipr>=0; ipr--) {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 9c90c38..f643c57 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -79,9 +79,7 @@
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 @@
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/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 77ac2651..3ef0961 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -110,7 +110,7 @@
*
* This method must only be called by the device administration policy manager.
*/
- public abstract void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs);
+ public abstract void setMaximumScreenOffTimeoutFromDeviceAdmin(int userId, long timeMs);
/**
* Used by the dream manager to override certain properties while dozing.
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index b9b9a18..bbb8a7b 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -334,6 +334,23 @@
}
/**
+ * Performs {@code action} for each cookie associated with a callback, calling
+ * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping
+ *
+ * @hide
+ */
+ public <C> void broadcastForEachCookie(Consumer<C> action) {
+ int itemCount = beginBroadcast();
+ try {
+ for (int i = 0; i < itemCount; i++) {
+ action.accept((C) getBroadcastCookie(i));
+ }
+ } finally {
+ finishBroadcast();
+ }
+ }
+
+ /**
* Returns the number of registered callbacks. Note that the number of registered
* callbacks may differ from the value returned by {@link #beginBroadcast()} since
* the former returns the number of callbacks registered at the time of the call
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3504142..75cbd57 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2130,15 +2130,46 @@
}
/**
- * 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 userHandle The user handle of the profile.
- * @param enableQuietMode Whether quiet mode should be enabled or disabled.
+ * @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.
+ *
+ * @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 @@
}
/**
- * 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 8632aad..bf145a0 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -266,7 +266,9 @@
if (mUids == null) mUids = new int[2];
mUids[0] = uid;
mNames = null;
- mChains.clear();
+ if (mChains != null) {
+ mChains.clear();
+ }
}
/** @hide */
@@ -281,7 +283,9 @@
}
mUids[0] = uid;
mNames[0] = name;
- mChains.clear();
+ if (mChains != null) {
+ mChains.clear();
+ }
}
/**
diff --git a/telephony/java/com/android/ims/internal/ISmsListener.aidl b/core/java/android/os/connectivity/CellularBatteryStats.aidl
similarity index 61%
copy from telephony/java/com/android/ims/internal/ISmsListener.aidl
copy to core/java/android/os/connectivity/CellularBatteryStats.aidl
index 1266f04..ca0a585 100644
--- a/telephony/java/com/android/ims/internal/ISmsListener.aidl
+++ b/core/java/android/os/connectivity/CellularBatteryStats.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,7 @@
* limitations under the License.
*/
-package com.android.ims.internal;
+package android.os.connectivity;
-/**
- * See SmsFeature for more information.
- * {@hide}
- */
-interface ISmsListener {
- void setSentSmsResult(in int messageRef, in int result);
- void setSentSmsStatusReport(in int format, in byte[] pdu);
- void deliverSms(in int format, in byte[] pdu);
-}
\ No newline at end of file
+/** {@hide} */
+parcelable CellularBatteryStats;
\ No newline at end of file
diff --git a/core/java/android/os/connectivity/CellularBatteryStats.java b/core/java/android/os/connectivity/CellularBatteryStats.java
new file mode 100644
index 0000000..2593c85
--- /dev/null
+++ b/core/java/android/os/connectivity/CellularBatteryStats.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.connectivity;
+
+import android.os.BatteryStats;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.telephony.ModemActivityInfo;
+import android.telephony.SignalStrength;
+
+import java.util.Arrays;
+
+/**
+ * API for Cellular power stats
+ *
+ * @hide
+ */
+public final class CellularBatteryStats implements Parcelable {
+
+ private long mLoggingDurationMs;
+ private long mKernelActiveTimeMs;
+ private long mNumPacketsTx;
+ private long mNumBytesTx;
+ private long mNumPacketsRx;
+ private long mNumBytesRx;
+ private long mSleepTimeMs;
+ private long mIdleTimeMs;
+ private long mRxTimeMs;
+ private long mEnergyConsumedMaMs;
+ private long[] mTimeInRatMs;
+ private long[] mTimeInRxSignalStrengthLevelMs;
+ private long[] mTxTimeMs;
+
+ public static final Parcelable.Creator<CellularBatteryStats> CREATOR = new
+ Parcelable.Creator<CellularBatteryStats>() {
+ public CellularBatteryStats createFromParcel(Parcel in) {
+ return new CellularBatteryStats(in);
+ }
+
+ public CellularBatteryStats[] newArray(int size) {
+ return new CellularBatteryStats[size];
+ }
+ };
+
+ public CellularBatteryStats() {
+ initialize();
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mLoggingDurationMs);
+ out.writeLong(mKernelActiveTimeMs);
+ out.writeLong(mNumPacketsTx);
+ out.writeLong(mNumBytesTx);
+ out.writeLong(mNumPacketsRx);
+ out.writeLong(mNumBytesRx);
+ out.writeLong(mSleepTimeMs);
+ out.writeLong(mIdleTimeMs);
+ out.writeLong(mRxTimeMs);
+ out.writeLong(mEnergyConsumedMaMs);
+ out.writeLongArray(mTimeInRatMs);
+ out.writeLongArray(mTimeInRxSignalStrengthLevelMs);
+ out.writeLongArray(mTxTimeMs);
+ }
+
+ public void readFromParcel(Parcel in) {
+ mLoggingDurationMs = in.readLong();
+ mKernelActiveTimeMs = in.readLong();
+ mNumPacketsTx = in.readLong();
+ mNumBytesTx = in.readLong();
+ mNumPacketsRx = in.readLong();
+ mNumBytesRx = in.readLong();
+ mSleepTimeMs = in.readLong();
+ mIdleTimeMs = in.readLong();
+ mRxTimeMs = in.readLong();
+ mEnergyConsumedMaMs = in.readLong();
+ in.readLongArray(mTimeInRatMs);
+ in.readLongArray(mTimeInRxSignalStrengthLevelMs);
+ in.readLongArray(mTxTimeMs);
+ }
+
+ public long getLoggingDurationMs() {
+ return mLoggingDurationMs;
+ }
+
+ public long getKernelActiveTimeMs() {
+ return mKernelActiveTimeMs;
+ }
+
+ public long getNumPacketsTx() {
+ return mNumPacketsTx;
+ }
+
+ public long getNumBytesTx() {
+ return mNumBytesTx;
+ }
+
+ public long getNumPacketsRx() {
+ return mNumPacketsRx;
+ }
+
+ public long getNumBytesRx() {
+ return mNumBytesRx;
+ }
+
+ public long getSleepTimeMs() {
+ return mSleepTimeMs;
+ }
+
+ public long getIdleTimeMs() {
+ return mIdleTimeMs;
+ }
+
+ public long getRxTimeMs() {
+ return mRxTimeMs;
+ }
+
+ public long getEnergyConsumedMaMs() {
+ return mEnergyConsumedMaMs;
+ }
+
+ public long[] getTimeInRatMs() {
+ return mTimeInRatMs;
+ }
+
+ public long[] getTimeInRxSignalStrengthLevelMs() {
+ return mTimeInRxSignalStrengthLevelMs;
+ }
+
+ public long[] getTxTimeMs() {
+ return mTxTimeMs;
+ }
+
+ public void setLoggingDurationMs(long t) {
+ mLoggingDurationMs = t;
+ return;
+ }
+
+ public void setKernelActiveTimeMs(long t) {
+ mKernelActiveTimeMs = t;
+ return;
+ }
+
+ public void setNumPacketsTx(long n) {
+ mNumPacketsTx = n;
+ return;
+ }
+
+ public void setNumBytesTx(long b) {
+ mNumBytesTx = b;
+ return;
+ }
+
+ public void setNumPacketsRx(long n) {
+ mNumPacketsRx = n;
+ return;
+ }
+
+ public void setNumBytesRx(long b) {
+ mNumBytesRx = b;
+ return;
+ }
+
+ public void setSleepTimeMs(long t) {
+ mSleepTimeMs = t;
+ return;
+ }
+
+ public void setIdleTimeMs(long t) {
+ mIdleTimeMs = t;
+ return;
+ }
+
+ public void setRxTimeMs(long t) {
+ mRxTimeMs = t;
+ return;
+ }
+
+ public void setEnergyConsumedMaMs(long e) {
+ mEnergyConsumedMaMs = e;
+ return;
+ }
+
+ public void setTimeInRatMs(long[] t) {
+ mTimeInRatMs = Arrays.copyOfRange(t, 0,
+ Math.min(t.length, BatteryStats.NUM_DATA_CONNECTION_TYPES));
+ return;
+ }
+
+ public void setTimeInRxSignalStrengthLevelMs(long[] t) {
+ mTimeInRxSignalStrengthLevelMs = Arrays.copyOfRange(t, 0,
+ Math.min(t.length, SignalStrength.NUM_SIGNAL_STRENGTH_BINS));
+ return;
+ }
+
+ public void setTxTimeMs(long[] t) {
+ mTxTimeMs = Arrays.copyOfRange(t, 0, Math.min(t.length, ModemActivityInfo.TX_POWER_LEVELS));
+ return;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ private CellularBatteryStats(Parcel in) {
+ initialize();
+ readFromParcel(in);
+ }
+
+ private void initialize() {
+ mLoggingDurationMs = 0;
+ mKernelActiveTimeMs = 0;
+ mNumPacketsTx = 0;
+ mNumBytesTx = 0;
+ mNumPacketsRx = 0;
+ mNumBytesRx = 0;
+ mSleepTimeMs = 0;
+ mIdleTimeMs = 0;
+ mRxTimeMs = 0;
+ mEnergyConsumedMaMs = 0;
+ mTimeInRatMs = new long[BatteryStats.NUM_DATA_CONNECTION_TYPES];
+ Arrays.fill(mTimeInRatMs, 0);
+ mTimeInRxSignalStrengthLevelMs = new long[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+ Arrays.fill(mTimeInRxSignalStrengthLevelMs, 0);
+ mTxTimeMs = new long[ModemActivityInfo.TX_POWER_LEVELS];
+ Arrays.fill(mTxTimeMs, 0);
+ return;
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 766ad84..60df467 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -212,16 +212,19 @@
public static final String FEATURES = "features";
/** Call had video. */
- public static final int FEATURES_VIDEO = 0x1;
+ public static final int FEATURES_VIDEO = 1 << 0;
/** Call was pulled externally. */
- public static final int FEATURES_PULLED_EXTERNALLY = 0x2;
+ public static final int FEATURES_PULLED_EXTERNALLY = 1 << 1;
/** Call was HD. */
- public static final int FEATURES_HD_CALL = 0x4;
+ public static final int FEATURES_HD_CALL = 1 << 2;
/** Call was WIFI call. */
- public static final int FEATURES_WIFI = 0x8;
+ public static final int FEATURES_WIFI = 1 << 3;
+
+ /** Call was on RTT at some point */
+ public static final int FEATURES_RTT = 1 << 4;
/**
* Indicates the call underwent Assisted Dialing.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d118219..c6deecc 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5345,6 +5345,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static final String AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE =
"autofill_user_data_max_user_data_size";
@@ -5355,6 +5356,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static final String AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE =
"autofill_user_data_max_field_classification_size";
@@ -5364,6 +5366,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static final String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH =
"autofill_user_data_max_value_length";
@@ -5373,6 +5376,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static final String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH =
"autofill_user_data_min_value_length";
@@ -9567,6 +9571,31 @@
public static final String ANOMALY_DETECTION_CONSTANTS = "anomaly_detection_constants";
/**
+ * Battery tip specific settings
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "battery_tip_enabled=true,summary_enabled=true,high_usage_enabled=true,"
+ * "high_usage_app_count=3,reduced_battery_enabled=false,reduced_battery_percent=50"
+ *
+ * The following keys are supported:
+ *
+ * <pre>
+ * battery_tip_enabled (boolean)
+ * summary_enabled (boolean)
+ * battery_saver_tip_enabled (boolean)
+ * high_usage_enabled (boolean)
+ * high_usage_app_count (int)
+ * app_restriction_enabled (boolean)
+ * reduced_battery_enabled (boolean)
+ * reduced_battery_percent (int)
+ * low_battery_enabled (boolean)
+ * low_battery_hour (int)
+ * </pre>
+ * @hide
+ */
+ public static final String BATTERY_TIP_CONSTANTS = "battery_tip_constants";
+
+ /**
* Always on display(AOD) specific settings
* This is encoded as a key=value list, separated by commas. Ex:
*
diff --git a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
index 2205c41..978e60e 100644
--- a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
+++ b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
@@ -60,7 +60,7 @@
/**
* 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/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
index f2f225d..f88768b 100644
--- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
+++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -29,6 +30,7 @@
import com.android.internal.widget.ILockSettings;
import java.util.List;
+import java.util.Map;
/**
* A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
@@ -43,9 +45,23 @@
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.
+ /**
+ * Rate limit is enforced to prevent using too many trusted remote devices, since each device
+ * can have its own number of user secret guesses allowed.
+ *
+ * @hide
+ */
public static final int RATE_LIMIT_EXCEEDED = 21;
+ /** Key has been successfully synced. */
+ public static final int RECOVERY_STATUS_SYNCED = 0;
+ /** Waiting for recovery agent to sync the key. */
+ public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
+ /** Recovery account is not available. */
+ public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
+ /** Key cannot be synced. */
+ public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
+
private final ILockSettings mBinder;
private RecoverableKeyStoreLoader(ILockSettings binder) {
@@ -155,7 +171,7 @@
* @return Data necessary to recover keystore.
* @hide
*/
- public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
+ public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
throws RecoverableKeyStoreLoaderException {
try {
KeyStoreRecoveryData recoveryData =
@@ -169,6 +185,50 @@
}
/**
+ * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
+ * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
+ * most one registered listener at any time.
+ *
+ * @param intent triggered when new snapshot is available. Unregisters listener if the value is
+ * {@code null}.
+ * @hide
+ */
+ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ mBinder.setSnapshotCreatedPendingIntent(intent, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
+ * version. Version zero is used, if no snapshots were created for the account.
+ *
+ * @return Map from recovery agent accounts to snapshot versions.
+ * @see KeyStoreRecoveryData.getSnapshotVersion
+ * @hide
+ */
+ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ // IPC doesn't support generic Maps.
+ @SuppressWarnings("unchecked")
+ Map<byte[], Integer> result =
+ (Map<byte[], Integer>)
+ mBinder.getRecoverySnapshotVersions(UserHandle.getCallingUserId());
+ return result;
+ } 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}
@@ -191,8 +251,8 @@
/**
* 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.
+ * successfully stored on the server or there were an error. Application can check this value
+ * using {@code getRecoveyStatus}.
*
* @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
@@ -212,6 +272,39 @@
}
/**
+ * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
+ * Negative status values are reserved for recovery agent specific codes. List of common codes:
+ *
+ * <ul>
+ * <li>{@link #RECOVERY_STATUS_SYNCED}
+ * <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
+ * <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
+ * <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
+ * </ul>
+ *
+ * @param packageName Application whose recoverable keys' statuses are to be retrieved. if
+ * {@code null} caller's package will be used.
+ * @return {@code Map} from KeyStore alias to recovery status.
+ * @see #setRecoveryStatus
+ * @hide
+ */
+ public Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ // IPC doesn't support generic Maps.
+ @SuppressWarnings("unchecked")
+ Map<String, Integer> result =
+ (Map<String, Integer>)
+ mBinder.getRecoveryStatus(packageName, UserHandle.getCallingUserId());
+ return result;
+ } 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.
*
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 2600f8a..917efa8 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -438,7 +438,7 @@
* AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
*
* save(username, password);
- * </pre>
+ * </pre>
*
* <a name="Privacy"></a>
* <h3>Privacy</h3>
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b7d33599..87f3bc7 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -39,7 +39,7 @@
DEFAULT_FLAGS = new HashMap<>();
DEFAULT_FLAGS.put("device_info_v2", "true");
DEFAULT_FLAGS.put("new_settings_suggestion", "true");
- DEFAULT_FLAGS.put("settings_search_v2", "false");
+ DEFAULT_FLAGS.put("settings_search_v2", "true");
DEFAULT_FLAGS.put("settings_app_info_v2", "false");
DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
DEFAULT_FLAGS.put("settings_battery_v2", "false");
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index a74a882..0a54f3a 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -97,12 +97,30 @@
*/
public static X509Certificate[][] verify(String apkFile)
throws SignatureNotFoundException, SecurityException, IOException {
- try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
- return verify(apk);
- }
+ return verify(apkFile, true);
}
/**
+ * Returns the certificates associated with each signer for the given APK without verification.
+ * This method is dangerous and should not be used, unless the caller is absolutely certain the
+ * APK is trusted. Specifically, verification is only done for the APK Signature Scheme V2
+ * Block while gathering signer information. The APK contents are not verified.
+ *
+ * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
+ * @throws IOException if an I/O error occurs while reading the APK file.
+ */
+ public static X509Certificate[][] plsCertsNoVerifyOnlyCerts(String apkFile)
+ throws SignatureNotFoundException, SecurityException, IOException {
+ return verify(apkFile, false);
+ }
+
+ private static X509Certificate[][] verify(String apkFile, boolean verifyIntegrity)
+ throws SignatureNotFoundException, SecurityException, IOException {
+ try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+ return verify(apk, verifyIntegrity);
+ }
+ }
+ /**
* Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
* associated with each signer.
*
@@ -111,10 +129,10 @@
* verify.
* @throws IOException if an I/O error occurs while reading the APK file.
*/
- private static X509Certificate[][] verify(RandomAccessFile apk)
+ private static X509Certificate[][] verify(RandomAccessFile apk, boolean verifyIntegrity)
throws SignatureNotFoundException, SecurityException, IOException {
SignatureInfo signatureInfo = findSignature(apk);
- return verify(apk.getFD(), signatureInfo);
+ return verify(apk.getFD(), signatureInfo, verifyIntegrity);
}
/**
@@ -161,7 +179,8 @@
*/
private static X509Certificate[][] verify(
FileDescriptor apkFileDescriptor,
- SignatureInfo signatureInfo) throws SecurityException {
+ SignatureInfo signatureInfo,
+ boolean doVerifyIntegrity) throws SecurityException {
int signerCount = 0;
Map<Integer, byte[]> contentDigests = new ArrayMap<>();
List<X509Certificate[]> signerCerts = new ArrayList<>();
@@ -198,13 +217,15 @@
throw new SecurityException("No content digests found");
}
- verifyIntegrity(
- contentDigests,
- apkFileDescriptor,
- signatureInfo.apkSigningBlockOffset,
- signatureInfo.centralDirOffset,
- signatureInfo.eocdOffset,
- signatureInfo.eocd);
+ if (doVerifyIntegrity) {
+ verifyIntegrity(
+ contentDigests,
+ apkFileDescriptor,
+ signatureInfo.apkSigningBlockOffset,
+ signatureInfo.centralDirOffset,
+ signatureInfo.eocdOffset,
+ signatureInfo.eocd);
+ }
return signerCerts.toArray(new X509Certificate[signerCerts.size()][]);
}
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 75c1000..17b11a9 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -58,37 +58,19 @@
private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
/**
- * Verifies the provided APK and returns the certificates associated with each signer. Also
- * ensures that the provided APK contains an AndroidManifest.xml file.
- *
- * @param systemDir systemDir apk contents are already trusted, so we don't need to enforce
- * v2 stripping rollback protection, or verify integrity of the APK.
+ * Verifies the provided APK and returns the certificates associated with each signer.
*
* @throws PackageParserException if the APK's signature failed to verify.
*/
- public static Result verify(String apkPath, int minSignatureSchemeVersion, boolean systemDir)
+ public static Result verify(String apkPath, int minSignatureSchemeVersion)
throws PackageParserException {
// first try v2
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
try {
- Certificate[][] signerCerts;
- signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
+ Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
Signature[] signerSigs = convertToSignatures(signerCerts);
- // sanity check - must have an AndroidManifest file
- StrictJarFile jarFile = null;
- try {
- jarFile = new StrictJarFile(apkPath, false, false);
- final ZipEntry manifestEntry =
- jarFile.findEntry(PackageParser.ANDROID_MANIFEST_FILENAME);
- if (manifestEntry == null) {
- throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
- "Package " + apkPath + " has no manifest");
- }
- } finally {
- closeQuietly(jarFile);
- }
return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2);
} catch (SignatureNotFoundException e) {
// not signed with v2, try older if allowed
@@ -96,9 +78,6 @@
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
- throw e;
} catch (Exception e) {
// APK Signature Scheme v2 signature found but did not verify
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
@@ -109,10 +88,17 @@
}
// v2 didn't work, try jarsigner
- return verifyV1Signature(apkPath, systemDir);
+ return verifyV1Signature(apkPath, true);
}
- private static Result verifyV1Signature(String apkPath, boolean systemDir)
+ /**
+ * Verifies the provided APK and returns the certificates associated with each signer.
+ *
+ * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+ *
+ * @throws PackageParserException if there was a problem collecting certificates
+ */
+ private static Result verifyV1Signature(String apkPath, boolean verifyFull)
throws PackageParserException {
StrictJarFile jarFile = null;
@@ -121,10 +107,17 @@
final Signature[] lastSigs;
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
- jarFile = new StrictJarFile(apkPath, true, !systemDir);
+
+ // we still pass verify = true to ctor to collect certs, even though we're not checking
+ // the whole jar.
+ jarFile = new StrictJarFile(
+ apkPath,
+ true, // collect certs
+ verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819)
final List<ZipEntry> toVerify = new ArrayList<>();
- // Always verify manifest, regardless of source
+ // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization
+ // to not need to verify the whole APK when verifyFUll == false.
final ZipEntry manifestEntry = jarFile.findEntry(
PackageParser.ANDROID_MANIFEST_FILENAME);
if (manifestEntry == null) {
@@ -139,8 +132,8 @@
}
lastSigs = convertToSignatures(lastCerts);
- // don't waste time on already-trusted packages
- if (!systemDir) {
+ // fully verify all contents, except for AndroidManifest.xml and the META-INF/ files.
+ if (verifyFull) {
final Iterator<ZipEntry> i = jarFile.iterator();
while (i.hasNext()) {
final ZipEntry entry = i.next();
@@ -153,9 +146,6 @@
toVerify.add(entry);
}
- // Verify that entries are signed consistently with the first entry
- // we encountered. Note that for splits, certificates may have
- // already been populated during an earlier parse of a base APK.;
for (ZipEntry entry : toVerify) {
final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
if (ArrayUtils.isEmpty(entryCerts)) {
@@ -245,6 +235,43 @@
}
/**
+ * Returns the certificates associated with each signer for the given APK without verification.
+ * This method is dangerous and should not be used, unless the caller is absolutely certain the
+ * APK is trusted.
+ *
+ * @throws PackageParserException if the APK's signature failed to verify.
+ * or greater is not found, except in the case of no JAR signature.
+ */
+ public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion)
+ throws PackageParserException {
+
+ // first try v2
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2");
+ try {
+ Certificate[][] signerCerts =
+ ApkSignatureSchemeV2Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
+ Signature[] signerSigs = convertToSignatures(signerCerts);
+ 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 PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No APK Signature Scheme v2 signature in package " + apkPath, e);
+ }
+ } catch (Exception e) {
+ // APK Signature Scheme v2 signature found but did not verify
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed to collect certificates from " + apkPath
+ + " using APK Signature Scheme v2", e);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+
+ // v2 didn't work, try jarsigner
+ return verifyV1Signature(apkPath, false);
+ }
+
+ /**
* Result of a successful APK verification operation.
*/
public static class Result {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 02beee0..cc63a62 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -26890,7 +26890,7 @@
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 @@
}
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/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 9c2f6bb..28ef697 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2325,7 +2325,7 @@
/**
* Returns whether the node is explicitly marked as a focusable unit by a screen reader. Note
* that {@code false} indicates that it is not explicitly marked, not that the node is not
- * a focusable unit. Screen readers should generally used other signals, such as
+ * a focusable unit. Screen readers should generally use other signals, such as
* {@link #isFocusable()}, or the presence of text in a node, to determine what should receive
* focus.
*
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index f11767d..ef1a3f3 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -87,6 +87,7 @@
private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;
private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1;
private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2;
+ private static final int BOOLEAN_PROPERTY_PICTURE_IN_PICTURE = 1 << 3;
// Housekeeping.
private static final int MAX_POOL_SIZE = 10;
@@ -103,8 +104,7 @@
private final Rect mBoundsInScreen = new Rect();
private LongArray mChildIds;
private CharSequence mTitle;
- private int mAnchorId = UNDEFINED_WINDOW_ID;
- private boolean mInPictureInPicture;
+ private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
private int mConnectionId = UNDEFINED_WINDOW_ID;
@@ -202,7 +202,7 @@
*
* @hide
*/
- public void setAnchorId(int anchorId) {
+ public void setAnchorId(long anchorId) {
mAnchorId = anchorId;
}
@@ -212,7 +212,8 @@
* @return The anchor node, or {@code null} if none exists.
*/
public AccessibilityNodeInfo getAnchor() {
- if ((mConnectionId == UNDEFINED_WINDOW_ID) || (mAnchorId == UNDEFINED_WINDOW_ID)
+ if ((mConnectionId == UNDEFINED_WINDOW_ID)
+ || (mAnchorId == AccessibilityNodeInfo.UNDEFINED_NODE_ID)
|| (mParentId == UNDEFINED_WINDOW_ID)) {
return null;
}
@@ -224,17 +225,7 @@
/** @hide */
public void setPictureInPicture(boolean pictureInPicture) {
- mInPictureInPicture = pictureInPicture;
- }
-
- /**
- * Check if the window is in picture-in-picture mode.
- *
- * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
- * @removed
- */
- public boolean inPictureInPicture() {
- return isInPictureInPictureMode();
+ setBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE, pictureInPicture);
}
/**
@@ -243,7 +234,7 @@
* @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
*/
public boolean isInPictureInPictureMode() {
- return mInPictureInPicture;
+ return getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE);
}
/**
@@ -463,7 +454,6 @@
infoClone.mBoundsInScreen.set(info.mBoundsInScreen);
infoClone.mTitle = info.mTitle;
infoClone.mAnchorId = info.mAnchorId;
- infoClone.mInPictureInPicture = info.mInPictureInPicture;
if (info.mChildIds != null && info.mChildIds.size() > 0) {
if (infoClone.mChildIds == null) {
@@ -520,8 +510,7 @@
parcel.writeInt(mParentId);
mBoundsInScreen.writeToParcel(parcel, flags);
parcel.writeCharSequence(mTitle);
- parcel.writeInt(mAnchorId);
- parcel.writeInt(mInPictureInPicture ? 1 : 0);
+ parcel.writeLong(mAnchorId);
final LongArray childIds = mChildIds;
if (childIds == null) {
@@ -545,8 +534,7 @@
mParentId = parcel.readInt();
mBoundsInScreen.readFromParcel(parcel);
mTitle = parcel.readCharSequence();
- mAnchorId = parcel.readInt();
- mInPictureInPicture = parcel.readInt() == 1;
+ mAnchorId = parcel.readLong();
final int childCount = parcel.readInt();
if (childCount > 0) {
@@ -593,7 +581,7 @@
builder.append(", bounds=").append(mBoundsInScreen);
builder.append(", focused=").append(isFocused());
builder.append(", active=").append(isActive());
- builder.append(", pictureInPicture=").append(inPictureInPicture());
+ builder.append(", pictureInPicture=").append(isInPictureInPictureMode());
if (DEBUG) {
builder.append(", parent=").append(mParentId);
builder.append(", children=[");
@@ -611,7 +599,8 @@
builder.append(']');
} else {
builder.append(", hasParent=").append(mParentId != UNDEFINED_WINDOW_ID);
- builder.append(", isAnchored=").append(mAnchorId != UNDEFINED_WINDOW_ID);
+ builder.append(", isAnchored=")
+ .append(mAnchorId != AccessibilityNodeInfo.UNDEFINED_NODE_ID);
builder.append(", hasChildren=").append(mChildIds != null
&& mChildIds.size() > 0);
}
@@ -633,8 +622,7 @@
mChildIds.clear();
}
mConnectionId = UNDEFINED_WINDOW_ID;
- mAnchorId = UNDEFINED_WINDOW_ID;
- mInPictureInPicture = false;
+ mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
mTitle = null;
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index d0dbff0e..2697454 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -530,10 +530,13 @@
* @return whether autofill is enabled for the current user.
*/
public boolean isEnabled() {
- if (!hasAutofillFeature() || isDisabledByService()) {
+ if (!hasAutofillFeature()) {
return false;
}
synchronized (mLock) {
+ if (isDisabledByServiceLocked()) {
+ return false;
+ }
ensureServiceClientAddedIfNeededLocked();
return mEnabled;
}
@@ -605,19 +608,16 @@
}
private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) {
- if (isDisabledByService()) {
+ if (isDisabledByServiceLocked()) {
if (sVerbose) {
Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+ ") on state " + getStateAsStringLocked());
}
return true;
}
- if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) {
- if (sVerbose) {
- Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
- + ") on state " + getStateAsStringLocked());
- }
- return true;
+ if (sVerbose && isFinishedLocked()) {
+ Log.v(TAG, "not ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+ + ") on state " + getStateAsStringLocked());
}
return false;
}
@@ -1009,6 +1009,21 @@
}
/**
+ * Returns the component name of the {@link AutofillService} that is enabled for the current
+ * user.
+ */
+ @Nullable
+ public ComponentName getAutofillServiceComponentName() {
+ if (mService == null) return null;
+
+ try {
+ return mService.getAutofillServiceComponentName();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the user data used for
* <a href="AutofillService.html#FieldClassification">field classification</a>.
*
@@ -1139,10 +1154,10 @@
Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
+ ", flags=" + flags + ", state=" + getStateAsStringLocked());
}
- if (mState != STATE_UNKNOWN && (flags & FLAG_MANUAL_REQUEST) == 0) {
+ if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
if (sVerbose) {
Log.v(TAG, "not automatically starting session for " + id
- + " on state " + getStateAsStringLocked());
+ + " on state " + getStateAsStringLocked() + " and flags " + flags);
}
return;
}
@@ -1744,10 +1759,14 @@
return mState == STATE_ACTIVE;
}
- private boolean isDisabledByService() {
+ private boolean isDisabledByServiceLocked() {
return mState == STATE_DISABLED_BY_SERVICE;
}
+ private boolean isFinishedLocked() {
+ return mState == STATE_FINISHED;
+ }
+
private void post(Runnable runnable) {
final AutofillClient client = getClient();
if (client == null) {
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index f49aa5b..38bb311 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -57,4 +57,5 @@
UserData getUserData();
void setUserData(in UserData userData);
boolean isFieldClassificationEnabled();
+ ComponentName getAutofillServiceComponentName();
}
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 63519a6..03ff0ca 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -34,7 +34,7 @@
private final UserInfo mUserInfo;
private final PackageInfo mPackageInfo;
- public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.O_MR1;
+ public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.P;
public UserPackage(UserInfo user, PackageInfo packageInfo) {
this.mUserInfo = user;
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index e3efad0..b3522ec 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -47,7 +47,7 @@
// visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
/** @hide */
private static final String CHROMIUM_WEBVIEW_FACTORY =
- "com.android.webview.chromium.WebViewChromiumFactoryProviderForOMR1";
+ "com.android.webview.chromium.WebViewChromiumFactoryProviderForP";
private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 7156300..53318c9 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -20,6 +20,7 @@
import static android.widget.RemoteViews.RemoteView;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -141,6 +142,9 @@
private boolean mShowCurrentUserTime;
private ContentObserver mFormatChangeObserver;
+ // Used by tests to stop time change events from triggering the text update
+ private boolean mStopTicking;
+
private class FormatChangeObserver extends ContentObserver {
public FormatChangeObserver(Handler handler) {
@@ -163,6 +167,9 @@
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ if (mStopTicking) {
+ return; // Test disabled the clock ticks
+ }
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
final String timeZone = intent.getStringExtra("time-zone");
createTime(timeZone);
@@ -173,6 +180,9 @@
private final Runnable mTicker = new Runnable() {
public void run() {
+ if (mStopTicking) {
+ return; // Test disabled the clock ticks
+ }
onTimeChanged();
long now = SystemClock.uptimeMillis();
@@ -546,6 +556,15 @@
}
}
+ /**
+ * Used by tests to stop the clock tick from updating the text.
+ * @hide
+ */
+ @TestApi
+ public void disableClockTick() {
+ mStopTicking = true;
+ }
+
private void registerReceiver() {
final IntentFilter filter = new IntentFilter();
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index f405231..ab118ba 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -22,6 +22,7 @@
import android.net.wifi.WifiActivityEnergyInfo;
import android.os.ParcelFileDescriptor;
import android.os.WorkSource;
+import android.os.connectivity.CellularBatteryStats;
import android.os.health.HealthStatsParceler;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
@@ -134,6 +135,9 @@
void noteResetBleScan();
void noteBleScanResults(in WorkSource ws, int numNewResults);
+ /** {@hide} */
+ CellularBatteryStats getCellularBatteryStats();
+
HealthStatsParceler takeUidSnapshot(int uid);
HealthStatsParceler[] takeUidSnapshots(in int[] uid);
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 8016a65..2eadaf3 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -111,7 +111,7 @@
@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/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6510a70..9ab16d8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -30,6 +30,7 @@
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Build;
+import android.os.connectivity.CellularBatteryStats;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBatteryPropertiesRegistrar;
@@ -51,6 +52,7 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.IntArray;
import android.util.Log;
import android.util.LogWriter;
import android.util.LongSparseArray;
@@ -120,7 +122,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 171 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 172 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -188,6 +190,8 @@
@VisibleForTesting
protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
new KernelUidCpuFreqTimeReader();
+ @VisibleForTesting
+ protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
= new KernelMemoryBandwidthStats();
@@ -196,6 +200,18 @@
return mKernelMemoryStats;
}
+ @GuardedBy("this")
+ public boolean mPerProcStateCpuTimesAvailable = true;
+
+ /**
+ * Uids for which per-procstate cpu times need to be updated.
+ *
+ * Contains uid -> procState mappings.
+ */
+ @GuardedBy("this")
+ @VisibleForTesting
+ protected final SparseIntArray mPendingUids = new SparseIntArray();
+
/** Container for Resource Power Manager stats. Updated by updateRpmStatsLocked. */
private final RpmStats mTmpRpmStats = new RpmStats();
/** The soonest the RPM stats can be updated after it was last updated. */
@@ -268,6 +284,138 @@
}
}
+ /**
+ * Update per-freq cpu times for all the uids in {@link #mPendingUids}.
+ */
+ public void updateProcStateCpuTimes() {
+ final SparseIntArray uidStates;
+ synchronized (BatteryStatsImpl.this) {
+ if(!initKernelSingleUidTimeReaderLocked()) {
+ return;
+ }
+
+ if (mPendingUids.size() == 0) {
+ return;
+ }
+ uidStates = mPendingUids.clone();
+ mPendingUids.clear();
+ }
+ for (int i = uidStates.size() - 1; i >= 0; --i) {
+ final int uid = uidStates.keyAt(i);
+ final int procState = uidStates.valueAt(i);
+ final int[] isolatedUids;
+ final Uid u;
+ final boolean onBattery;
+ synchronized (BatteryStatsImpl.this) {
+ // It's possible that uid no longer exists and any internal references have
+ // already been deleted, so using {@link #getAvailableUidStatsLocked} to avoid
+ // creating an UidStats object if it doesn't already exist.
+ u = getAvailableUidStatsLocked(uid);
+ if (u == null) {
+ continue;
+ }
+ if (u.mChildUids == null) {
+ isolatedUids = null;
+ } else {
+ isolatedUids = u.mChildUids.toArray();
+ for (int j = isolatedUids.length - 1; j >= 0; --j) {
+ isolatedUids[j] = u.mChildUids.get(j);
+ }
+ }
+ onBattery = mOnBatteryInternal;
+ }
+ long[] cpuTimesMs = mKernelSingleUidTimeReader.readDeltaMs(uid);
+ if (isolatedUids != null) {
+ for (int j = isolatedUids.length - 1; j >= 0; --j) {
+ cpuTimesMs = addCpuTimes(cpuTimesMs,
+ mKernelSingleUidTimeReader.readDeltaMs(isolatedUids[j]));
+ }
+ }
+ if (onBattery && cpuTimesMs != null) {
+ synchronized (BatteryStatsImpl.this) {
+ u.addProcStateTimesMs(procState, cpuTimesMs);
+ u.addProcStateScreenOffTimesMs(procState, cpuTimesMs);
+ }
+ }
+ }
+ }
+
+ /**
+ * When the battery/screen state changes, we don't attribute the cpu times to any process
+ * but we still need to snapshots of all uids to get correct deltas later on. Since we
+ * already read this data for updating per-freq cpu times, we can use the same data for
+ * per-procstate cpu times.
+ */
+ public void copyFromAllUidsCpuTimes() {
+ synchronized (BatteryStatsImpl.this) {
+ if(!initKernelSingleUidTimeReaderLocked()) {
+ return;
+ }
+
+ final SparseArray<long[]> allUidCpuFreqTimesMs =
+ mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs();
+ for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
+ final int uid = allUidCpuFreqTimesMs.keyAt(i);
+ final Uid u = getAvailableUidStatsLocked(mapUid(uid));
+ if (u == null) {
+ continue;
+ }
+ final long[] cpuTimesMs = allUidCpuFreqTimesMs.valueAt(i);
+ if (cpuTimesMs == null) {
+ continue;
+ }
+ final long[] deltaTimesMs = mKernelSingleUidTimeReader.computeDelta(
+ uid, cpuTimesMs.clone());
+ if (mOnBatteryInternal && deltaTimesMs != null) {
+ final int procState;
+ final int idx = mPendingUids.indexOfKey(uid);
+ if (idx >= 0) {
+ procState = mPendingUids.valueAt(idx);
+ mPendingUids.removeAt(idx);
+ } else {
+ procState = u.mProcessState;
+ }
+ if (procState >= 0 && procState < Uid.NUM_PROCESS_STATE) {
+ u.addProcStateTimesMs(procState, deltaTimesMs);
+ u.addProcStateScreenOffTimesMs(procState, deltaTimesMs);
+ }
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public long[] addCpuTimes(long[] timesA, long[] timesB) {
+ if (timesA != null && timesB != null) {
+ for (int i = timesA.length - 1; i >= 0; --i) {
+ timesA[i] += timesB[i];
+ }
+ return timesA;
+ }
+ return timesA == null ? (timesB == null ? null : timesB) : timesA;
+ }
+
+ @GuardedBy("this")
+ private boolean initKernelSingleUidTimeReaderLocked() {
+ if (mKernelSingleUidTimeReader == null) {
+ if (mPowerProfile == null) {
+ return false;
+ }
+ if (mCpuFreqs == null) {
+ mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+ }
+ if (mCpuFreqs != null) {
+ mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(mCpuFreqs.length);
+ } else {
+ mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable();
+ return false;
+ }
+ }
+ mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable()
+ && mKernelSingleUidTimeReader.singleUidCpuTimesAvailable();
+ return true;
+ }
+
public interface Clocks {
public long elapsedRealtime();
public long uptimeMillis();
@@ -293,9 +441,11 @@
Future<?> scheduleSync(String reason, int flags);
Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
+ Future<?> scheduleReadProcStateCpuTimes();
+ Future<?> scheduleCopyFromAllUidsCpuTimes();
}
- public final MyHandler mHandler;
+ public Handler mHandler;
private ExternalStatsSync mExternalSync = null;
@VisibleForTesting
protected UserInfoProvider mUserInfoProvider = null;
@@ -3623,6 +3773,7 @@
+ " and battery is " + (unplugged ? "on" : "off"));
}
updateCpuTimeLocked();
+ mExternalSync.scheduleCopyFromAllUidsCpuTimes();
mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
if (updateOnBatteryTimeBase) {
@@ -3652,6 +3803,8 @@
public void addIsolatedUidLocked(int isolatedUid, int appUid) {
mIsolatedUids.put(isolatedUid, appUid);
StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, 1);
+ final Uid u = getUidStatsLocked(appUid);
+ u.addIsolatedUid(isolatedUid);
}
/**
@@ -3672,11 +3825,17 @@
* @see #scheduleRemoveIsolatedUidLocked(int, int)
*/
public void removeIsolatedUidLocked(int isolatedUid) {
- StatsLog.write(
- StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
- mIsolatedUids.delete(isolatedUid);
- mKernelUidCpuTimeReader.removeUid(isolatedUid);
- mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
+ StatsLog.write(
+ StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
+ final int idx = mIsolatedUids.indexOfKey(isolatedUid);
+ if (idx >= 0) {
+ final int ownerUid = mIsolatedUids.valueAt(idx);
+ final Uid u = getUidStatsLocked(ownerUid);
+ u.removeIsolatedUid(isolatedUid);
+ mIsolatedUids.removeAt(idx);
+ }
+ mKernelUidCpuTimeReader.removeUid(isolatedUid);
+ mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
}
public int mapUid(int uid) {
@@ -5899,6 +6058,11 @@
LongSamplingCounterArray mCpuFreqTimeMs;
LongSamplingCounterArray mScreenOffCpuFreqTimeMs;
+ LongSamplingCounterArray[] mProcStateTimeMs;
+ LongSamplingCounterArray[] mProcStateScreenOffTimeMs;
+
+ IntArray mChildUids;
+
/**
* The statistics we have collected for this uid's wake locks.
*/
@@ -5984,40 +6148,107 @@
mProcessStateTimer = new StopwatchTimer[NUM_PROCESS_STATE];
}
+ @VisibleForTesting
+ public void setProcessStateForTest(int procState) {
+ mProcessState = procState;
+ }
+
@Override
public long[] getCpuFreqTimes(int which) {
- if (mCpuFreqTimeMs == null) {
+ return nullIfAllZeros(mCpuFreqTimeMs, which);
+ }
+
+ @Override
+ public long[] getScreenOffCpuFreqTimes(int which) {
+ return nullIfAllZeros(mScreenOffCpuFreqTimeMs, which);
+ }
+
+ @Override
+ public long[] getCpuFreqTimes(int which, int procState) {
+ if (which < 0 || which >= NUM_PROCESS_STATE) {
return null;
}
- final long[] cpuFreqTimes = mCpuFreqTimeMs.getCountsLocked(which);
- if (cpuFreqTimes == null) {
+ if (mProcStateTimeMs == null) {
return null;
}
- // Return cpuFreqTimes only if atleast one of the elements in non-zero.
- for (int i = 0; i < cpuFreqTimes.length; ++i) {
- if (cpuFreqTimes[i] != 0) {
- return cpuFreqTimes;
+ if (!mBsi.mPerProcStateCpuTimesAvailable) {
+ mProcStateTimeMs = null;
+ return null;
+ }
+ return nullIfAllZeros(mProcStateTimeMs[procState], which);
+ }
+
+ @Override
+ public long[] getScreenOffCpuFreqTimes(int which, int procState) {
+ if (which < 0 || which >= NUM_PROCESS_STATE) {
+ return null;
+ }
+ if (mProcStateScreenOffTimeMs == null) {
+ return null;
+ }
+ if (!mBsi.mPerProcStateCpuTimesAvailable) {
+ mProcStateScreenOffTimeMs = null;
+ return null;
+ }
+ return nullIfAllZeros(mProcStateScreenOffTimeMs[procState], which);
+ }
+
+ public void addIsolatedUid(int isolatedUid) {
+ if (mChildUids == null) {
+ mChildUids = new IntArray();
+ } else if (mChildUids.indexOf(isolatedUid) >= 0) {
+ return;
+ }
+ mChildUids.add(isolatedUid);
+ }
+
+ public void removeIsolatedUid(int isolatedUid) {
+ final int idx = mChildUids == null ? -1 : mChildUids.indexOf(isolatedUid);
+ if (idx < 0) {
+ return;
+ }
+ mChildUids.remove(idx);
+ }
+
+ private long[] nullIfAllZeros(LongSamplingCounterArray cpuTimesMs, int which) {
+ if (cpuTimesMs == null) {
+ return null;
+ }
+ final long[] counts = cpuTimesMs.getCountsLocked(which);
+ if (counts == null) {
+ return null;
+ }
+ // Return counts only if at least one of the elements is non-zero.
+ for (int i = counts.length - 1; i >= 0; --i) {
+ if (counts[i] != 0) {
+ return counts;
}
}
return null;
}
- @Override
- public long[] getScreenOffCpuFreqTimes(int which) {
- if (mScreenOffCpuFreqTimeMs == null) {
- return null;
+ private void addProcStateTimesMs(int procState, long[] cpuTimesMs) {
+ if (mProcStateTimeMs == null) {
+ mProcStateTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
}
- final long[] cpuFreqTimes = mScreenOffCpuFreqTimeMs.getCountsLocked(which);
- if (cpuFreqTimes == null) {
- return null;
+ if (mProcStateTimeMs[procState] == null
+ || mProcStateTimeMs[procState].getSize() != cpuTimesMs.length) {
+ mProcStateTimeMs[procState] = new LongSamplingCounterArray(
+ mBsi.mOnBatteryTimeBase);
}
- // Return cpuFreqTimes only if atleast one of the elements in non-zero.
- for (int i = 0; i < cpuFreqTimes.length; ++i) {
- if (cpuFreqTimes[i] != 0) {
- return cpuFreqTimes;
- }
+ mProcStateTimeMs[procState].addCountLocked(cpuTimesMs);
+ }
+
+ private void addProcStateScreenOffTimesMs(int procState, long[] cpuTimesMs) {
+ if (mProcStateScreenOffTimeMs == null) {
+ mProcStateScreenOffTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
}
- return null;
+ if (mProcStateScreenOffTimeMs[procState] == null
+ || mProcStateScreenOffTimeMs[procState].getSize() != cpuTimesMs.length) {
+ mProcStateScreenOffTimeMs[procState] = new LongSamplingCounterArray(
+ mBsi.mOnBatteryScreenOffTimeBase);
+ }
+ mProcStateScreenOffTimeMs[procState].addCountLocked(cpuTimesMs);
}
@Override
@@ -6999,6 +7230,21 @@
mScreenOffCpuFreqTimeMs.reset(false);
}
+ if (mProcStateTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+ if (counters != null) {
+ counters.reset(false);
+ }
+ }
+ }
+ if (mProcStateScreenOffTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+ if (counters != null) {
+ counters.reset(false);
+ }
+ }
+ }
+
resetLongCounterIfNotNull(mMobileRadioApWakeupCount, false);
resetLongCounterIfNotNull(mWifiRadioApWakeupCount, false);
@@ -7189,6 +7435,20 @@
mScreenOffCpuFreqTimeMs.detach();
}
+ if (mProcStateTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+ if (counters != null) {
+ counters.detach();
+ }
+ }
+ }
+ if (mProcStateScreenOffTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+ if (counters != null) {
+ counters.detach();
+ }
+ }
+ }
detachLongCounterIfNotNull(mMobileRadioApWakeupCount);
detachLongCounterIfNotNull(mWifiRadioApWakeupCount);
}
@@ -7449,6 +7709,22 @@
LongSamplingCounterArray.writeToParcel(out, mCpuFreqTimeMs);
LongSamplingCounterArray.writeToParcel(out, mScreenOffCpuFreqTimeMs);
+ if (mProcStateTimeMs != null) {
+ out.writeInt(mProcStateTimeMs.length);
+ for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+ LongSamplingCounterArray.writeToParcel(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
+ if (mProcStateScreenOffTimeMs != null) {
+ out.writeInt(mProcStateScreenOffTimeMs.length);
+ for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+ LongSamplingCounterArray.writeToParcel(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
if (mMobileRadioApWakeupCount != null) {
out.writeInt(1);
@@ -7750,6 +8026,27 @@
mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(
in, mBsi.mOnBatteryScreenOffTimeBase);
+ int length = in.readInt();
+ if (length == NUM_PROCESS_STATE) {
+ mProcStateTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ mProcStateTimeMs[procState] = LongSamplingCounterArray.readFromParcel(
+ in, mBsi.mOnBatteryTimeBase);
+ }
+ } else {
+ mProcStateTimeMs = null;
+ }
+ length = in.readInt();
+ if (length == NUM_PROCESS_STATE) {
+ mProcStateScreenOffTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ mProcStateScreenOffTimeMs[procState] = LongSamplingCounterArray.readFromParcel(
+ in, mBsi.mOnBatteryScreenOffTimeBase);
+ }
+ } else {
+ mProcStateScreenOffTimeMs = null;
+ }
+
if (in.readInt() != 0) {
mMobileRadioApWakeupCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
} else {
@@ -8671,25 +8968,7 @@
// Make special note of Foreground Services
final boolean userAwareService =
(procState == ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
- if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
- uidRunningState = ActivityManager.PROCESS_STATE_NONEXISTENT;
- } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
- uidRunningState = PROCESS_STATE_TOP;
- } 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_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;
- }
+ uidRunningState = BatteryStats.mapToInternalProcessState(procState);
if (mProcessState == uidRunningState && userAwareService == mInForegroundService) {
return;
@@ -8701,6 +8980,18 @@
if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
+
+ if (mBsi.mPerProcStateCpuTimesAvailable) {
+ if (mBsi.mPendingUids.size() == 0) {
+ mBsi.mExternalSync.scheduleReadProcStateCpuTimes();
+ }
+ if (mBsi.mPendingUids.indexOfKey(mUid) < 0
+ || ArrayUtils.contains(CRITICAL_PROC_STATES, mProcessState)) {
+ mBsi.mPendingUids.put(mUid, mProcessState);
+ }
+ } else {
+ mBsi.mPendingUids.clear();
+ }
}
mProcessState = uidRunningState;
if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
@@ -11599,6 +11890,51 @@
return (msPerLevel * (100-mCurrentBatteryLevel)) * 1000;
}
+ /*@hide */
+ public CellularBatteryStats getCellularBatteryStats() {
+ CellularBatteryStats s = new CellularBatteryStats();
+ final int which = STATS_SINCE_CHARGED;
+ final long rawRealTime = SystemClock.elapsedRealtime() * 1000;
+ final ControllerActivityCounter counter = getModemControllerActivity();
+ final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which);
+ final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which);
+ final long energyConsumedMaMs = counter.getPowerCounter().getCountLocked(which);
+ long[] timeInRatMs = new long[BatteryStats.NUM_DATA_CONNECTION_TYPES];
+ for (int i = 0; i < timeInRatMs.length; i++) {
+ timeInRatMs[i] = getPhoneDataConnectionTime(i, rawRealTime, which) / 1000;
+ }
+ long[] timeInRxSignalStrengthLevelMs = new long[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+ for (int i = 0; i < timeInRxSignalStrengthLevelMs.length; i++) {
+ timeInRxSignalStrengthLevelMs[i]
+ = getPhoneSignalStrengthTime(i, rawRealTime, which) / 1000;
+ }
+ long[] txTimeMs = new long[Math.min(ModemActivityInfo.TX_POWER_LEVELS,
+ counter.getTxTimeCounters().length)];
+ long totalTxTimeMs = 0;
+ for (int i = 0; i < txTimeMs.length; i++) {
+ txTimeMs[i] = counter.getTxTimeCounters()[i].getCountLocked(which);
+ totalTxTimeMs += txTimeMs[i];
+ }
+ final long totalControllerActivityTimeMs
+ = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000;
+ final long sleepTimeMs
+ = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + totalTxTimeMs);
+ s.setLoggingDurationMs(computeBatteryRealtime(rawRealTime, which) / 1000);
+ s.setKernelActiveTimeMs(getMobileRadioActiveTime(rawRealTime, which) / 1000);
+ s.setNumPacketsTx(getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+ s.setNumBytesTx(getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+ s.setNumPacketsRx(getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+ s.setNumBytesRx(getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+ s.setSleepTimeMs(sleepTimeMs);
+ s.setIdleTimeMs(idleTimeMs);
+ s.setRxTimeMs(rxTimeMs);
+ s.setEnergyConsumedMaMs(energyConsumedMaMs);
+ s.setTimeInRatMs(timeInRatMs);
+ s.setTimeInRxSignalStrengthLevelMs(timeInRxSignalStrengthLevelMs);
+ s.setTxTimeMs(txTimeMs);
+ return s;
+ }
+
@Override
public LevelStepTracker getChargeLevelStepTracker() {
return mChargeStepTracker;
@@ -11769,11 +12105,23 @@
return u;
}
+ /**
+ * Retrieve the statistics object for a particular uid. Returns null if the object is not
+ * available.
+ */
+ public Uid getAvailableUidStatsLocked(int uid) {
+ Uid u = mUidStats.get(uid);
+ return u;
+ }
+
public void onCleanupUserLocked(int userId) {
final int firstUidForUser = UserHandle.getUid(userId, 0);
final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
mKernelUidCpuFreqTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
mKernelUidCpuTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+ if (mKernelSingleUidTimeReader != null) {
+ mKernelSingleUidTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+ }
}
public void onUserRemovedLocked(int userId) {
@@ -11792,6 +12140,9 @@
public void removeUidStatsLocked(int uid) {
mKernelUidCpuTimeReader.removeUid(uid);
mKernelUidCpuFreqTimeReader.removeUid(uid);
+ if (mKernelSingleUidTimeReader != null) {
+ mKernelSingleUidTimeReader.removeUid(uid);
+ }
mUidStats.remove(uid);
}
@@ -12393,6 +12744,28 @@
in, mOnBatteryTimeBase);
u.mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readSummaryFromParcelLocked(
in, mOnBatteryScreenOffTimeBase);
+ int length = in.readInt();
+ if (length == Uid.NUM_PROCESS_STATE) {
+ u.mProcStateTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ u.mProcStateTimeMs[procState]
+ = LongSamplingCounterArray.readSummaryFromParcelLocked(
+ in, mOnBatteryTimeBase);
+ }
+ } else {
+ u.mProcStateTimeMs = null;
+ }
+ length = in.readInt();
+ if (length == Uid.NUM_PROCESS_STATE) {
+ u.mProcStateScreenOffTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ u.mProcStateScreenOffTimeMs[procState]
+ = LongSamplingCounterArray.readSummaryFromParcelLocked(
+ in, mOnBatteryScreenOffTimeBase);
+ }
+ } else {
+ u.mProcStateScreenOffTimeMs = null;
+ }
if (in.readInt() != 0) {
u.mMobileRadioApWakeupCount = new LongSamplingCounter(mOnBatteryTimeBase);
@@ -12846,6 +13219,23 @@
LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mCpuFreqTimeMs);
LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mScreenOffCpuFreqTimeMs);
+ if (u.mProcStateTimeMs != null) {
+ out.writeInt(u.mProcStateTimeMs.length);
+ for (LongSamplingCounterArray counters : u.mProcStateTimeMs) {
+ LongSamplingCounterArray.writeSummaryToParcelLocked(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
+ if (u.mProcStateScreenOffTimeMs != null) {
+ out.writeInt(u.mProcStateScreenOffTimeMs.length);
+ for (LongSamplingCounterArray counters : u.mProcStateScreenOffTimeMs) {
+ LongSamplingCounterArray.writeSummaryToParcelLocked(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
+
if (u.mMobileRadioApWakeupCount != null) {
out.writeInt(1);
u.mMobileRadioApWakeupCount.writeSummaryFromParcelLocked(out);
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
new file mode 100644
index 0000000..ca635a4
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.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 com.android.internal.os;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+@VisibleForTesting(visibility = PACKAGE)
+public class KernelSingleUidTimeReader {
+ private final String TAG = KernelUidCpuFreqTimeReader.class.getName();
+ private final boolean DBG = false;
+
+ private final String PROC_FILE_DIR = "/proc/uid/";
+ private final String PROC_FILE_NAME = "/time_in_state";
+
+ @VisibleForTesting
+ public static final int TOTAL_READ_ERROR_COUNT = 5;
+
+ @GuardedBy("this")
+ private final int mCpuFreqsCount;
+
+ @GuardedBy("this")
+ private final SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
+
+ @GuardedBy("this")
+ private int mReadErrorCounter;
+ @GuardedBy("this")
+ private boolean mSingleUidCpuTimesAvailable = true;
+
+ private final Injector mInjector;
+
+ KernelSingleUidTimeReader(int cpuFreqsCount) {
+ this(cpuFreqsCount, new Injector());
+ }
+
+ public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
+ mInjector = injector;
+ mCpuFreqsCount = cpuFreqsCount;
+ if (mCpuFreqsCount == 0) {
+ mSingleUidCpuTimesAvailable = false;
+ }
+ }
+
+ public boolean singleUidCpuTimesAvailable() {
+ return mSingleUidCpuTimesAvailable;
+ }
+
+ public long[] readDeltaMs(int uid) {
+ synchronized (this) {
+ if (!mSingleUidCpuTimesAvailable) {
+ return null;
+ }
+ // Read total cpu times from the proc file.
+ final String procFile = new StringBuilder(PROC_FILE_DIR)
+ .append(uid)
+ .append(PROC_FILE_NAME).toString();
+ final long[] cpuTimesMs = new long[mCpuFreqsCount];
+ try {
+ final byte[] data = mInjector.readData(procFile);
+ final ByteBuffer buffer = ByteBuffer.wrap(data);
+ buffer.order(ByteOrder.nativeOrder());
+ for (int i = 0; i < mCpuFreqsCount; ++i) {
+ // Times read will be in units of 10ms
+ cpuTimesMs[i] = buffer.getLong() * 10;
+ }
+ } catch (Exception e) {
+ if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+ mSingleUidCpuTimesAvailable = false;
+ }
+ if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
+ return null;
+ }
+
+ return computeDelta(uid, cpuTimesMs);
+ }
+ }
+
+ /**
+ * Compute and return cpu times delta of an uid using previously read cpu times and
+ * {@param latestCpuTimesMs}.
+ *
+ * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
+ */
+ public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
+ synchronized (this) {
+ if (!mSingleUidCpuTimesAvailable) {
+ return null;
+ }
+ // Subtract the last read cpu times to get deltas.
+ final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
+ final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
+ if (deltaTimesMs == null) {
+ if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
+ + "; last=" + Arrays.toString(lastCpuTimesMs)
+ + "; latest=" + Arrays.toString(latestCpuTimesMs));
+ return null;
+ }
+ // If all elements are zero, return null to avoid unnecessary work on the caller side.
+ boolean hasNonZero = false;
+ for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
+ if (deltaTimesMs[i] > 0) {
+ hasNonZero = true;
+ break;
+ }
+ }
+ if (hasNonZero) {
+ mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
+ return deltaTimesMs;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns null if the latest cpu times are not valid**, otherwise delta of
+ * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
+ *
+ * **latest cpu times are considered valid if all the cpu times are +ve and
+ * greater than or equal to previously read cpu times.
+ */
+ @GuardedBy("this")
+ @VisibleForTesting(visibility = PACKAGE)
+ public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
+ for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+ if (latestCpuTimesMs[i] < 0) {
+ return null;
+ }
+ }
+ if (lastCpuTimesMs == null) {
+ return latestCpuTimesMs;
+ }
+ final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
+ for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+ deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
+ if (deltaTimesMs[i] < 0) {
+ return null;
+ }
+ }
+ return deltaTimesMs;
+ }
+
+ public void removeUid(int uid) {
+ synchronized (this) {
+ mLastUidCpuTimeMs.delete(uid);
+ }
+ }
+
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (endUid < startUid) {
+ return;
+ }
+ synchronized (this) {
+ mLastUidCpuTimeMs.put(startUid, null);
+ mLastUidCpuTimeMs.put(endUid, null);
+ final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
+ final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
+ mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
+ }
+ }
+
+ @VisibleForTesting
+ public static class Injector {
+ public byte[] readData(String procFile) throws IOException {
+ return Files.readAllBytes(Paths.get(procFile));
+ }
+ }
+
+ @VisibleForTesting
+ public SparseArray<long[]> getLastUidCpuTimeMs() {
+ return mLastUidCpuTimeMs;
+ }
+
+ @VisibleForTesting
+ public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
+ mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index a39997d..b8982cc 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -66,13 +66,21 @@
// start reading) and if it is not available, we simply ignore further read requests.
private static final int TOTAL_READ_ERROR_COUNT = 5;
private int mReadErrorCounter;
- private boolean mProcFileAvailable;
private boolean mPerClusterTimesAvailable;
+ private boolean mAllUidTimesAvailable = true;
public boolean perClusterTimesAvailable() {
return mPerClusterTimesAvailable;
}
+ public boolean allUidTimesAvailable() {
+ return mAllUidTimesAvailable;
+ }
+
+ public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
+ return mLastUidCpuFreqTimeMs;
+ }
+
public long[] readFreqs(@NonNull PowerProfile powerProfile) {
checkNotNull(powerProfile);
@@ -80,15 +88,16 @@
// No need to read cpu freqs more than once.
return mCpuFreqs;
}
- if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+ if (!mAllUidTimesAvailable) {
return null;
}
final int oldMask = StrictMode.allowThreadDiskReadsMask();
try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
- mProcFileAvailable = true;
return readFreqs(reader, powerProfile);
} catch (IOException e) {
- mReadErrorCounter++;
+ if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+ mAllUidTimesAvailable = false;
+ }
Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
return null;
} finally {
@@ -107,7 +116,7 @@
}
public void readDelta(@Nullable Callback callback) {
- if (!mProcFileAvailable) {
+ if (mCpuFreqs == null) {
return;
}
final int oldMask = StrictMode.allowThreadDiskReadsMask();
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index f983de1..7985e57 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -290,11 +290,11 @@
if (cur instanceof ArraySet) {
ArraySet<T> arraySet = (ArraySet<T>) cur;
for (int i = 0; i < size; i++) {
- action.accept(arraySet.valueAt(i));
+ action.acceptOrThrow(arraySet.valueAt(i));
}
} else {
for (T t : cur) {
- action.accept(t);
+ action.acceptOrThrow(t);
}
}
} catch (Exception e) {
diff --git a/core/java/com/android/internal/util/FunctionalUtils.java b/core/java/com/android/internal/util/FunctionalUtils.java
index eb92c1c..82ac241 100644
--- a/core/java/com/android/internal/util/FunctionalUtils.java
+++ b/core/java/com/android/internal/util/FunctionalUtils.java
@@ -16,6 +16,9 @@
package com.android.internal.util;
+import android.os.RemoteException;
+
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -25,6 +28,21 @@
private FunctionalUtils() {}
/**
+ * Converts a lambda expression that throws a checked exception(s) into a regular
+ * {@link Consumer} by propagating any checked exceptions as {@link RuntimeException}
+ */
+ public static <T> Consumer<T> uncheckExceptions(ThrowingConsumer<T> action) {
+ return action;
+ }
+
+ /**
+ *
+ */
+ public static <T> Consumer<T> ignoreRemoteException(RemoteExceptionIgnoringConsumer<T> action) {
+ return action;
+ }
+
+ /**
* An equivalent of {@link Runnable} that allows throwing checked exceptions
*
* This can be used to specify a lambda argument without forcing all the checked exceptions
@@ -47,13 +65,43 @@
}
/**
- * An equivalent of {@link java.util.function.Consumer} that allows throwing checked exceptions
+ * A {@link Consumer} that allows throwing checked exceptions from its single abstract method.
*
- * This can be used to specify a lambda argument without forcing all the checked exceptions
- * to be handled within it
+ * Can be used together with {@link #uncheckExceptions} to effectively turn a lambda expression
+ * that throws a checked exception into a regular {@link Consumer}
*/
@FunctionalInterface
- public interface ThrowingConsumer<T> {
- void accept(T t) throws Exception;
+ @SuppressWarnings("FunctionalInterfaceMethodChanged")
+ public interface ThrowingConsumer<T> extends Consumer<T> {
+ void acceptOrThrow(T t) throws Exception;
+
+ @Override
+ default void accept(T t) {
+ try {
+ acceptOrThrow(t);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+
+ /**
+ * A {@link Consumer} that automatically ignores any {@link RemoteException}s.
+ *
+ * Used by {@link #ignoreRemoteException}
+ */
+ @FunctionalInterface
+ @SuppressWarnings("FunctionalInterfaceMethodChanged")
+ public interface RemoteExceptionIgnoringConsumer<T> extends Consumer<T> {
+ void acceptOrThrow(T t) throws RemoteException;
+
+ @Override
+ default void accept(T t) {
+ try {
+ acceptOrThrow(t);
+ } catch (RemoteException ex) {
+ // ignore
+ }
+ }
}
}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 164a745..43536a5 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -16,6 +16,7 @@
package com.android.internal.widget;
+import android.app.PendingIntent;
import android.app.trust.IStrongAuthTracker;
import android.os.Bundle;
import android.security.recoverablekeystore.KeyEntryRecoveryData;
@@ -24,6 +25,8 @@
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.VerifyCredentialResponse;
+import java.util.Map;
+
/** {@hide} */
interface ILockSettings {
void setBoolean(in String key, in boolean value, in int userId);
@@ -63,8 +66,11 @@
void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList,
int userId);
KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId);
+ void setSnapshotCreatedPendingIntent(in PendingIntent intent, int userId);
+ Map getRecoverySnapshotVersions(int userId);
void setServerParameters(long serverParameters, int userId);
void setRecoveryStatus(in String packageName, in String[] aliases, int status, int userId);
+ Map getRecoveryStatus(in String packageName, int userId);
void setRecoverySecretTypes(in int[] secretTypes, int userId);
int[] getRecoverySecretTypes(int userId);
int[] getPendingRecoverySecretTypes(int userId);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 297bae2..b3f66e9 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -107,7 +107,6 @@
"android_os_VintfRuntimeInfo.cpp",
"android_net_LocalSocketImpl.cpp",
"android_net_NetUtils.cpp",
- "android_net_TrafficStats.cpp",
"android_nio_utils.cpp",
"android_util_AssetManager.cpp",
"android_util_Binder.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b5d1868..6d7fe05 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -176,7 +176,6 @@
extern int register_android_os_SharedMemory(JNIEnv* env);
extern int register_android_net_LocalSocketImpl(JNIEnv* env);
extern int register_android_net_NetworkUtils(JNIEnv* env);
-extern int register_android_net_TrafficStats(JNIEnv* env);
extern int register_android_text_AndroidCharacter(JNIEnv *env);
extern int register_android_text_Hyphenator(JNIEnv *env);
extern int register_android_text_MeasuredText(JNIEnv* env);
@@ -1433,7 +1432,6 @@
REG_JNI(register_android_os_UEventObserver),
REG_JNI(register_android_net_LocalSocketImpl),
REG_JNI(register_android_net_NetworkUtils),
- REG_JNI(register_android_net_TrafficStats),
REG_JNI(register_android_os_MemoryFile),
REG_JNI(register_android_os_SharedMemory),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 79c5a34..79aa5ac 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -385,10 +385,18 @@
return nullObjectReturn("codec->getAndroidPixels() failed.");
}
+ // This is weird so let me explain: we could use the scale parameter
+ // directly, but for historical reasons this is how the corresponding
+ // Dalvik code has always behaved. We simply recreate the behavior here.
+ // The result is slightly different from simply using scale because of
+ // the 0.5f rounding bias applied when computing the target image size
+ const float scaleX = scaledWidth / float(decodingBitmap.width());
+ const float scaleY = scaledHeight / float(decodingBitmap.height());
+
jbyteArray ninePatchChunk = NULL;
if (peeker.mPatch != NULL) {
if (willScale) {
- peeker.scale(scale, scale, scaledWidth, scaledHeight);
+ peeker.scale(scaleX, scaleY, scaledWidth, scaledHeight);
}
size_t ninePatchArraySize = peeker.mPatch->serializedSize();
@@ -419,14 +427,6 @@
SkBitmap outputBitmap;
if (willScale) {
- // This is weird so let me explain: we could use the scale parameter
- // directly, but for historical reasons this is how the corresponding
- // Dalvik code has always behaved. We simply recreate the behavior here.
- // The result is slightly different from simply using scale because of
- // the 0.5f rounding bias applied when computing the target image size
- const float sx = scaledWidth / float(decodingBitmap.width());
- const float sy = scaledHeight / float(decodingBitmap.height());
-
// Set the allocator for the outputBitmap.
SkBitmap::Allocator* outputAllocator;
if (javaBitmap != nullptr) {
@@ -456,7 +456,7 @@
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);
diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp
index 066d47b..9171fc6 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -76,13 +76,18 @@
if (!mPatch) {
return;
}
- mPatch->paddingLeft = int(mPatch->paddingLeft * scaleX + 0.5f);
- mPatch->paddingTop = int(mPatch->paddingTop * scaleY + 0.5f);
- mPatch->paddingRight = int(mPatch->paddingRight * scaleX + 0.5f);
- mPatch->paddingBottom = int(mPatch->paddingBottom * scaleY + 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(mPatch->getXDivs(), mPatch->numXDivs, scaleX, scaledWidth - 1);
- scaleDivRange(mPatch->getYDivs(), mPatch->numYDivs, scaleY, scaledHeight - 1);
+ 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/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index a51253f..59582ec 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -327,7 +327,7 @@
optional string crypto_state = 21;
optional string crypto_type = 22;
optional string dalvik_vm_native_bridge = 23;
- optional int32 debuggable = 24;
+ optional bool debuggable = 24;
optional string frp_pst = 25;
optional string gfx_driver_0 = 26;
@@ -413,19 +413,29 @@
optional string revision = 35;
optional int32 sf_lcd_density = 36;
optional bool storage_manager_enabled = 37;
- optional bool telephony_call_ring_multiple = 38;
- optional int32 telephony_default_cdma_sub = 39;
- optional int32 telephony_default_network = 40;
- optional string url_legal = 41;
- optional string url_legal_android_privacy = 42;
- optional string vendor_build_date = 43;
- optional int32 vendor_build_date_utc = 44;
- optional string vendor_build_fingerprint = 45;
- optional string vndk_version = 46;
- optional int32 vts_coverage = 47;
- optional string zygote = 48;
- // Next Tag: 49
+ message Telephony {
+ optional bool call_ring_multiple = 1;
+ optional int32 default_cdma_sub = 2;
+ optional int32 default_network = 3;
+ }
+ optional Telephony telephony = 38;
+
+ optional string url_legal = 39;
+ optional string url_legal_android_privacy = 40;
+
+ message Vendor {
+ optional string build_date = 1;
+ optional int32 build_date_utc = 2;
+ optional string build_fingerprint = 3;
+ }
+ optional Vendor vendor = 41;
+
+ optional string vndk_version = 42;
+ optional int32 vts_coverage = 43;
+ optional string zygote = 44;
+
+ // Next Tag: 45
}
optional Ro ro = 21;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 13fedfe..86d2ee3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3505,6 +3505,7 @@
@hide -->
<permission android:name="android.permission.LOCAL_MAC_ADDRESS"
android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS"/>
<!-- @SystemApi Allows access to MAC addresses of WiFi and Bluetooth peer devices.
@hide -->
@@ -3632,6 +3633,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 0000000..c678ad4
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_settings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M12.584,15.93c0.026-0.194,0.044-0.397,0.044-0.608c0-0.211-0.018-0.405-0.044-0.608l1.304-1.022
+c0.115-0.088,0.15-0.256,0.071-0.397l-1.234-2.133c-0.071-0.132-0.238-0.185-0.379-0.132l-1.533,0.617
+c-0.317-0.247-0.67-0.449-1.04-0.608L9.535,9.4c-0.018-0.132-0.141-0.247-0.3-0.247H6.768c-0.15,0-0.282,0.115-0.3,0.256
+L6.23,11.048c-0.379,0.159-0.723,0.361-1.04,0.608l-1.533-0.617c-0.141-0.053-0.3,0-0.379,0.132l-1.234,2.133
+c-0.079,0.132-0.044,0.3,0.07,0.397l1.304,1.022c-0.026,0.194-0.044,0.405-0.044,0.608s0.018,0.405,0.044,0.608l-1.304,1.022
+c-0.115,0.088-0.15,0.256-0.07,0.397l1.234,2.133c0.07,0.132,0.238,0.185,0.379,0.132l1.533-0.617
+c0.317,0.247,0.67,0.449,1.04,0.608l0.238,1.639c0.018,0.15,0.15,0.256,0.3,0.256h2.467c0.159,0,0.282-0.115,0.3-0.256
+l0.238-1.639c0.379-0.15,0.723-0.361,1.04-0.608l1.533,0.617c0.141,0.053,0.3,0,0.379-0.132l1.234-2.133
+c0.071-0.132,0.044-0.3-0.07-0.397L12.584,15.93z
+M8.002,17.481c-1.19,0-2.159-0.969-2.159-2.159s0.969-2.159,2.159-2.159
+s2.159,0.969,2.159,2.159C10.161,16.512,9.191,17.481,8.002,17.481z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M16.003,12.026l5.995-7.474c-0.229-0.172-2.537-2.06-6-2.06s-5.771,1.889-6,2.06l5.995,7.469l0.005,0.01L16.003,12.026z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/raw/color_fade_vert.vert b/core/res/res/raw/color_fade_vert.vert
index d17437f..b501b06 100644
--- a/core/res/res/raw/color_fade_vert.vert
+++ b/core/res/res/raw/color_fade_vert.vert
@@ -1,6 +1,5 @@
uniform mat4 proj_matrix;
uniform mat4 tex_matrix;
-uniform float scale;
attribute vec2 position;
attribute vec2 uv;
varying vec2 UV;
@@ -9,5 +8,5 @@
{
vec4 transformed_uv = tex_matrix * vec4(uv.x, uv.y, 1.0, 1.0);
UV = transformed_uv.st / transformed_uv.q;
- gl_Position = vec4(scale, scale, 1.0, 1.0) * (proj_matrix * vec4(position.x, position.y, 0.0, 1.0));
+ gl_Position = proj_matrix * vec4(position.x, position.y, 0.0, 1.0);
}
diff --git a/core/res/res/values-az/required_apps_managed_device.xml b/core/res/res/values-az/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-az/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-az/required_apps_managed_profile.xml b/core/res/res/values-az/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-az/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-az/required_apps_managed_user.xml b/core/res/res/values-az/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-az/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-bs/required_apps_managed_device.xml b/core/res/res/values-bs/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-bs/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-bs/required_apps_managed_profile.xml b/core/res/res/values-bs/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-bs/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-bs/required_apps_managed_user.xml b/core/res/res/values-bs/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-bs/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-en-rAU/required_apps_managed_device.xml b/core/res/res/values-en-rAU/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-en-rAU/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-en-rAU/required_apps_managed_profile.xml b/core/res/res/values-en-rAU/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-en-rAU/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-en-rAU/required_apps_managed_user.xml b/core/res/res/values-en-rAU/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-en-rAU/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-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index a175e6c..7a2e15a 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -212,6 +212,7 @@
<string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
<string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+ <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
<string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
<string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1498,6 +1499,8 @@
<string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings > Accessibility."</string>
<string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
<string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+ <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+ <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
<string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
<string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
<string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1687,16 @@
<string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
<string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
<string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+ <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
<string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
<string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
<string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rCA/required_apps_managed_device.xml b/core/res/res/values-en-rCA/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-en-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-en-rCA/required_apps_managed_profile.xml b/core/res/res/values-en-rCA/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-en-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-en-rCA/required_apps_managed_user.xml b/core/res/res/values-en-rCA/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-en-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-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index a175e6c..7a2e15a 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -212,6 +212,7 @@
<string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
<string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+ <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
<string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
<string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1498,6 +1499,8 @@
<string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings > Accessibility."</string>
<string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
<string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+ <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+ <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
<string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
<string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
<string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1687,16 @@
<string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
<string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
<string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+ <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
<string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
<string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
<string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rGB/required_apps_managed_device.xml b/core/res/res/values-en-rGB/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-en-rGB/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-en-rGB/required_apps_managed_profile.xml b/core/res/res/values-en-rGB/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-en-rGB/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-en-rGB/required_apps_managed_user.xml b/core/res/res/values-en-rGB/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-en-rGB/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-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index a175e6c..7a2e15a 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -212,6 +212,7 @@
<string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
<string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+ <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
<string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
<string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1498,6 +1499,8 @@
<string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings > Accessibility."</string>
<string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
<string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+ <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+ <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
<string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
<string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
<string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1687,16 @@
<string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
<string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
<string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+ <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
<string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
<string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
<string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rIN/required_apps_managed_device.xml b/core/res/res/values-en-rIN/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-en-rIN/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-en-rIN/required_apps_managed_profile.xml b/core/res/res/values-en-rIN/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-en-rIN/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-en-rIN/required_apps_managed_user.xml b/core/res/res/values-en-rIN/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-en-rIN/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-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index a175e6c..7a2e15a 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -212,6 +212,7 @@
<string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
<string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+ <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
<string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
<string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1498,6 +1499,8 @@
<string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings > Accessibility."</string>
<string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
<string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+ <string name="color_inversion_feature_name" msgid="4231186527799958644">"Colour Inversion"</string>
+ <string name="color_correction_feature_name" msgid="6779391426096954933">"Colour Correction"</string>
<string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
<string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
<string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1687,16 @@
<string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
<string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
<string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+ <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
<string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
<string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
<string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-en-rXC/required_apps_managed_device.xml b/core/res/res/values-en-rXC/required_apps_managed_device.xml
new file mode 100644
index 0000000..70d510f
--- /dev/null
+++ b/core/res/res/values-en-rXC/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-en-rXC/required_apps_managed_profile.xml b/core/res/res/values-en-rXC/required_apps_managed_profile.xml
new file mode 100644
index 0000000..9ca9960
--- /dev/null
+++ b/core/res/res/values-en-rXC/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-en-rXC/required_apps_managed_user.xml b/core/res/res/values-en-rXC/required_apps_managed_user.xml
new file mode 100644
index 0000000..b2ef802
--- /dev/null
+++ b/core/res/res/values-en-rXC/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-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 5635292..22da8ef 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -212,6 +212,7 @@
<string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
<string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
+ <string name="global_action_logout" msgid="935179188218826050">"End session"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
<string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an e-mail message. It will take a little time from starting the bug report until it is ready to be sent; please be patient."</string>
<string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactive report"</string>
@@ -1498,6 +1499,8 @@
<string name="accessibility_shortcut_toogle_warning" msgid="7256507885737444807">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings > Accessibility."</string>
<string name="disable_accessibility_shortcut" msgid="627625354248453445">"Turn off Shortcut"</string>
<string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Use Shortcut"</string>
+ <string name="color_inversion_feature_name" msgid="4231186527799958644">"Color Inversion"</string>
+ <string name="color_correction_feature_name" msgid="6779391426096954933">"Color Correction"</string>
<string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> on"</string>
<string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Accessibility Shortcut turned <xliff:g id="SERVICE_NAME">%1$s</xliff:g> off"</string>
<string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Choose a feature to use when you tap the Accessibility button:"</string>
@@ -1684,13 +1687,16 @@
<string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Weeknight"</string>
<string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Weekend"</string>
<string name="zen_mode_default_events_name" msgid="8158334939013085363">"Event"</string>
+ <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"Sleeping"</string>
<string name="muted_by" msgid="6147073845094180001">"Muted by <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
<string name="system_error_wipe_data" msgid="6608165524785354962">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
<string name="system_error_manufacturer" msgid="8086872414744210668">"There\'s an internal problem with your device. Contact your manufacturer for details."</string>
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD request is modified to DIAL request."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD request is modified to SS request."</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD request is modified to new USSD request."</string>
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD request is modified to Video DIAL request."</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS request is modified to DIAL request."</string>
+ <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS request is modified to Video DIAL request."</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS request is modified to USSD request."</string>
<string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS request is modified to new SS request."</string>
<string name="notification_work_profile_content_description" msgid="4600554564103770764">"Work profile"</string>
diff --git a/core/res/res/values-es/required_apps_managed_device.xml b/core/res/res/values-es/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-es/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/required_apps_managed_profile.xml b/core/res/res/values-es/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-es/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/required_apps_managed_user.xml b/core/res/res/values-es/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-es/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/required_apps_managed_device.xml b/core/res/res/values-fr/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-fr/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/required_apps_managed_profile.xml b/core/res/res/values-fr/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-fr/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/required_apps_managed_user.xml b/core/res/res/values-fr/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-fr/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-in/required_apps_managed_device.xml b/core/res/res/values-in/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-in/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-in/required_apps_managed_profile.xml b/core/res/res/values-in/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-in/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-in/required_apps_managed_user.xml b/core/res/res/values-in/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-in/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-it/required_apps_managed_device.xml b/core/res/res/values-it/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-it/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-it/required_apps_managed_profile.xml b/core/res/res/values-it/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-it/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-it/required_apps_managed_user.xml b/core/res/res/values-it/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-it/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-it/strings.xml b/core/res/res/values-it/strings.xml
index 2116542..7e96aff 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1694,7 +1694,7 @@
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"La richiesta USSD è stata modificata in richiesta DIAL."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"La richiesta USSD è stata modificata in richiesta SS."</string>
<string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"La richiesta USSD è stata modificata in nuova richiesta USSD."</string>
- <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"La richiesta USSD è stata modificata in richiesta DIAL."</string>
+ <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"La richiesta USSD è stata modificata in richiesta Video DIAL."</string>
<string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"La richiesta SS è stata modificata in richiesta DIAL."</string>
<string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"La richiesta SS è stata modificata in richiesta Video DIAL."</string>
<string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"La richiesta SS è stata modificata in richiesta USSD."</string>
diff --git a/core/res/res/values-ka/required_apps_managed_device.xml b/core/res/res/values-ka/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ka/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-ka/required_apps_managed_profile.xml b/core/res/res/values-ka/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ka/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-ka/required_apps_managed_user.xml b/core/res/res/values-ka/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ka/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-my/required_apps_managed_device.xml b/core/res/res/values-my/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-my/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-my/required_apps_managed_profile.xml b/core/res/res/values-my/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-my/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-my/required_apps_managed_user.xml b/core/res/res/values-my/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-my/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-nl/required_apps_managed_device.xml b/core/res/res/values-nl/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-nl/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-nl/required_apps_managed_profile.xml b/core/res/res/values-nl/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-nl/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-nl/required_apps_managed_user.xml b/core/res/res/values-nl/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-nl/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-rBR/required_apps_managed_device.xml b/core/res/res/values-pt-rBR/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-pt-rBR/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-rBR/required_apps_managed_profile.xml b/core/res/res/values-pt-rBR/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-pt-rBR/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-rBR/required_apps_managed_user.xml b/core/res/res/values-pt-rBR/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-pt-rBR/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/required_apps_managed_device.xml b/core/res/res/values-pt/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-pt/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/required_apps_managed_profile.xml b/core/res/res/values-pt/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-pt/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/required_apps_managed_user.xml b/core/res/res/values-pt/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-pt/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-ru/required_apps_managed_device.xml b/core/res/res/values-ru/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-ru/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-ru/required_apps_managed_profile.xml b/core/res/res/values-ru/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-ru/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-ru/required_apps_managed_user.xml b/core/res/res/values-ru/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-ru/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-si/required_apps_managed_device.xml b/core/res/res/values-si/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-si/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-si/required_apps_managed_profile.xml b/core/res/res/values-si/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-si/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-si/required_apps_managed_user.xml b/core/res/res/values-si/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-si/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-uk/required_apps_managed_device.xml b/core/res/res/values-uk/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-uk/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-uk/required_apps_managed_profile.xml b/core/res/res/values-uk/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-uk/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-uk/required_apps_managed_user.xml b/core/res/res/values-uk/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-uk/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-zu/required_apps_managed_device.xml b/core/res/res/values-zu/required_apps_managed_device.xml
new file mode 100644
index 0000000..9044fcc
--- /dev/null
+++ b/core/res/res/values-zu/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-zu/required_apps_managed_profile.xml b/core/res/res/values-zu/required_apps_managed_profile.xml
new file mode 100644
index 0000000..4296b0d
--- /dev/null
+++ b/core/res/res/values-zu/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-zu/required_apps_managed_user.xml b/core/res/res/values-zu/required_apps_managed_user.xml
new file mode 100644
index 0000000..1a7ade9
--- /dev/null
+++ b/core/res/res/values-zu/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/strings.xml b/core/res/res/values/strings.xml
index 5ccaf5c..5783435 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 4b2424f..7f71446 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 3f2a46a..e094772 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/BstatsTestApp/AndroidManifest.xml b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
index 0cb5498..1e6bdc6 100644
--- a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
+++ b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
@@ -17,9 +17,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.coretests.apps.bstatstestapp">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25"/>
+
<application>
<activity android:name=".TestActivity"
android:exported="true" />
+ <service android:name=".TestService"
+ android:exported="true" />
<service android:name=".IsolatedTestService"
android:exported="true"
android:isolatedProcess="true" />
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
new file mode 100644
index 0000000..2601f35
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.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 com.android.coretests.apps.bstatstestapp;
+
+import android.os.RemoteException;
+
+import com.android.frameworks.coretests.aidl.ICmdReceiver;
+
+public class BaseCmdReceiver extends ICmdReceiver.Stub {
+ @Override
+ public void doSomeWork(int durationMs) {}
+ @Override
+ public void showApplicationOverlay() throws RemoteException {}
+ @Override
+ public void finishHost() {}
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
new file mode 100644
index 0000000..d192fbd
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.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.coretests.apps.bstatstestapp;
+
+import com.android.frameworks.coretests.aidl.ICmdCallback;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class Common {
+ private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+
+ public static void doSomeWork(int durationMs) {
+ final long endTime = SystemClock.currentThreadTimeMillis() + durationMs;
+ double x;
+ double y;
+ double z;
+ while (SystemClock.currentThreadTimeMillis() <= endTime) {
+ x = 0.02;
+ x *= 1000;
+ y = x % 5;
+ z = Math.sqrt(y / 100);
+ }
+ }
+
+ public static void notifyLaunched(Intent intent, IBinder binder, String tag) {
+ if (intent == null) {
+ return;
+ }
+
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+ final ICmdCallback callback = ICmdCallback.Stub.asInterface(
+ extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
+ try {
+ callback.onLaunched(binder);
+ } catch (RemoteException e) {
+ Log.e(tag, "Error occured while notifying the test: " + e);
+ }
+ }
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
index 1f5f397..892f60e 100644
--- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
@@ -15,17 +15,14 @@
*/
package com.android.coretests.apps.bstatstestapp;
-import com.android.frameworks.coretests.aidl.ICmdReceiver;
-
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
-import android.os.SystemClock;
import android.util.Log;
public class IsolatedTestService extends Service {
- private static final String TAG = IsolatedTestService.class.getName();
+ private static final String TAG = IsolatedTestService.class.getSimpleName();
@Override
public void onCreate() {
@@ -34,23 +31,20 @@
@Override
public IBinder onBind(Intent intent) {
+ Log.d(TAG, "onBind called. myUid=" + Process.myUid());
return mReceiver.asBinder();
}
- private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy called. myUid=" + Process.myUid());
+ }
+
+ private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
@Override
public void doSomeWork(int durationMs) {
- final long endTime = SystemClock.uptimeMillis() + durationMs;
- double x;
- double y;
- double z;
- while (SystemClock.uptimeMillis() <= endTime) {
- x = 0.02;
- x *= 1000;
- y = x % 5;
- z = Math.sqrt(y / 100);
- }
- };
+ Common.doSomeWork(durationMs);
+ }
@Override
public void finishHost() {
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
index 87b14d9..5c551d5 100644
--- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
@@ -15,19 +15,12 @@
*/
package com.android.coretests.apps.bstatstestapp;
-import com.android.frameworks.coretests.aidl.ICmdCallback;
-import com.android.frameworks.coretests.aidl.ICmdReceiver;
-
import android.app.Activity;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.SystemClock;
import android.util.Log;
public class TestActivity extends Activity {
- private static final String TAG = TestActivity.class.getName();
-
- private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+ private static final String TAG = TestActivity.class.getSimpleName();
@Override
public void onCreate(Bundle icicle) {
@@ -37,21 +30,7 @@
}
private void notifyActivityLaunched() {
- if (getIntent() == null) {
- return;
- }
-
- final Bundle extras = getIntent().getExtras();
- if (extras == null) {
- return;
- }
- final ICmdCallback callback = ICmdCallback.Stub.asInterface(
- extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
- try {
- callback.onActivityLaunched(mReceiver.asBinder());
- } catch (RemoteException e) {
- Log.e(TAG, "Error occured while notifying the test: " + e);
- }
+ Common.notifyLaunched(getIntent(), mReceiver.asBinder(), TAG);
}
@Override
@@ -60,24 +39,17 @@
Log.d(TAG, "finish called");
}
- private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+ private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
@Override
public void doSomeWork(int durationMs) {
- final long endTime = SystemClock.uptimeMillis() + durationMs;
- double x;
- double y;
- double z;
- while (SystemClock.uptimeMillis() <= endTime) {
- x = 0.02;
- x *= 1000;
- y = x % 5;
- z = Math.sqrt(y / 100);
- }
- };
+ Common.doSomeWork(durationMs);
+ }
@Override
public void finishHost() {
- finish();
+ if (!isFinishing()) {
+ finish();
+ }
}
};
}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
new file mode 100644
index 0000000..8a22aca
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.coretests.apps.bstatstestapp;
+
+import android.R;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TestService extends Service {
+ private static final String TAG = TestService.class.getSimpleName();
+
+ private static final int FLAG_START_FOREGROUND = 1;
+
+ private static final String NOTIFICATION_CHANNEL_ID = TAG;
+ private static final int NOTIFICATION_ID = 42;
+
+ private static final int TIMEOUT_OVERLAY_SEC = 2;
+
+ private View mOverlay;
+
+ @Override
+ public void onCreate() {
+ Log.d(TAG, "onCreate called. myUid=" + Process.myUid());
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG, "onStartCommand called. myUid=" + Process.myUid());
+ if (intent != null && (intent.getFlags() & FLAG_START_FOREGROUND) != 0) {
+ startForeground();
+ }
+ notifyServiceLaunched(intent);
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.d(TAG, "onBind called. myUid=" + Process.myUid());
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy called. myUid=" + Process.myUid());
+ removeOverlays();
+ }
+
+ private void notifyServiceLaunched(Intent intent) {
+ Common.notifyLaunched(intent, mReceiver.asBinder(), TAG);
+ }
+
+ private void startForeground() {
+ final NotificationManager noMan = getSystemService(NotificationManager.class);
+ noMan.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ Log.d(TAG, "Starting foreground. myUid=" + Process.myUid());
+ startForeground(NOTIFICATION_ID,
+ new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_dialog_alert)
+ .build());
+ }
+
+ private void removeOverlays() {
+ if (mOverlay != null) {
+ final WindowManager wm = TestService.this.getSystemService(WindowManager.class);
+ wm.removeView(mOverlay);
+ mOverlay = null;
+ }
+ }
+
+ private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
+ @Override
+ public void doSomeWork(int durationMs) {
+ Common.doSomeWork(durationMs);
+ }
+
+ @Override
+ public void showApplicationOverlay() throws RemoteException {
+ final WindowManager wm = TestService.this.getSystemService(WindowManager.class);
+ final Point size = new Point();
+ wm.getDefaultDisplay().getSize(size);
+
+ final WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+ wmlp.width = size.x / 2;
+ wmlp.height = size.y / 2;
+ wmlp.gravity = Gravity.CENTER | Gravity.LEFT;
+ wmlp.setTitle(TAG);
+
+ final ViewGroup.LayoutParams vglp = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+
+ mOverlay = new View(TestService.this);
+ mOverlay.setBackgroundColor(Color.GREEN);
+ mOverlay.setLayoutParams(vglp);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Handler handler = new Handler(TestService.this.getMainLooper());
+ handler.post(() -> {
+ wm.addView(mOverlay, wmlp);
+ latch.countDown();
+ });
+ try {
+ if (!latch.await(TIMEOUT_OVERLAY_SEC, TimeUnit.SECONDS)) {
+ throw new RemoteException("Timed out waiting for the overlay");
+ }
+ } catch (InterruptedException e) {
+ throw new RemoteException("Error while adding overlay: " + e.toString());
+ }
+ Log.d(TAG, "Overlay displayed, myUid=" + Process.myUid());
+ }
+
+ @Override
+ public void finishHost() {
+ removeOverlays();
+ stopSelf();
+ }
+ };
+}
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
index 53a181a..6d0239b 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
@@ -17,5 +17,5 @@
package com.android.frameworks.coretests.aidl;
interface ICmdCallback {
- void onActivityLaunched(IBinder receiver);
+ void onLaunched(IBinder receiver);
}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
index c406570..cce8e28 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
@@ -18,5 +18,6 @@
interface ICmdReceiver {
void doSomeWork(int durationMs);
+ void showApplicationOverlay();
void finishHost();
}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index 7350db7..704b780 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -205,4 +205,17 @@
ws.add(ws2);
assertEquals(2, workChains.size());
}
+
+ public void testSet_noWorkChains() {
+ WorkSource ws = new WorkSource();
+ ws.set(10);
+ assertEquals(1, ws.size());
+ assertEquals(10, ws.get(0));
+
+ WorkSource ws2 = new WorkSource();
+ ws2.set(20, "foo");
+ assertEquals(1, ws2.size());
+ assertEquals(20, ws2.get(0));
+ assertEquals("foo", ws2.getName(0));
+ }
}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 78d7785..eef9866 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -173,6 +173,7 @@
Settings.Global.DEVICE_DEMO_MODE,
Settings.Global.DEVICE_IDLE_CONSTANTS,
Settings.Global.BATTERY_SAVER_CONSTANTS,
+ Settings.Global.BATTERY_TIP_CONSTANTS,
Settings.Global.DEFAULT_SM_DP_PLUS,
Settings.Global.DEVICE_NAME,
Settings.Global.DEVICE_POLICY_CONSTANTS,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
index 6ff0ab2..b5a7bec 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -960,7 +960,7 @@
}
@Test
- public void testReadKernelUiidCpuFreqTimesLocked_invalidUid() {
+ public void testReadKernelUidCpuFreqTimesLocked_invalidUid() {
// PRECONDITIONS
updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
new file mode 100644
index 0000000..3794b5f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static android.os.BatteryStats.STATS_SINCE_CHARGED;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.os.BatteryStats;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.Display;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsImplTest {
+ @Mock
+ private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+ @Mock
+ private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
+
+ private MockBatteryStatsImpl mBatteryStatsImpl;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
+ when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
+ mBatteryStatsImpl = new MockBatteryStatsImpl()
+ .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
+ .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
+ }
+
+ @Test
+ public void testUpdateProcStateCpuTimes() {
+ mBatteryStatsImpl.setOnBatteryInternal(true);
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+
+ final int[] testUids = {10032, 10048, 10145, 10139};
+ final int[] testProcStates = {
+ PROCESS_STATE_BACKGROUND,
+ PROCESS_STATE_FOREGROUND_SERVICE,
+ PROCESS_STATE_TOP,
+ PROCESS_STATE_CACHED
+ };
+ addPendingUids(testUids, testProcStates);
+ final long[][] cpuTimes = {
+ {349734983, 394982394832l, 909834, 348934, 9838},
+ {7498, 1239890, 988, 13298, 98980},
+ {989834, 384098, 98483, 23809, 4984},
+ {4859048, 348903, 4578967, 5973894, 298549}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(cpuTimes[i]);
+
+ // Verify there are no cpu times initially.
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ assertArrayEquals("Uid=" + testUids[i], cpuTimes[i],
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+
+ final long[][] delta1 = {
+ {9589, 148934, 309894, 3098493, 98754},
+ {21983, 94983, 4983, 9878493, 84854},
+ {945894, 9089432, 19478, 3834, 7845},
+ {843895, 43948, 949582, 99, 384}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(delta1[i]);
+ }
+ addPendingUids(testUids, testProcStates);
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ long[] expectedCpuTimes = cpuTimes[i].clone();
+ for (int j = 0; j < expectedCpuTimes.length; ++j) {
+ expectedCpuTimes[j] += delta1[i][j];
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ final long[][] delta2 = {
+ {95932, 2943, 49834, 89034, 139},
+ {349, 89605, 5896, 845, 98444},
+ {678, 7498, 9843, 889, 4894},
+ {488, 998, 8498, 394, 574}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(delta2[i]);
+ }
+ addPendingUids(testUids, testProcStates);
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ long[] expectedCpuTimes = cpuTimes[i].clone();
+ for (int j = 0; j < expectedCpuTimes.length; ++j) {
+ expectedCpuTimes[j] += delta1[i][j] + delta2[i][j];
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertArrayEquals("Uid=" + testUids[i], delta2[i],
+ u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+ }
+
+ final long[][] delta3 = {
+ {98545, 95768795, 76586, 548945, 57846},
+ {788876, 586, 578459, 8776984, 9578923},
+ {3049509483598l, 4597834, 377654, 94589035, 7854},
+ {9493, 784, 99895, 8974893, 9879843}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(
+ delta3[i].clone());
+ }
+ addPendingUids(testUids, testProcStates);
+ final int parentUid = testUids[1];
+ final int childUid = 99099;
+ addIsolatedUid(parentUid, childUid);
+ final long[] isolatedUidCpuTimes = {495784, 398473, 4895, 4905, 30984093};
+ when(mKernelSingleUidTimeReader.readDeltaMs(childUid)).thenReturn(isolatedUidCpuTimes);
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ long[] expectedCpuTimes = cpuTimes[i].clone();
+ for (int j = 0; j < expectedCpuTimes.length; ++j) {
+ expectedCpuTimes[j] += delta1[i][j] + delta2[i][j] + delta3[i][j]
+ + (testUids[i] == parentUid ? isolatedUidCpuTimes[j] : 0);
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ long[] expectedScreenOffTimes = delta2[i].clone();
+ for (int j = 0; j < expectedScreenOffTimes.length; ++j) {
+ expectedScreenOffTimes[j] += delta3[i][j]
+ + (testUids[i] == parentUid ? isolatedUidCpuTimes[j] : 0);
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedScreenOffTimes,
+ u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testCopyFromAllUidsCpuTimes() {
+ mBatteryStatsImpl.setOnBatteryInternal(true);
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+
+ final int[] testUids = {10032, 10048, 10145, 10139};
+ final int[] testProcStates = {
+ PROCESS_STATE_BACKGROUND,
+ PROCESS_STATE_FOREGROUND_SERVICE,
+ PROCESS_STATE_TOP,
+ PROCESS_STATE_CACHED
+ };
+ final int[] pendingUidIdx = {1, 2};
+ updateProcessStates(testUids, testProcStates, pendingUidIdx);
+
+ final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
+ long[][] allCpuTimes = {
+ {938483, 4985984, 439893},
+ {499, 94904, 27694},
+ {302949085, 39789473, 34792839},
+ {9809485, 9083475, 347889834},
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ allUidCpuTimes.put(testUids[i], allCpuTimes[i]);
+ }
+ when(mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs()).thenReturn(allUidCpuTimes);
+ long[][] expectedCpuTimes = {
+ {843598745, 397843, 32749, 99854},
+ {9834, 5885, 487589, 394},
+ {203984, 439, 9859, 30948},
+ {9389, 858, 239, 349}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ final int idx = i;
+ final ArgumentMatcher<long[]> matcher = times -> Arrays.equals(times, allCpuTimes[idx]);
+ when(mKernelSingleUidTimeReader.computeDelta(eq(testUids[i]), argThat(matcher)))
+ .thenReturn(expectedCpuTimes[i]);
+ }
+
+ mBatteryStatsImpl.copyFromAllUidsCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes[i],
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+ }
+
+ @Test
+ public void testAddCpuTimes() {
+ long[] timesA = null;
+ long[] timesB = null;
+ assertNull(mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+ timesA = new long[] {34, 23, 45, 24};
+ assertArrayEquals(timesA, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+ timesB = timesA;
+ timesA = null;
+ assertArrayEquals(timesB, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+ final long[] expected = {434, 6784, 34987, 9984};
+ timesA = new long[timesB.length];
+ for (int i = 0; i < timesA.length; ++i) {
+ timesA[i] = expected[i] - timesB[i];
+ }
+ assertArrayEquals(expected, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+ }
+
+ private void addIsolatedUid(int parentUid, int childUid) {
+ final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid);
+ u.addIsolatedUid(childUid);
+ }
+
+ private void addPendingUids(int[] uids, int[] procStates) {
+ final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+ for (int i = 0; i < uids.length; ++i) {
+ pendingUids.put(uids[i], procStates[i]);
+ }
+ }
+
+ private void updateProcessStates(int[] uids, int[] procStates,
+ int[] pendingUidsIdx) {
+ final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+ for (int i = 0; i < uids.length; ++i) {
+ final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(uids[i]);
+ if (ArrayUtils.contains(pendingUidsIdx, i)) {
+ u.setProcessStateForTest(PROCESS_STATE_TOP);
+ pendingUids.put(uids[i], procStates[i]);
+ } else {
+ u.setProcessStateForTest(procStates[i]);
+ }
+ }
+ }
+
+ private void verifyNoPendingUids() {
+ final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+ assertEquals("There shouldn't be any pending uids left: " + pendingUids,
+ 0, pendingUids.size());
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 4e83221..0afec34 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -170,7 +170,6 @@
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BACKUP)
- + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_HEAVY_WEIGHT)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_SERVICE)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 12f5b70..e8f2456 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -26,6 +26,7 @@
BatteryStatsDualTimerTest.class,
BatteryStatsDurationTimerTest.class,
BatteryStatsHelperTest.class,
+ BatteryStatsImplTest.class,
BatteryStatsNoteTest.class,
BatteryStatsSamplingTimerTest.class,
BatteryStatsSensorTest.class,
@@ -36,6 +37,7 @@
BatteryStatsUidTest.class,
BatteryStatsUserLifecycleTests.class,
KernelMemoryBandwidthStatsTest.class,
+ KernelSingleUidTimeReaderTest.class,
KernelUidCpuFreqTimeReaderTest.class,
KernelWakelockReaderTest.class,
LongSamplingCounterArrayTest.class
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index 4b197e4..e54fe7d 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -15,19 +15,25 @@
*/
package com.android.internal.os;
-import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.os.BatteryStats.UID_TIMES_TYPE_ALL;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
+import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;
-import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
import com.android.frameworks.coretests.aidl.ICmdCallback;
import com.android.frameworks.coretests.aidl.ICmdReceiver;
+import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
@@ -36,6 +42,7 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.BatteryManager;
+import android.os.BatteryStats;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
@@ -45,9 +52,9 @@
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
+import android.util.DebugUtils;
import android.util.Log;
-import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,22 +68,26 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
public class BstatsCpuTimesValidationTest {
- private static final String TAG = BstatsCpuTimesValidationTest.class.getName();
+ private static final String TAG = BstatsCpuTimesValidationTest.class.getSimpleName();
private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
private static final String TEST_ACTIVITY = TEST_PKG + ".TestActivity";
+ private static final String TEST_SERVICE = TEST_PKG + ".TestService";
private static final String ISOLATED_TEST_SERVICE = TEST_PKG + ".IsolatedTestService";
private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+ private static final int FLAG_START_FOREGROUND = 1;
private static final int BATTERY_STATE_TIMEOUT_MS = 2000;
private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 200;
private static final int START_ACTIVITY_TIMEOUT_MS = 2000;
+ private static final int START_FG_SERVICE_TIMEOUT_MS = 2000;
+ private static final int START_SERVICE_TIMEOUT_MS = 2000;
private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000;
- private static final int GENERAL_TIMEOUT_MS = 1000;
- private static final int GENERAL_INTERVAL_MS = 100;
+ private static final int GENERAL_TIMEOUT_MS = 4000;
+ private static final int GENERAL_INTERVAL_MS = 200;
private static final int WORK_DURATION_MS = 2000;
@@ -84,6 +95,7 @@
private static UiDevice sUiDevice;
private static int sTestPkgUid;
private static boolean sCpuFreqTimesAvailable;
+ private static boolean sPerProcStateTimesAvailable;
@BeforeClass
public static void setupOnce() throws Exception {
@@ -92,14 +104,20 @@
sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
- sCpuFreqTimesAvailable = cpuFreqTimesAvailable();
+ checkCpuTimesAvailability();
}
// Checks cpu freq times of system uid as an indication of whether /proc/uid_time_in_state
- // kernel node is available.
- private static boolean cpuFreqTimesAvailable() throws Exception {
- final long[] cpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
- return cpuTimes != null;
+ // and /proc/uid/<uid>/time_in_state kernel nodes are available.
+ private static void checkCpuTimesAvailability() throws Exception {
+ batteryOn();
+ SystemClock.sleep(GENERAL_TIMEOUT_MS);
+ batteryOff();
+ final long[] totalCpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
+ sCpuFreqTimesAvailable = totalCpuTimes != null;
+ final long[] fgSvcCpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE);
+ sPerProcStateTimesAvailable = fgSvcCpuTimes != null;
}
@Test
@@ -112,7 +130,8 @@
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
- assertNull("Initial snapshot should be null", initialSnapshot);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
doSomeWork();
forceStop();
@@ -136,7 +155,8 @@
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
- assertNull("Initial snapshot should be null", initialSnapshot);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
doSomeWork();
forceStop();
@@ -166,7 +186,8 @@
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
- assertNull("Initial snapshot should be null", initialSnapshot);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
doSomeWorkInIsolatedProcess();
forceStop();
@@ -180,6 +201,224 @@
batteryOffScreenOn();
}
+ @Test
+ public void testCpuFreqTimes_stateTop() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+ doSomeWork(PROCESS_STATE_TOP);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testIsolatedCpuFreqTimes_stateService() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+ final ICmdReceiver activityReceiver = ICmdReceiver.Stub.asInterface(startActivity());
+ final ICmdReceiver isolatedReceiver = ICmdReceiver.Stub.asInterface(startIsolatedService());
+ try {
+ assertProcState(PROCESS_STATE_TOP);
+ isolatedReceiver.doSomeWork(WORK_DURATION_MS);
+ } finally {
+ activityReceiver.finishHost();
+ isolatedReceiver.finishHost();
+ }
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateTopSleeping() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOff();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP_SLEEPING));
+
+ doSomeWork(PROCESS_STATE_TOP_SLEEPING);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP_SLEEPING);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = cpuTimesMs.length / 2; i < cpuTimesMs.length; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateFgService() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOff();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND_SERVICE));
+
+ doSomeWork(PROCESS_STATE_FOREGROUND_SERVICE);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND_SERVICE);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateFg() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND));
+
+ doSomeWork(PROCESS_STATE_FOREGROUND);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOff();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateBg() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOff();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_BACKGROUND));
+
+ doSomeWork(PROCESS_STATE_BACKGROUND);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_BACKGROUND);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateCached() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_CACHED));
+
+ doSomeWork(PROCESS_STATE_CACHED);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_CACHED);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
private void assertCpuTimesValid(long[] cpuTimes) {
assertNotNull(cpuTimes);
for (int i = 0; i < cpuTimes.length; ++i) {
@@ -219,6 +458,66 @@
receiver.finishHost();
}
+ private void doSomeWork(int procState) throws Exception {
+ final ICmdReceiver receiver;
+ switch (procState) {
+ case PROCESS_STATE_TOP:
+ receiver = ICmdReceiver.Stub.asInterface(startActivity());
+ break;
+ case PROCESS_STATE_TOP_SLEEPING:
+ receiver = ICmdReceiver.Stub.asInterface(startActivity());
+ break;
+ case PROCESS_STATE_FOREGROUND_SERVICE:
+ receiver = ICmdReceiver.Stub.asInterface(startForegroundService());
+ break;
+ case PROCESS_STATE_FOREGROUND:
+ receiver = ICmdReceiver.Stub.asInterface(startService());
+ receiver.showApplicationOverlay();
+ break;
+ case PROCESS_STATE_BACKGROUND:
+ receiver = ICmdReceiver.Stub.asInterface(startService());
+ break;
+ case PROCESS_STATE_CACHED:
+ receiver = ICmdReceiver.Stub.asInterface(startActivity());
+ receiver.finishHost();
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown state: " + procState);
+ }
+ try {
+ assertProcState(procState);
+ receiver.doSomeWork(WORK_DURATION_MS);
+ } finally {
+ receiver.finishHost();
+ }
+ }
+
+ private void assertProcState(String state) throws Exception {
+ final String expectedState = "(" + state + ")";
+ assertDelayedCondition("", () -> {
+ final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+ final String actualState = uidStateStr.split(" ")[1];
+ return expectedState.equals(actualState) ? null
+ : "expected=" + expectedState + ", actual" + actualState;
+ });
+ }
+
+ private void assertProcState(int expectedState) throws Exception {
+ assertDelayedCondition("Unexpected proc state", () -> {
+ final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+ final int amProcState = Integer.parseInt(uidStateStr.split(" ")[0]);
+ final int actualState = BatteryStats.mapToInternalProcessState(amProcState);
+ return (actualState == expectedState) ? null
+ : "expected=" + getStateName(BatteryStats.Uid.class, expectedState)
+ + ", actual=" + getStateName(BatteryStats.Uid.class, actualState)
+ + ", amState=" + getStateName(ActivityManager.class, amProcState);
+ });
+ }
+
+ private String getStateName(Class clazz, int procState) {
+ return DebugUtils.valueToString(clazz, "PROCESS_STATE_", procState);
+ }
+
private IBinder startIsolatedService() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final IBinder[] binders = new IBinder[1];
@@ -248,6 +547,59 @@
return null;
}
+ private IBinder startForegroundService() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Intent launchIntent = new Intent()
+ .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE))
+ .setFlags(FLAG_START_FOREGROUND);
+ final Bundle extras = new Bundle();
+ final IBinder[] binders = new IBinder[1];
+ extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+ @Override
+ public void onLaunched(IBinder receiver) {
+ binders[0] = receiver;
+ latch.countDown();
+ }
+ });
+ launchIntent.putExtras(extras);
+ sContext.startForegroundService(launchIntent);
+ if (latch.await(START_FG_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (binders[0] == null) {
+ fail("Receiver binder should not be null");
+ }
+ return binders[0];
+ } else {
+ fail("Timed out waiting for the test fg service to start; testUid=" + sTestPkgUid);
+ }
+ return null;
+ }
+
+ private IBinder startService() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Intent launchIntent = new Intent()
+ .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE));
+ final Bundle extras = new Bundle();
+ final IBinder[] binders = new IBinder[1];
+ extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+ @Override
+ public void onLaunched(IBinder receiver) {
+ binders[0] = receiver;
+ latch.countDown();
+ }
+ });
+ launchIntent.putExtras(extras);
+ sContext.startService(launchIntent);
+ if (latch.await(START_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (binders[0] == null) {
+ fail("Receiver binder should not be null");
+ }
+ return binders[0];
+ } else {
+ fail("Timed out waiting for the test service to start; testUid=" + sTestPkgUid);
+ }
+ return null;
+ }
+
private IBinder startActivity() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final Intent launchIntent = new Intent()
@@ -256,7 +608,7 @@
final IBinder[] binders = new IBinder[1];
extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
@Override
- public void onActivityLaunched(IBinder receiver) {
+ public void onLaunched(IBinder receiver) {
binders[0] = receiver;
latch.countDown();
}
@@ -274,21 +626,63 @@
return null;
}
+ private static String getAllCpuTimesMsg() throws Exception {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("uid=" + sTestPkgUid + ";");
+ sb.append(UID_TIMES_TYPE_ALL + "=" + getMsgCpuTimesSum(getAllCpuFreqTimes(sTestPkgUid)));
+ for (int i = 0; i < NUM_PROCESS_STATE; ++i) {
+ sb.append("|");
+ sb.append(UID_PROCESS_TYPES[i] + "="
+ + getMsgCpuTimesSum(getAllCpuFreqTimes(sTestPkgUid, i)));
+ }
+ return sb.toString();
+ }
+
+ private static String getMsgCpuTimesSum(long[] cpuTimes) throws Exception {
+ if (cpuTimes == null) {
+ return "(0,0)";
+ }
+ long totalTime = 0;
+ for (int i = 0; i < cpuTimes.length / 2; ++i) {
+ totalTime += cpuTimes[i];
+ }
+ long screenOffTime = 0;
+ for (int i = cpuTimes.length / 2; i < cpuTimes.length; ++i) {
+ screenOffTime += cpuTimes[i];
+ }
+ return "(" + totalTime + "," + screenOffTime + ")";
+ }
+
private static long[] getAllCpuFreqTimes(int uid) throws Exception {
final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
- final Pattern pattern = Pattern.compile(uid + ",l,ctf,A,(.*?)\n");
+ final Pattern pattern = Pattern.compile(uid + ",l,ctf," + UID_TIMES_TYPE_ALL + ",(.*?)\n");
final Matcher matcher = pattern.matcher(checkinDump);
if (!matcher.find()) {
return null;
}
- final String[] uidTimesStr = matcher.group(1).split(",");
- final int freqCount = Integer.parseInt(uidTimesStr[0]);
- if (uidTimesStr.length != (2 * freqCount + 1)) {
- fail("Malformed data: " + Arrays.toString(uidTimesStr));
+ return parseCpuTimesStr(matcher.group(1));
+ }
+
+ private static long[] getAllCpuFreqTimes(int uid, int procState) throws Exception {
+ final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
+ final Pattern pattern = Pattern.compile(
+ uid + ",l,ctf," + UID_PROCESS_TYPES[procState] + ",(.*?)\n");
+ final Matcher matcher = pattern.matcher(checkinDump);
+ if (!matcher.find()) {
+ return null;
+ }
+ return parseCpuTimesStr(matcher.group(1));
+ }
+
+ private static long[] parseCpuTimesStr(String str) {
+ final String[] cpuTimesStr = str.split(",");
+ final int freqCount = Integer.parseInt(cpuTimesStr[0]);
+ if (cpuTimesStr.length != (2 * freqCount + 1)) {
+ fail("Malformed data: " + Arrays.toString(cpuTimesStr));
}
final long[] cpuTimes = new long[freqCount * 2];
for (int i = 0; i < cpuTimes.length; ++i) {
- cpuTimes[i] = Long.parseLong(uidTimesStr[i + 1]);
+ cpuTimes[i] = Long.parseLong(cpuTimesStr[i + 1]);
}
return cpuTimes;
}
@@ -312,12 +706,12 @@
screenOn();
}
- private void batteryOn() throws Exception {
+ private static void batteryOn() throws Exception {
executeCmd("dumpsys battery unplug");
assertBatteryState(false);
}
- private void batteryOff() throws Exception {
+ private static void batteryOff() throws Exception {
executeCmd("dumpsys battery reset");
assertBatteryState(true);
}
@@ -336,43 +730,41 @@
private void forceStop() throws Exception {
executeCmd("cmd activity force-stop " + TEST_PKG);
- assertUidState(PROCESS_STATE_NONEXISTENT);
+ assertProcState("NONEXISTENT");
}
- private void assertUidState(int state) throws Exception {
- final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
- final int uidState = Integer.parseInt(uidStateStr.split(" ")[0]);
- assertEquals(state, uidState);
- }
-
- private void assertKeyguardUnLocked() {
+ private void assertKeyguardUnLocked() throws Exception {
final KeyguardManager keyguardManager =
(KeyguardManager) sContext.getSystemService(Context.KEYGUARD_SERVICE);
- assertDelayedCondition("Keyguard should be unlocked",
- () -> !keyguardManager.isKeyguardLocked());
+ assertDelayedCondition("Unexpected Keyguard state", () ->
+ keyguardManager.isKeyguardLocked() ? "expected=unlocked" : null
+ );
}
- private void assertScreenInteractive(boolean interactive) {
+ private void assertScreenInteractive(boolean interactive) throws Exception {
final PowerManager powerManager =
(PowerManager) sContext.getSystemService(Context.POWER_SERVICE);
- assertDelayedCondition("Unexpected screen interactive state",
- () -> interactive == powerManager.isInteractive());
+ assertDelayedCondition("Unexpected screen interactive state", () ->
+ interactive == powerManager.isInteractive() ? null : "expected=" + interactive
+ );
}
- private void assertDelayedCondition(String errorMsg, ExpectedCondition condition) {
+ private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition)
+ throws Exception {
final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS;
while (SystemClock.uptimeMillis() <= endTime) {
- if (condition.isTrue()) {
+ if (condition.getErrIfNotTrue() == null) {
return;
}
SystemClock.sleep(GENERAL_INTERVAL_MS);
}
- if (!condition.isTrue()) {
- fail(errorMsg);
+ final String errMsg = condition.getErrIfNotTrue();
+ if (errMsg != null) {
+ fail(errMsgPrefix + ": " + errMsg);
}
}
- private void assertBatteryState(boolean pluggedIn) throws Exception {
+ private static void assertBatteryState(boolean pluggedIn) throws Exception {
final long endTime = SystemClock.uptimeMillis() + BATTERY_STATE_TIMEOUT_MS;
while (isDevicePluggedIn() != pluggedIn && SystemClock.uptimeMillis() <= endTime) {
Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
@@ -383,13 +775,13 @@
}
}
- private boolean isDevicePluggedIn() {
+ private static boolean isDevicePluggedIn() {
final Intent batteryIntent = sContext.registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
}
- private String executeCmd(String cmd) throws Exception {
+ private static String executeCmd(String cmd) throws Exception {
final String result = sUiDevice.executeShellCommand(cmd).trim();
Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
return result;
@@ -400,6 +792,6 @@
}
private interface ExpectedCondition {
- boolean isTrue();
+ String getErrIfNotTrue() throws Exception;
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
new file mode 100644
index 0000000..5d72942
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static org.junit.Assert.assertArrayEquals;
+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 android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import com.android.internal.os.KernelSingleUidTimeReader.Injector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelSingleUidTimeReaderTest {
+ private final static int TEST_UID = 2222;
+ private final static int TEST_FREQ_COUNT = 5;
+
+ private KernelSingleUidTimeReader mReader;
+ private TestInjector mInjector;
+
+ @Before
+ public void setUp() {
+ mInjector = new TestInjector();
+ mReader = new KernelSingleUidTimeReader(TEST_FREQ_COUNT, mInjector);
+ }
+
+ @Test
+ public void readDelta() {
+ final SparseArray<long[]> allLastCpuTimes = mReader.getLastUidCpuTimeMs();
+ long[] latestCpuTimes = new long[] {120, 130, 140, 150, 160};
+ mInjector.setData(latestCpuTimes);
+ long[] deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(latestCpuTimes, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ long[] expectedDeltaTimes = new long[] {200, 340, 1230, 490, 4890};
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ latestCpuTimes[i] += expectedDeltaTimes[i];
+ }
+ mInjector.setData(latestCpuTimes);
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(expectedDeltaTimes, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // delta should be null if cpu times haven't changed
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed data (-ve)
+ long[] malformedLatestTimes = new long[latestCpuTimes.length];
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = -4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ mInjector.setData(malformedLatestTimes);
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed data (decreased)
+ malformedLatestTimes = new long[latestCpuTimes.length];
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = latestCpuTimes[i] - 4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ mInjector.setData(malformedLatestTimes);
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+ }
+
+ @Test
+ public void readDelta_fileNotAvailable() {
+ mInjector.letReadDataThrowException(true);
+
+ for (int i = 0; i < KernelSingleUidTimeReader.TOTAL_READ_ERROR_COUNT; ++i) {
+ assertTrue(mReader.singleUidCpuTimesAvailable());
+ mReader.readDeltaMs(TEST_UID);
+ }
+ assertFalse(mReader.singleUidCpuTimesAvailable());
+ }
+
+ @Test
+ public void testComputeDelta() {
+ // proc file not available
+ mReader.setSingleUidCpuTimesAvailable(false);
+ long[] latestCpuTimes = new long[] {12, 13, 14, 15, 16};
+ long[] deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+
+ // cpu times have changed
+ mReader.setSingleUidCpuTimesAvailable(true);
+ SparseArray<long[]> allLastCpuTimes = mReader.getLastUidCpuTimeMs();
+ long[] lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ allLastCpuTimes.put(TEST_UID, lastCpuTimes);
+ long[] expectedDeltaTimes = new long[] {123, 324, 43, 989, 80};
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ latestCpuTimes[i] = lastCpuTimes[i] + expectedDeltaTimes[i];
+ }
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+ assertCpuTimesEqual(expectedDeltaTimes, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // no change in cpu times
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed cpu times (-ve)
+ long[] malformedLatestTimes = new long[latestCpuTimes.length];
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = -4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, malformedLatestTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed cpu times (decreased)
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = latestCpuTimes[i] - 4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, malformedLatestTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+ }
+
+ @Test
+ public void testGetDelta() {
+ // No last cpu times
+ long[] lastCpuTimes = null;
+ long[] latestCpuTimes = new long[] {12, 13, 14, 15, 16};
+ long[] deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, deltaCpuTimes);
+
+ // Latest cpu times are -ve
+ lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ latestCpuTimes = new long[] {15, -10, 19, 21, 23};
+ deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+
+ // Latest cpu times are less than last cpu times
+ lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ latestCpuTimes = new long[] {15, 11, 21, 34, 171};
+ deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+
+ lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ latestCpuTimes = new long[] {112, 213, 314, 415, 516};
+ deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(new long[] {100, 200, 300, 400, 500}, deltaCpuTimes);
+ }
+
+ @Test
+ public void testRemoveUid() {
+ final SparseArray<long[]> lastUidCpuTimes = mReader.getLastUidCpuTimeMs();
+ lastUidCpuTimes.put(12, new long[] {});
+ lastUidCpuTimes.put(16, new long[] {});
+
+ mReader.removeUid(12);
+ assertFalse("Removal failed, cpuTimes=" + lastUidCpuTimes,
+ lastUidCpuTimes.indexOfKey(12) >= 0);
+ mReader.removeUid(16);
+ assertFalse("Removal failed, cpuTimes=" + lastUidCpuTimes,
+ lastUidCpuTimes.indexOfKey(16) >= 0);
+ }
+
+ @Test
+ public void testRemoveUidsRange() {
+ final SparseArray<long[]> lastUidCpuTimes = mReader.getLastUidCpuTimeMs();
+ final int startUid = 12;
+ final int endUid = 24;
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid, endUid);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid - 1, endUid);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid, endUid + 1);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid - 1, endUid + 1);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+ }
+
+ private void assertCpuTimesEqual(long[] expected, long[] actual) {
+ assertArrayEquals("Expected=" + Arrays.toString(expected)
+ + ", Actual=" + Arrays.toString(actual), expected, actual);
+ }
+
+ class TestInjector extends Injector {
+ private byte[] mData;
+ private boolean mThrowExcpetion;
+
+ @Override
+ public byte[] readData(String procFile) throws IOException {
+ if (mThrowExcpetion) {
+ throw new IOException("In the test");
+ } else {
+ return mData;
+ }
+ }
+
+ public void setData(long[] cpuTimes) {
+ final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES);
+ buffer.order(ByteOrder.nativeOrder());
+ for (long time : cpuTimes) {
+ buffer.putLong(time / 10);
+ }
+ mData = buffer.array();
+ }
+
+ public void letReadDataThrowException(boolean throwException) {
+ mThrowExcpetion = throwException;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 63d1e5a..de2fd12 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,7 +16,10 @@
package com.android.internal.os;
+import android.util.SparseIntArray;
+
import java.util.ArrayList;
+import java.util.concurrent.Future;
/**
* Mocks a BatteryStatsImpl object.
@@ -33,6 +36,7 @@
mScreenDozeTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
mOnBatteryTimeBase);
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
+ setExternalStatsSyncLocked(new DummyExternalStatsSync());
}
MockBatteryStatsImpl() {
@@ -78,6 +82,11 @@
return this;
}
+ public MockBatteryStatsImpl setKernelSingleUidTimeReader(KernelSingleUidTimeReader reader) {
+ mKernelSingleUidTimeReader = reader;
+ return this;
+ }
+
public MockBatteryStatsImpl setKernelCpuSpeedReaders(KernelCpuSpeedReader[] readers) {
mKernelCpuSpeedReaders = readers;
return this;
@@ -102,5 +111,32 @@
mOnBatteryInternal = onBatteryInternal;
return this;
}
+
+ public SparseIntArray getPendingUids() {
+ return mPendingUids;
+ }
+
+ private class DummyExternalStatsSync implements ExternalStatsSync {
+ @Override
+ public Future<?> scheduleSync(String reason, int flags) {
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleReadProcStateCpuTimes() {
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+ return null;
+ }
+
+ }
}
diff --git a/core/tests/packagemanagertests/Android.mk b/core/tests/packagemanagertests/Android.mk
index c1e8c98..5bfde78 100644
--- a/core/tests/packagemanagertests/Android.mk
+++ b/core/tests/packagemanagertests/Android.mk
@@ -10,7 +10,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
- frameworks-base-testutils
+ frameworks-base-testutils \
+ mockito-target-minus-junit4
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksCorePackageManagerTests
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 8e7147c..7bb2859 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -236,6 +236,7 @@
<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/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 7c60467..e3af655 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -551,18 +551,20 @@
}
// Animate spots that are fading out and being removed.
- for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ for (size_t i = 0; i < mLocked.spots.size();) {
Spot* spot = mLocked.spots.itemAt(i);
if (spot->id == Spot::INVALID_ID) {
spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
if (spot->alpha <= 0) {
- mLocked.spots.removeAt(i--);
+ mLocked.spots.removeAt(i);
releaseSpotLocked(spot);
+ continue;
} else {
spot->sprite->setAlpha(spot->alpha);
keepAnimating = true;
}
}
+ ++i;
}
return keepAnimating;
}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 19d467a..1a97b6b 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -16,9 +16,12 @@
package android.media;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.util.SparseIntArray;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.TreeSet;
/**
@@ -120,6 +123,57 @@
*/
public static final int TYPE_USB_HEADSET = 22;
+ /** @hide */
+ @IntDef(flag = false, prefix = "TYPE", value = {
+ TYPE_BUILTIN_EARPIECE,
+ TYPE_BUILTIN_SPEAKER,
+ TYPE_WIRED_HEADSET,
+ TYPE_WIRED_HEADPHONES,
+ TYPE_BLUETOOTH_SCO,
+ TYPE_BLUETOOTH_A2DP,
+ TYPE_HDMI,
+ TYPE_DOCK,
+ TYPE_USB_ACCESSORY,
+ TYPE_USB_DEVICE,
+ TYPE_USB_HEADSET,
+ TYPE_TELEPHONY,
+ TYPE_LINE_ANALOG,
+ TYPE_HDMI_ARC,
+ TYPE_LINE_DIGITAL,
+ TYPE_FM,
+ TYPE_AUX_LINE,
+ TYPE_IP }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AudioDeviceTypeOut {}
+
+ /** @hide */
+ /*package*/ static boolean isValidAudioDeviceTypeOut(int type) {
+ switch (type) {
+ case TYPE_BUILTIN_EARPIECE:
+ case TYPE_BUILTIN_SPEAKER:
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ case TYPE_BLUETOOTH_SCO:
+ case TYPE_BLUETOOTH_A2DP:
+ case TYPE_HDMI:
+ case TYPE_DOCK:
+ case TYPE_USB_ACCESSORY:
+ case TYPE_USB_DEVICE:
+ case TYPE_USB_HEADSET:
+ case TYPE_TELEPHONY:
+ case TYPE_LINE_ANALOG:
+ case TYPE_HDMI_ARC:
+ case TYPE_LINE_DIGITAL:
+ case TYPE_FM:
+ case TYPE_AUX_LINE:
+ case TYPE_IP:
+ return true;
+ default:
+ return false;
+ }
+ }
+
private final AudioDevicePort mPort;
AudioDeviceInfo(AudioDevicePort port) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 58976ca..f87c846 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -16,6 +16,7 @@
package android.media;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -52,6 +53,8 @@
import android.util.Slog;
import android.view.KeyEvent;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -911,13 +914,28 @@
/**
* Returns the minimum volume index for a particular stream.
- *
+ * @param streamType The stream type whose minimum volume index is returned. Must be one of
+ * {@link #STREAM_VOICE_CALL}, {@link #STREAM_SYSTEM},
+ * {@link #STREAM_RING}, {@link #STREAM_MUSIC}, {@link #STREAM_ALARM},
+ * {@link #STREAM_NOTIFICATION}, {@link #STREAM_DTMF} or {@link #STREAM_ACCESSIBILITY}.
+ * @return The minimum valid volume index for the stream.
+ * @see #getStreamVolume(int)
+ */
+ public int getStreamMinVolume(int streamType) {
+ if (!isPublicStreamType(streamType)) {
+ throw new IllegalArgumentException("Invalid stream type " + streamType);
+ }
+ return getStreamMinVolumeInt(streamType);
+ }
+
+ /**
+ * @hide
+ * Same as {@link #getStreamMinVolume(int)} but without the check on the public stream type.
* @param streamType The stream type whose minimum volume index is returned.
* @return The minimum valid volume index for the stream.
* @see #getStreamVolume(int)
- * @hide
*/
- public int getStreamMinVolume(int streamType) {
+ public int getStreamMinVolumeInt(int streamType) {
final IAudioService service = getService();
try {
return service.getStreamMinVolume(streamType);
@@ -943,6 +961,72 @@
}
}
+ // keep in sync with frameworks/av/services/audiopolicy/common/include/Volume.h
+ private static final float VOLUME_MIN_DB = -758.0f;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = "STREAM", value = {
+ STREAM_VOICE_CALL,
+ STREAM_SYSTEM,
+ STREAM_RING,
+ STREAM_MUSIC,
+ STREAM_ALARM,
+ STREAM_NOTIFICATION,
+ STREAM_DTMF,
+ STREAM_ACCESSIBILITY }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PublicStreamTypes {}
+
+ /**
+ * Returns the volume in dB (decibel) for the given stream type at the given volume index, on
+ * the given type of audio output device.
+ * @param streamType stream type for which the volume is queried.
+ * @param index the volume index for which the volume is queried. The index value must be
+ * between the minimum and maximum index values for the given stream type (see
+ * {@link #getStreamMinVolume(int)} and {@link #getStreamMaxVolume(int)}).
+ * @param deviceType the type of audio output device for which volume is queried.
+ * @return a volume expressed in dB.
+ * A negative value indicates the audio signal is attenuated. A typical maximum value
+ * at the maximum volume index is 0 dB (no attenuation nor amplification). Muting is
+ * reflected by a value of {@link Float#NEGATIVE_INFINITY}.
+ */
+ public float getStreamVolumeDb(@PublicStreamTypes int streamType, int index,
+ @AudioDeviceInfo.AudioDeviceTypeOut int deviceType) {
+ if (!isPublicStreamType(streamType)) {
+ throw new IllegalArgumentException("Invalid stream type " + streamType);
+ }
+ if (index > getStreamMaxVolume(streamType) || index < getStreamMinVolume(streamType)) {
+ throw new IllegalArgumentException("Invalid stream volume index " + index);
+ }
+ if (!AudioDeviceInfo.isValidAudioDeviceTypeOut(deviceType)) {
+ throw new IllegalArgumentException("Invalid audio output device type " + deviceType);
+ }
+ final float gain = AudioSystem.getStreamVolumeDB(streamType, index,
+ AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType));
+ if (gain <= VOLUME_MIN_DB) {
+ return Float.NEGATIVE_INFINITY;
+ } else {
+ return gain;
+ }
+ }
+
+ private static boolean isPublicStreamType(int streamType) {
+ switch (streamType) {
+ case STREAM_VOICE_CALL:
+ case STREAM_SYSTEM:
+ case STREAM_RING:
+ case STREAM_MUSIC:
+ case STREAM_ALARM:
+ case STREAM_NOTIFICATION:
+ case STREAM_DTMF:
+ case STREAM_ACCESSIBILITY:
+ return true;
+ default:
+ return false;
+ }
+ }
+
/**
* Get last audible volume before stream was muted.
*
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
index c3a36e9..fce5dd9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
@@ -432,53 +432,9 @@
* the admin component will be set to {@code null} and userId to {@link UserHandle#USER_NULL}
*/
public static EnforcedAdmin checkIfMaximumTimeToLockIsSet(Context context) {
- final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- if (dpm == null) {
- return null;
- }
- EnforcedAdmin enforcedAdmin = null;
- final int userId = UserHandle.myUserId();
- final UserManager um = UserManager.get(context);
- final List<UserInfo> profiles = um.getProfiles(userId);
- final int profilesSize = profiles.size();
- // As we do not have a separate screen lock timeout settings for work challenge,
- // we need to combine all profiles maximum time to lock even work challenge is
- // enabled.
- for (int i = 0; i < profilesSize; i++) {
- final UserInfo userInfo = profiles.get(i);
- final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id);
- if (admins == null) {
- continue;
- }
- for (ComponentName admin : admins) {
- if (dpm.getMaximumTimeToLock(admin, userInfo.id) > 0) {
- if (enforcedAdmin == null) {
- enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
- } else {
- return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
- }
- // This same admins could have set policies both on the managed profile
- // and on the parent. So, if the admin has set the policy on the
- // managed profile here, we don't need to further check if that admin
- // has set policy on the parent admin.
- continue;
- }
- if (userInfo.isManagedProfile()) {
- // If userInfo.id is a managed profile, we also need to look at
- // the policies set on the parent.
- DevicePolicyManager parentDpm = sProxy.getParentProfileInstance(dpm, userInfo);
- if (parentDpm.getMaximumTimeToLock(admin, userInfo.id) > 0) {
- if (enforcedAdmin == null) {
- enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
- } else {
- return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
- }
- }
- }
- }
- }
- return enforcedAdmin;
+ return checkForLockSetting(context, UserHandle.myUserId(),
+ (DevicePolicyManager dpm, ComponentName admin, @UserIdInt int userId) ->
+ dpm.getMaximumTimeToLock(admin, userId) > 0);
}
private interface LockSettingCheck {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
new file mode 100644
index 0000000..3c3c70a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.applications;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Class for managing services matching a given intent and requesting a given permission.
+ */
+public class ServiceListing {
+ private final ContentResolver mContentResolver;
+ private final Context mContext;
+ private final String mTag;
+ private final String mSetting;
+ private final String mIntentAction;
+ private final String mPermission;
+ private final String mNoun;
+ private final HashSet<ComponentName> mEnabledServices = new HashSet<>();
+ private final List<ServiceInfo> mServices = new ArrayList<>();
+ private final List<Callback> mCallbacks = new ArrayList<>();
+
+ private boolean mListening;
+
+ private ServiceListing(Context context, String tag,
+ String setting, String intentAction, String permission, String noun) {
+ mContentResolver = context.getContentResolver();
+ mContext = context;
+ mTag = tag;
+ mSetting = setting;
+ mIntentAction = intentAction;
+ mPermission = permission;
+ mNoun = noun;
+ }
+
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
+ }
+
+ public void removeCallback(Callback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (mListening) {
+ // listen for package changes
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(mPackageReceiver, filter);
+ mContentResolver.registerContentObserver(Settings.Secure.getUriFor(mSetting),
+ false, mSettingsObserver);
+ } else {
+ mContext.unregisterReceiver(mPackageReceiver);
+ mContentResolver.unregisterContentObserver(mSettingsObserver);
+ }
+ }
+
+ private void saveEnabledServices() {
+ StringBuilder sb = null;
+ for (ComponentName cn : mEnabledServices) {
+ if (sb == null) {
+ sb = new StringBuilder();
+ } else {
+ sb.append(':');
+ }
+ sb.append(cn.flattenToString());
+ }
+ Settings.Secure.putString(mContentResolver, mSetting,
+ sb != null ? sb.toString() : "");
+ }
+
+ private void loadEnabledServices() {
+ mEnabledServices.clear();
+ final String flat = Settings.Secure.getString(mContentResolver, mSetting);
+ if (flat != null && !"".equals(flat)) {
+ final String[] names = flat.split(":");
+ for (String name : names) {
+ final ComponentName cn = ComponentName.unflattenFromString(name);
+ if (cn != null) {
+ mEnabledServices.add(cn);
+ }
+ }
+ }
+ }
+
+ public void reload() {
+ loadEnabledServices();
+ mServices.clear();
+ final int user = ActivityManager.getCurrentUser();
+
+ final PackageManagerWrapper pmWrapper =
+ new PackageManagerWrapper(mContext.getPackageManager());
+ List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
+ new Intent(mIntentAction),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+ user);
+
+ for (ResolveInfo resolveInfo : installedServices) {
+ ServiceInfo info = resolveInfo.serviceInfo;
+
+ if (!mPermission.equals(info.permission)) {
+ Slog.w(mTag, "Skipping " + mNoun + " service "
+ + info.packageName + "/" + info.name
+ + ": it does not require the permission "
+ + mPermission);
+ continue;
+ }
+ mServices.add(info);
+ }
+ for (Callback callback : mCallbacks) {
+ callback.onServicesReloaded(mServices);
+ }
+ }
+
+ public boolean isEnabled(ComponentName cn) {
+ return mEnabledServices.contains(cn);
+ }
+
+ public void setEnabled(ComponentName cn, boolean enabled) {
+ if (enabled) {
+ mEnabledServices.add(cn);
+ } else {
+ mEnabledServices.remove(cn);
+ }
+ saveEnabledServices();
+ }
+
+ private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ reload();
+ }
+ };
+
+ private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reload();
+ }
+ };
+
+ public interface Callback {
+ void onServicesReloaded(List<ServiceInfo> services);
+ }
+
+ public static class Builder {
+ private final Context mContext;
+ private String mTag;
+ private String mSetting;
+ private String mIntentAction;
+ private String mPermission;
+ private String mNoun;
+
+ public Builder(Context context) {
+ mContext = context;
+ }
+
+ public Builder setTag(String tag) {
+ mTag = tag;
+ return this;
+ }
+
+ public Builder setSetting(String setting) {
+ mSetting = setting;
+ return this;
+ }
+
+ public Builder setIntentAction(String intentAction) {
+ mIntentAction = intentAction;
+ return this;
+ }
+
+ public Builder setPermission(String permission) {
+ mPermission = permission;
+ return this;
+ }
+
+ public Builder setNoun(String noun) {
+ mNoun = noun;
+ return this;
+ }
+
+ public ServiceListing build() {
+ return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index 3f826cc..6025d68 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 @@
*/
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 @@
/**
* 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 @@
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 @@
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 @@
}
// 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 @@
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 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/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
new file mode 100644
index 0000000..fa31a7d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.provider.Settings;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.testutils.shadow.ShadowPackageManagerWrapper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+ shadows = {ShadowPackageManagerWrapper.class})
+public class ServiceListingTest {
+
+ private static final String TEST_SETTING = "testSetting";
+ private static final String TEST_INTENT = "com.example.intent";
+ private static final String TEST_PERMISSION = "testPermission";
+
+ private ServiceListing mServiceListing;
+
+ @Before
+ public void setUp() {
+ mServiceListing = new ServiceListing.Builder(RuntimeEnvironment.application)
+ .setTag("testTag")
+ .setSetting(TEST_SETTING)
+ .setNoun("testNoun")
+ .setIntentAction(TEST_INTENT)
+ .setPermission("testPermission")
+ .build();
+ }
+
+ @After
+ public void tearDown() {
+ ShadowPackageManagerWrapper.reset();
+ }
+
+ @Test
+ public void testCallback() {
+ ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
+ mServiceListing.addCallback(callback);
+ mServiceListing.reload();
+ verify(callback, times(1)).onServicesReloaded(anyList());
+ mServiceListing.removeCallback(callback);
+ mServiceListing.reload();
+ verify(callback, times(1)).onServicesReloaded(anyList());
+ }
+
+ @Test
+ public void testSaveLoad() {
+ ComponentName testComponent1 = new ComponentName("testPackage1", "testClass1");
+ ComponentName testComponent2 = new ComponentName("testPackage2", "testClass2");
+ Settings.Secure.putString(RuntimeEnvironment.application.getContentResolver(),
+ TEST_SETTING,
+ testComponent1.flattenToString() + ":" + testComponent2.flattenToString());
+
+ mServiceListing.reload();
+
+ assertThat(mServiceListing.isEnabled(testComponent1)).isTrue();
+ assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
+ assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+ TEST_SETTING)).contains(testComponent1.flattenToString());
+ assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+ TEST_SETTING)).contains(testComponent2.flattenToString());
+
+ mServiceListing.setEnabled(testComponent1, false);
+
+ assertThat(mServiceListing.isEnabled(testComponent1)).isFalse();
+ assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
+ assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+ TEST_SETTING)).doesNotContain(testComponent1.flattenToString());
+ assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+ TEST_SETTING)).contains(testComponent2.flattenToString());
+
+ mServiceListing.setEnabled(testComponent1, true);
+
+ assertThat(mServiceListing.isEnabled(testComponent1)).isTrue();
+ assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
+ assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+ TEST_SETTING)).contains(testComponent1.flattenToString());
+ assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
+ TEST_SETTING)).contains(testComponent2.flattenToString());
+ }
+}
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 0000000..226166b
--- /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/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowPackageManagerWrapper.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowPackageManagerWrapper.java
new file mode 100644
index 0000000..1fdca27
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowPackageManagerWrapper.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.testutils.shadow;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.util.ArrayMap;
+
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Shadow for {@link PackageManagerWrapper} to allow stubbing hidden methods.
+ */
+@Implements(PackageManagerWrapper.class)
+public class ShadowPackageManagerWrapper {
+ private static final Map<Intent, List<ResolveInfo>> intentServices = new ArrayMap<>();
+
+ @Implementation
+ public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int i, int user) {
+ List<ResolveInfo> list = intentServices.get(intent);
+ return list != null ? list : Collections.emptyList();
+ }
+
+ public static void addResolveInfoForIntent(Intent intent, ResolveInfo info) {
+ List<ResolveInfo> infoList = intentServices.computeIfAbsent(intent, k -> new ArrayList<>());
+ infoList.add(info);
+ }
+
+ public static void reset() {
+ intentServices.clear();
+ }
+}
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 43e88ba..3d09b74 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -29,14 +29,6 @@
android:gravity="center_vertical"
android:orientation="horizontal">
- <include
- android:id="@+id/date_time_alarm_group"
- layout="@layout/status_bar_alarm_group"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="8dp"
- android:layout_width="wrap_content"
- android:layout_height="match_parent" />
-
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -44,6 +36,18 @@
android:layout_marginEnd="8dp"
android:gravity="end">
+ <com.android.keyguard.CarrierText
+ android:id="@+id/qs_carrier_text"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center_vertical|start"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textDirection="locale"
+ android:singleLine="true" />
+
<com.android.systemui.statusbar.phone.MultiUserSwitch
android:id="@+id/multi_user_switch"
android:layout_width="48dp"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
index 2cf3e4a..739a255 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -28,32 +28,29 @@
android:orientation="horizontal">
- <com.android.keyguard.CarrierText
- android:id="@+id/qs_carrier_text"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:gravity="center_vertical|start"
- android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimary"
- android:textDirection="locale"
- android:singleLine="true" />
-
- <com.android.systemui.BatteryMeterView android:id="@+id/battery"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- />
-
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:singleLine="true"
- android:paddingStart="@dimen/status_bar_clock_starting_padding"
- android:paddingEnd="@dimen/status_bar_clock_end_padding"
+ android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+ android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
android:gravity="center_vertical|start"
systemui:showDark="false"
+ />
+
+ <android.widget.Space
+ android:id="@+id/space"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center_vertical|center_horizontal"
+ />
+
+ <com.android.systemui.BatteryMeterView android:id="@+id/battery"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:gravity="center_vertical|end"
/>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 6de27ac..17b38cb 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -54,6 +54,17 @@
android:layout_height="match_parent"
android:layout="@layout/operator_name" />
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:singleLine="true"
+ android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+ android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
+ android:gravity="center_vertical|start"
+ />
+
<!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and
PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). -->
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
@@ -70,17 +81,6 @@
>
<include layout="@layout/system_icons" />
-
- <com.android.systemui.statusbar.policy.Clock
- android:id="@+id/clock"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:singleLine="true"
- android:paddingStart="@dimen/status_bar_clock_starting_padding"
- android:paddingEnd="@dimen/status_bar_clock_end_padding"
- android:gravity="center_vertical|start"
- />
</com.android.keyguard.AlphaOptimizedLinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0715d49..60e9ebf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -48,6 +48,12 @@
<!-- The end padding for the clock in the status bar. -->
<dimen name="status_bar_clock_end_padding">0dp</dimen>
+ <!-- Starting padding for a left-aligned status bar clock -->
+ <dimen name="status_bar_left_clock_starting_padding">0dp</dimen>
+
+ <!-- End padding for left-aligned status bar clock -->
+ <dimen name="status_bar_left_clock_end_padding">7dp</dimen>
+
<!-- Spacing after the wifi signals that is present if there are any icons following it. -->
<dimen name="status_bar_wifi_signal_spacer_width">4dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 12f75bb..d3dded0 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -31,6 +31,7 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.text.InputType;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
@@ -78,6 +79,8 @@
*/
private static final float OVERSHOOT_TIME_POSITION = 0.5f;
+ private static char DOT = '\u2022';
+
/**
* The raw text size, will be multiplied by the scaled density when drawn
*/
@@ -208,7 +211,7 @@
public void append(char c) {
int visibleChars = mTextChars.size();
- String textbefore = mText;
+ CharSequence textbefore = getTransformedText();
mText = mText + c;
int newLength = mText.length();
CharState charState;
@@ -245,7 +248,7 @@
public void deleteLastChar() {
int length = mText.length();
- String textbefore = mText;
+ CharSequence textbefore = getTransformedText();
if (length > 0) {
mText = mText.substring(0, length - 1);
CharState charState = mTextChars.get(length - 1);
@@ -259,6 +262,21 @@
return mText;
}
+ private CharSequence getTransformedText() {
+ int textLength = mTextChars.size();
+ StringBuilder stringBuilder = new StringBuilder(textLength);
+ for (int i = 0; i < textLength; i++) {
+ CharState charState = mTextChars.get(i);
+ // If the dot is disappearing, the character is disappearing entirely. Consider
+ // it gone.
+ if (charState.dotAnimator != null && !charState.dotAnimationIsGrowing) {
+ continue;
+ }
+ stringBuilder.append(charState.isCharVisibleForA11y() ? charState.whichChar : DOT);
+ }
+ return stringBuilder;
+ }
+
private CharState obtainCharState(char c) {
CharState charState;
if(mCharPool.isEmpty()) {
@@ -272,7 +290,7 @@
}
public void reset(boolean animated, boolean announce) {
- String textbefore = mText;
+ CharSequence textbefore = getTransformedText();
mText = "";
int length = mTextChars.size();
int middleIndex = (length - 1) / 2;
@@ -305,7 +323,7 @@
}
}
- void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex,
+ void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex,
int removedCount, int addedCount) {
if (AccessibilityManager.getInstance(mContext).isEnabled() &&
(isFocused() || isSelected() && isShown())) {
@@ -315,6 +333,10 @@
event.setRemovedCount(removedCount);
event.setAddedCount(addedCount);
event.setBeforeText(beforeText);
+ CharSequence transformedText = getTransformedText();
+ if (!TextUtils.isEmpty(transformedText)) {
+ event.getText().add(transformedText);
+ }
event.setPassword(true);
sendAccessibilityEventUnchecked(event);
}
@@ -332,8 +354,9 @@
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(PasswordTextView.class.getName());
+ info.setClassName(EditText.class.getName());
info.setPassword(true);
+ info.setText(getTransformedText());
info.setEditable(true);
@@ -420,7 +443,19 @@
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
+ boolean textVisibleBefore = isCharVisibleForA11y();
+ float beforeTextSizeFactor = currentTextSizeFactor;
currentTextSizeFactor = (float) animation.getAnimatedValue();
+ if (textVisibleBefore != isCharVisibleForA11y()) {
+ currentTextSizeFactor = beforeTextSizeFactor;
+ CharSequence beforeText = getTransformedText();
+ currentTextSizeFactor = (float) animation.getAnimatedValue();
+ int indexOfThisChar = mTextChars.indexOf(CharState.this);
+ if (indexOfThisChar >= 0) {
+ sendAccessibilityEventTypeViewTextChanged(
+ beforeText, indexOfThisChar, 1, 1);
+ }
+ }
invalidate();
}
};
@@ -673,5 +708,13 @@
}
return charWidth + mCharPadding * currentWidthFactor;
}
+
+ public boolean isCharVisibleForA11y() {
+ // The text has size 0 when it is first added, but we want to count it as visible if
+ // it will become visible presently. Count text as visible if an animator
+ // is configured to make it grow.
+ boolean textIsGrowing = textAnimator != null && textAnimationIsGrowing;
+ return (currentTextSizeFactor > 0) || textIsGrowing;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 50c1ede..ee41001 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -53,6 +53,8 @@
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:
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c4d9cf5..91ae448 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -872,7 +872,7 @@
// From DevicePolicyAdmin
final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
- .getMaximumTimeToLockForUserAndProfiles(userId);
+ .getMaximumTimeToLock(null, userId);
long timeout;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 8d50d4b..0b7b6d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -72,30 +72,22 @@
import com.android.systemui.tuner.TunerService;
public class QSFooterImpl extends FrameLayout implements QSFooter,
- NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
+ OnClickListener, OnUserInfoChangedListener, EmergencyListener,
SignalCallback, CommandQueue.Callbacks {
private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
private ActivityStarter mActivityStarter;
- private NextAlarmController mNextAlarmController;
private UserInfoController mUserInfoController;
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
- private TextView mAlarmStatus;
- private View mAlarmStatusCollapsed;
- private View mDate;
-
private boolean mQsDisabled;
private QSPanel mQsPanel;
private boolean mExpanded;
- private boolean mAlarmShowing;
-
protected ExpandableIndicator mExpandIndicator;
private boolean mListening;
- private AlarmManager.AlarmClockInfo mNextAlarm;
private boolean mShowEmergencyCallsOnly;
protected MultiUserSwitch mMultiUserSwitch;
@@ -106,9 +98,6 @@
protected View mEdit;
private TouchAnimator mAnimator;
- private View mDateTimeGroup;
- private boolean mKeyguardShowing;
- private TouchAnimator mAlarmAnimator;
public QSFooterImpl(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -124,18 +113,11 @@
Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
mQsPanel.showEdit(view)));
- mDateTimeGroup = findViewById(id.date_time_alarm_group);
- mDate = findViewById(R.id.date);
-
mExpandIndicator = findViewById(R.id.expand_indicator);
mSettingsButton = findViewById(R.id.settings_button);
mSettingsContainer = findViewById(R.id.settings_button_container);
mSettingsButton.setOnClickListener(this);
- mAlarmStatusCollapsed = findViewById(R.id.alarm_status_collapsed);
- mAlarmStatus = findViewById(R.id.alarm_status);
- mDateTimeGroup.setOnClickListener(this);
-
mMultiUserSwitch = findViewById(R.id.multi_user_switch);
mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
@@ -146,7 +128,6 @@
updateResources();
- mNextAlarmController = Dependency.get(NextAlarmController.class);
mUserInfoController = Dependency.get(UserInfoController.class);
mActivityStarter = Dependency.get(ActivityStarter.class);
addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
@@ -165,28 +146,7 @@
isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0)
.addFloat(mSettingsButton, "rotation", -120, 0)
.build();
- if (mAlarmShowing) {
- int translate = isLayoutRtl() ? mDate.getWidth() : -mDate.getWidth();
- mAlarmAnimator = new Builder().addFloat(mDate, "alpha", 1, 0)
- .addFloat(mDateTimeGroup, "translationX", 0, translate)
- .addFloat(mAlarmStatus, "alpha", 0, 1)
- .setListener(new ListenerAdapter() {
- @Override
- public void onAnimationAtStart() {
- mAlarmStatus.setVisibility(View.GONE);
- }
- @Override
- public void onAnimationStarted() {
- mAlarmStatus.setVisibility(View.VISIBLE);
- }
- }).build();
- } else {
- mAlarmAnimator = null;
- mAlarmStatus.setVisibility(View.GONE);
- mDate.setAlpha(1);
- mDateTimeGroup.setTranslationX(0);
- }
setExpansion(mExpansionAmount);
}
@@ -203,27 +163,11 @@
}
private void updateResources() {
- FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
-
updateSettingsAnimator();
}
private void updateSettingsAnimator() {
mSettingsAlpha = createSettingsAlphaAnimator();
-
- final boolean isRtl = isLayoutRtl();
- if (isRtl && mDate.getWidth() == 0) {
- mDate.addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- mDate.setPivotX(getWidth());
- mDate.removeOnLayoutChangeListener(this);
- }
- });
- } else {
- mDate.setPivotX(isRtl ? mDate.getWidth() : 0);
- }
}
@Nullable
@@ -236,7 +180,6 @@
@Override
public void setKeyguardShowing(boolean keyguardShowing) {
- mKeyguardShowing = keyguardShowing;
setExpansion(mExpansionAmount);
}
@@ -248,36 +191,14 @@
}
@Override
- public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
- mNextAlarm = nextAlarm;
- if (nextAlarm != null) {
- String alarmString = KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm);
- mAlarmStatus.setText(alarmString);
- mAlarmStatus.setContentDescription(mContext.getString(
- R.string.accessibility_quick_settings_alarm, alarmString));
- mAlarmStatusCollapsed.setContentDescription(mContext.getString(
- R.string.accessibility_quick_settings_alarm, alarmString));
- }
- if (mAlarmShowing != (nextAlarm != null)) {
- mAlarmShowing = nextAlarm != null;
- updateAnimator(getWidth());
- updateEverything();
- }
- }
-
- @Override
public void setExpansion(float headerExpansionFraction) {
mExpansionAmount = headerExpansionFraction;
if (mAnimator != null) mAnimator.setPosition(headerExpansionFraction);
- if (mAlarmAnimator != null) mAlarmAnimator.setPosition(
- mKeyguardShowing ? 0 : headerExpansionFraction);
if (mSettingsAlpha != null) {
mSettingsAlpha.setPosition(headerExpansionFraction);
}
- updateAlarmVisibilities();
-
mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
}
@@ -295,10 +216,6 @@
super.onDetachedFromWindow();
}
- private void updateAlarmVisibilities() {
- mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
- }
-
@Override
public void setListening(boolean listening) {
if (listening == mListening) {
@@ -329,8 +246,6 @@
}
private void updateVisibilities() {
- updateAlarmVisibilities();
-
mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
@@ -349,14 +264,12 @@
private void updateListeners() {
if (mListening) {
- mNextAlarmController.addCallback(this);
mUserInfoController.addCallback(this);
if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) {
Dependency.get(NetworkController.class).addEmergencyListener(this);
Dependency.get(NetworkController.class).addCallback(this);
}
} else {
- mNextAlarmController.removeCallback(this);
mUserInfoController.removeCallback(this);
Dependency.get(NetworkController.class).removeEmergencyListener(this);
Dependency.get(NetworkController.class).removeCallback(this);
@@ -400,16 +313,6 @@
} else {
startSettingsActivity();
}
- } else if (v == mDateTimeGroup) {
- Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
- mNextAlarm != null);
- if (mNextAlarm != null) {
- PendingIntent showIntent = mNextAlarm.getShowIntent();
- mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
- } else {
- mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- AlarmClock.ACTION_SHOW_ALARMS), 0);
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 75321fd..8325df7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -25,6 +25,7 @@
import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -56,6 +57,7 @@
private static final boolean ICON_ANMATIONS_WHILE_SCROLLING
= SystemProperties.getBoolean("debug.icon_scroll_animations", true);
private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
+ private static final String TAG = "NotificationShelf";
private ViewInvertHelper mViewInvertHelper;
private boolean mDark;
private NotificationIconContainer mShelfIcons;
@@ -98,7 +100,7 @@
setClipToActualHeight(false);
setClipChildren(false);
setClipToPadding(false);
- mShelfIcons.setShowAllIcons(false);
+ mShelfIcons.setIsStaticLayout(false);
mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
NotificationPanelView.DOZE_ANIMATION_DURATION);
mShelfState = new ShelfState();
@@ -294,10 +296,15 @@
if (notGoneIndex == 0) {
StatusBarIconView icon = row.getEntry().expandedIcon;
NotificationIconContainer.IconState iconState = getIconState(icon);
- if (iconState.clampedAppearAmount == 1.0f) {
+ if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
// only if the first icon is fully in the shelf we want to clip to it!
backgroundTop = (int) (row.getTranslationY() - getTranslationY());
firstElementRoundness = row.getCurrentTopRoundness();
+ } else if (iconState == null) {
+ Log.wtf(TAG, "iconState is null. ExpandedIcon: " + row.getEntry().expandedIcon
+ + (row.getEntry().expandedIcon != null
+ ? "\n icon parent: " + row.getEntry().expandedIcon.getParent() : "")
+ + " \n number of notifications: " + mHostLayout.getChildCount() );
}
}
notGoneIndex++;
@@ -681,7 +688,8 @@
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 +699,9 @@
// 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 +711,6 @@
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/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 61f3130..61cb61c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -55,6 +55,7 @@
private KeyguardMonitor mKeyguardMonitor;
private NetworkController mNetworkController;
private LinearLayout mSystemIconArea;
+ private View mClockView;
private View mNotificationIconAreaInner;
private int mDisabled1;
private StatusBar mStatusBarComponent;
@@ -93,6 +94,7 @@
mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
+ mClockView = mStatusBar.findViewById(R.id.clock);
mSignalClusterView = mStatusBar.findViewById(R.id.signal_cluster);
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView);
// Default to showing until we know otherwise.
@@ -197,10 +199,12 @@
public void hideSystemIconArea(boolean animate) {
animateHide(mSystemIconArea, animate);
+ animateHide(mClockView, animate);
}
public void showSystemIconArea(boolean animate) {
animateShow(mSystemIconArea, animate);
+ animateShow(mClockView, animate);
}
public void hideNotificationIconArea(boolean animate) {
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 316bd5b..7f4deb0 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 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 a1b49c1..91cae0a 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 @@
}.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 @@
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 @@
mIconSize = child.getWidth();
}
}
- if (mShowAllIcons) {
+ if (mIsStaticLayout) {
resetViewStates();
calculateIconTranslations();
applyIconStates();
@@ -287,7 +291,8 @@
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 @@
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 @@
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 @@
}
/**
- * 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 @@
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,19 +479,43 @@
mOpenedAmount = expandAmount;
}
- public float getVisualOverflowAdaption() {
- return mVisualOverflowAdaption;
- }
-
- public void setVisualOverflowAdaption(float visualOverflowAdaption) {
- mVisualOverflowAdaption = visualOverflowAdaption;
- }
-
public boolean hasOverflow() {
+ if (mIsStaticLayout) {
+ return getChildCount() > MAX_STATIC_ICONS;
+ }
+
float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize;
return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0;
}
+ /**
+ * 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;
+ }
+
+ /**
+ * 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() {
return mIconSize;
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 2129790..4464f75 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -168,7 +168,7 @@
}
protected int getAudioManagerStreamMinVolume(int stream) {
- return mAudio.getStreamMinVolume(stream);
+ return mAudio.getStreamMinVolumeInt(stream);
}
public void register() {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
similarity index 98%
rename from services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
rename to services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 01679dd..3d7d6b7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -71,7 +71,7 @@
* This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
* It is responsible for behavior common to both types of clients.
*/
-abstract class AccessibilityClientConnection extends IAccessibilityServiceConnection.Stub
+abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub
implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter,
FingerprintGestureDispatcher.FingerprintGestureClient {
private static final boolean DEBUG = false;
@@ -238,7 +238,7 @@
int flags);
}
- public AccessibilityClientConnection(Context context, ComponentName componentName,
+ public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
Object lock, SecurityPolicy securityPolicy, SystemSupport systemSupport,
WindowManagerInternal windowManagerInternal,
@@ -339,6 +339,11 @@
}
}
+ int getRelevantEventTypes() {
+ return (mUsesAccessibilityCache ? AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK : 0)
+ | mEventTypes;
+ }
+
@Override
public void setServiceInfo(AccessibilityServiceInfo info) {
final long identity = Binder.clearCallingIdentity();
@@ -954,13 +959,15 @@
public void notifyAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
+ final int eventType = event.getEventType();
+
final boolean serviceWantsEvent = wantsEventLocked(event);
- if (!serviceWantsEvent && !mUsesAccessibilityCache &&
- ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & event.getEventType()) == 0)) {
+ final boolean requiredForCacheConsistency = mUsesAccessibilityCache
+ && ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & eventType) != 0);
+ if (!serviceWantsEvent && !requiredForCacheConsistency) {
return;
}
- final int eventType = event.getEventType();
// Make a copy since during dispatch it is possible the event to
// be modified to remove its source if the receiving service does
// not have permission to access the window content.
@@ -1226,6 +1233,10 @@
return windowId;
}
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
private final class InvocationHandler extends Handler {
public static final int MSG_ON_GESTURE = 1;
public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 270a762..d83f6ae 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -20,6 +20,8 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -84,7 +86,6 @@
import android.view.View;
import android.view.WindowInfo;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityCache;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
@@ -114,6 +115,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -131,7 +133,7 @@
* on the device. Events are dispatched to {@link AccessibilityService}s.
*/
public class AccessibilityManagerService extends IAccessibilityManager.Stub
- implements AccessibilityClientConnection.SystemSupport {
+ implements AbstractAccessibilityServiceConnection.SystemSupport {
private static final boolean DEBUG = false;
@@ -455,7 +457,7 @@
}
@Override
- public long addClient(IAccessibilityManagerClient client, int userId) {
+ public long addClient(IAccessibilityManagerClient callback, int userId) {
synchronized (mLock) {
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
@@ -467,15 +469,17 @@
// the system UI or the system we add it to the global state that
// is shared across users.
UserState userState = getUserStateLocked(resolvedUserId);
+ Client client = new Client(callback, Binder.getCallingUid(), userState);
if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
- mGlobalClients.register(client);
+ mGlobalClients.register(callback, client);
if (DEBUG) {
Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
}
return IntPair.of(
- userState.getClientState(), userState.mLastSentRelevantEventTypes);
+ userState.getClientState(),
+ client.mLastSentRelevantEventTypes);
} else {
- userState.mUserClients.register(client);
+ userState.mUserClients.register(callback, client);
// If this client is not for the current user we do not
// return a state since it is not for the foreground user.
// We will send the state to the client on a user switch.
@@ -485,7 +489,7 @@
}
return IntPair.of(
(resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0,
- userState.mLastSentRelevantEventTypes);
+ client.mLastSentRelevantEventTypes);
}
}
}
@@ -951,7 +955,7 @@
* Has no effect if no item has accessibility focus, if the item with accessibility
* focus does not expose the specified action, or if the action fails.
*
- * @param actionId The id of the action to perform.
+ * @param action The action to perform.
*
* @return {@code true} if the action was performed. {@code false} if it was not.
*/
@@ -1329,33 +1333,67 @@
}
private void updateRelevantEventsLocked(UserState userState) {
- int relevantEventTypes = AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK;
- for (AccessibilityServiceConnection service : userState.mBoundServices) {
- relevantEventTypes |= service.mEventTypes;
- }
- relevantEventTypes |= mUiAutomationManager.getRequestedEventMaskLocked();
- int finalRelevantEventTypes = relevantEventTypes;
+ mMainHandler.post(() -> {
+ broadcastToClients(userState, ignoreRemoteException(client -> {
+ int relevantEventTypes = computeRelevantEventTypes(userState, client);
- if (userState.mLastSentRelevantEventTypes != finalRelevantEventTypes) {
- userState.mLastSentRelevantEventTypes = finalRelevantEventTypes;
- mMainHandler.obtainMessage(MainHandler.MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS,
- userState.mUserId, finalRelevantEventTypes);
- mMainHandler.post(() -> {
- broadcastToClients(userState, (client) -> {
- try {
- client.setRelevantEventTypes(finalRelevantEventTypes);
- } catch (RemoteException re) {
- /* ignore */
- }
- });
- });
+ if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+ client.mLastSentRelevantEventTypes = relevantEventTypes;
+ client.mCallback.setRelevantEventTypes(relevantEventTypes);
+ }
+ }));
+ });
+ }
+
+ private int computeRelevantEventTypes(UserState userState, Client client) {
+ int relevantEventTypes = 0;
+
+ int numBoundServices = userState.mBoundServices.size();
+ for (int i = 0; i < numBoundServices; i++) {
+ AccessibilityServiceConnection service =
+ userState.mBoundServices.get(i);
+ relevantEventTypes |= isClientInPackageWhitelist(service.getServiceInfo(), client)
+ ? service.getRelevantEventTypes()
+ : 0;
}
+ relevantEventTypes |= isClientInPackageWhitelist(
+ mUiAutomationManager.getServiceInfo(), client)
+ ? mUiAutomationManager.getRelevantEventTypes()
+ : 0;
+ return relevantEventTypes;
+ }
+
+ private static boolean isClientInPackageWhitelist(
+ @Nullable AccessibilityServiceInfo serviceInfo, Client client) {
+ if (serviceInfo == null) return false;
+
+ String[] clientPackages = client.mPackageNames;
+ boolean result = ArrayUtils.isEmpty(serviceInfo.packageNames);
+ if (!result && clientPackages != null) {
+ for (String packageName : clientPackages) {
+ if (ArrayUtils.contains(serviceInfo.packageNames, packageName)) {
+ result = true;
+ break;
+ }
+ }
+ }
+ if (!result) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Dropping events: "
+ + Arrays.toString(clientPackages) + " -> "
+ + serviceInfo.getComponentName().flattenToShortString()
+ + " due to not being in package whitelist "
+ + Arrays.toString(serviceInfo.packageNames));
+ }
+ }
+
+ return result;
}
private void broadcastToClients(
- UserState userState, Consumer<IAccessibilityManagerClient> clientAction) {
- mGlobalClients.broadcast(clientAction);
- userState.mUserClients.broadcast(clientAction);
+ UserState userState, Consumer<Client> clientAction) {
+ mGlobalClients.broadcastForEachCookie(clientAction);
+ userState.mUserClients.broadcastForEachCookie(clientAction);
}
private void unbindAllServicesLocked(UserState userState) {
@@ -2156,6 +2194,7 @@
* permission to write secure settings, since someone with that permission can enable
* accessibility services themselves.
*/
+ @Override
public void performAccessibilityShortcut() {
if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
&& (mContext.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -2445,13 +2484,8 @@
synchronized (mLock) {
userState = getUserStateLocked(userId);
}
- broadcastToClients(userState, (client) -> {
- try {
- client.setRelevantEventTypes(relevantEventTypes);
- } catch (RemoteException re) {
- /* ignore */
- }
- });
+ broadcastToClients(userState, ignoreRemoteException(
+ client -> client.mCallback.setRelevantEventTypes(relevantEventTypes)));
} break;
case MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER: {
@@ -2500,30 +2534,14 @@
private void sendStateToClients(int clientState,
RemoteCallbackList<IAccessibilityManagerClient> clients) {
- clients.broadcast((client) -> {
- try {
- client.setState(clientState);
- } catch (RemoteException re) {
- /* ignore */
- }
- });
+ clients.broadcast(ignoreRemoteException(
+ client -> client.setState(clientState)));
}
private void notifyClientsOfServicesStateChange(
RemoteCallbackList<IAccessibilityManagerClient> clients) {
- try {
- final int userClientCount = clients.beginBroadcast();
- for (int i = 0; i < userClientCount; i++) {
- IAccessibilityManagerClient client = clients.getBroadcastItem(i);
- try {
- client.notifyServicesStateChanged();
- } catch (RemoteException re) {
- /* ignore */
- }
- }
- } finally {
- clients.finishBroadcast();
- }
+ clients.broadcast(ignoreRemoteException(
+ client -> client.notifyServicesStateChanged()));
}
}
@@ -3335,20 +3353,20 @@
}
public boolean canGetAccessibilityNodeInfoLocked(
- AccessibilityClientConnection service, int windowId) {
+ AbstractAccessibilityServiceConnection service, int windowId) {
return canRetrieveWindowContentLocked(service) && isRetrievalAllowingWindow(windowId);
}
- public boolean canRetrieveWindowsLocked(AccessibilityClientConnection service) {
+ public boolean canRetrieveWindowsLocked(AbstractAccessibilityServiceConnection service) {
return canRetrieveWindowContentLocked(service) && service.mRetrieveInteractiveWindows;
}
- public boolean canRetrieveWindowContentLocked(AccessibilityClientConnection service) {
+ public boolean canRetrieveWindowContentLocked(AbstractAccessibilityServiceConnection service) {
return (service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
}
- public boolean canControlMagnification(AccessibilityClientConnection service) {
+ public boolean canControlMagnification(AbstractAccessibilityServiceConnection service) {
return (service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
}
@@ -3445,7 +3463,7 @@
final int windowCount = mWindows.size();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = mWindows.get(i);
- if (window.inPictureInPicture()) {
+ if (window.isInPictureInPictureMode()) {
return window;
}
}
@@ -3476,13 +3494,26 @@
}
}
+ /** Represents an {@link AccessibilityManager} */
+ class Client {
+ final IAccessibilityManagerClient mCallback;
+ final String[] mPackageNames;
+ int mLastSentRelevantEventTypes;
+
+ private Client(IAccessibilityManagerClient callback, int clientUid, UserState userState) {
+ mCallback = callback;
+ mPackageNames = mPackageManager.getPackagesForUid(clientUid);
+ mLastSentRelevantEventTypes = computeRelevantEventTypes(userState, this);
+ }
+ }
+
public class UserState {
public final int mUserId;
// Non-transient state.
public final RemoteCallbackList<IAccessibilityManagerClient> mUserClients =
- new RemoteCallbackList<>();
+ new RemoteCallbackList<>();
public final SparseArray<RemoteAccessibilityConnection> mInteractionConnections =
new SparseArray<>();
@@ -3494,8 +3525,6 @@
public final CopyOnWriteArrayList<AccessibilityServiceConnection> mBoundServices =
new CopyOnWriteArrayList<>();
- public int mLastSentRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
-
public final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
new HashMap<>();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 9cafa1e..5f6efb6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -19,9 +19,7 @@
import static android.provider.Settings.Secure.SHOW_MODE_AUTO;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.IAccessibilityServiceClient;
-import android.accessibilityservice.IAccessibilityServiceConnection;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -39,7 +37,6 @@
import com.android.server.wm.WindowManagerInternal;
import java.lang.ref.WeakReference;
-import java.util.List;
import java.util.Set;
/**
@@ -50,7 +47,7 @@
* passed to the service it represents as soon it is bound. It also serves as the
* connection for the service.
*/
-class AccessibilityServiceConnection extends AccessibilityClientConnection {
+class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
private static final String LOG_TAG = "AccessibilityServiceConnection";
/*
Holding a weak reference so there isn't a loop of references. UserState keeps lists of bound
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 56a9534..ed3b3e7 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -18,6 +18,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.annotation.Nullable;
import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.Context;
@@ -46,7 +47,7 @@
private AccessibilityServiceInfo mUiAutomationServiceInfo;
- private AccessibilityClientConnection.SystemSupport mSystemSupport;
+ private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport;
private int mUiAutomationFlags;
@@ -78,7 +79,7 @@
Context context, AccessibilityServiceInfo accessibilityServiceInfo,
int id, Handler mainHandler, Object lock,
AccessibilityManagerService.SecurityPolicy securityPolicy,
- AccessibilityClientConnection.SystemSupport systemSupport,
+ AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
WindowManagerInternal windowManagerInternal,
GlobalActionPerformer globalActionPerfomer, int flags) {
accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
@@ -157,6 +158,17 @@
return mUiAutomationService.mEventTypes;
}
+ int getRelevantEventTypes() {
+ if (mUiAutomationService == null) return 0;
+ return mUiAutomationService.getRelevantEventTypes();
+ }
+
+ @Nullable
+ AccessibilityServiceInfo getServiceInfo() {
+ if (mUiAutomationService == null) return null;
+ return mUiAutomationService.getServiceInfo();
+ }
+
void dumpUiAutomationService(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (mUiAutomationService != null) {
mUiAutomationService.dump(fd, pw, args);
@@ -176,7 +188,7 @@
mSystemSupport.onClientChange(false);
}
- private class UiAutomationService extends AccessibilityClientConnection {
+ private class UiAutomationService extends AbstractAccessibilityServiceConnection {
private final Handler mMainHandler;
UiAutomationService(Context context, AccessibilityServiceInfo accessibilityServiceInfo,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 690c45b..e1cb154 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -568,13 +568,12 @@
@Override
public FillEventHistory getFillEventHistory() throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getFillEventHistory(uid);
+ return service.getFillEventHistory(getCallingUid());
}
}
@@ -583,13 +582,12 @@
@Override
public UserData getUserData() throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getUserData(uid);
+ return service.getUserData(getCallingUid());
}
}
@@ -598,26 +596,24 @@
@Override
public void setUserData(UserData userData) throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- service.setUserData(uid, userData);
+ service.setUserData(getCallingUid(), userData);
}
}
}
@Override
public boolean isFieldClassificationEnabled() throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.isFieldClassificationEnabled();
+ return service.isFieldClassificationEnabled(getCallingUid());
}
}
@@ -625,6 +621,20 @@
}
@Override
+ public ComponentName getAutofillServiceComponentName() throws RemoteException {
+ final int userId = UserHandle.getCallingUserId();
+
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ return service.getServiceComponentName();
+ }
+ }
+
+ return null;
+ }
+
+ @Override
public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
throws RemoteException {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 3361824..9ecf63d 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -43,7 +43,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -835,7 +834,7 @@
pw.println(mContext.getString(R.string.config_defaultAutofillService));
pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
pw.print(prefix); pw.print("Field classification enabled: ");
- pw.println(isFieldClassificationEnabled());
+ pw.println(isFieldClassificationEnabledLocked());
pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
@@ -1095,7 +1094,18 @@
return false;
}
- boolean isFieldClassificationEnabled() {
+ // Called by AutofillManager, checks UID.
+ boolean isFieldClassificationEnabled(int uid) {
+ synchronized (mLock) {
+ if (!isCalledByServiceLocked("isFieldClassificationEnabled", uid)) {
+ return false;
+ }
+ return isFieldClassificationEnabledLocked();
+ }
+ }
+
+ // Called by internally, no need to check UID.
+ boolean isFieldClassificationEnabledLocked() {
return Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION, 0,
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7b85a6c..6d4a525 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -65,7 +65,6 @@
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;
@@ -500,6 +499,8 @@
@Override
public void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response,
@NonNull String servicePackageName) {
+ final AutofillId[] fieldClassificationIds;
+
synchronized (mLock) {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
@@ -510,13 +511,13 @@
processNullResponseLocked(requestFlags);
return;
}
- }
- final AutofillId[] fieldClassificationIds = response.getFieldClassificationIds();
- if (fieldClassificationIds != null && !mService.isFieldClassificationEnabled()) {
- Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
- processNullResponseLocked(requestFlags);
- return;
+ fieldClassificationIds = response.getFieldClassificationIds();
+ if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
+ Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
+ processNullResponseLocked(requestFlags);
+ return;
+ }
}
mService.setLastResponse(id, response);
@@ -1558,7 +1559,7 @@
*
* <p>A new request will be started in 2 scenarios:
* <ol>
- * <li>If the user manually requested autofill after the view was already filled.
+ * <li>If the user manually requested autofill.
* <li>If the view is part of a new partition.
* </ol>
*
@@ -1566,14 +1567,10 @@
* @param viewState The view that is entered.
* @param flags The flag that was passed by the AutofillManager.
*/
- private void requestNewFillResponseIfNecessaryLocked(@NonNull AutofillId id,
+ private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
@NonNull ViewState viewState, int flags) {
- // First check if this is a manual request after view was autofilled.
- final int state = viewState.getState();
- final boolean restart = (state & STATE_AUTOFILLED) != 0
- && (flags & FLAG_MANUAL_REQUEST) != 0;
- if (restart) {
- if (sDebug) Slog.d(TAG, "Re-starting session on view " + id);
+ if ((flags & FLAG_MANUAL_REQUEST) != 0) {
+ if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
viewState.setState(STATE_RESTARTED_SESSION);
requestNewFillResponseLocked(flags);
return;
@@ -1728,7 +1725,7 @@
if (sVerbose && virtualBounds != null) {
Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds);
}
- requestNewFillResponseIfNecessaryLocked(id, viewState, flags);
+ requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags);
// Remove the UI if the ViewState has changed.
if (mCurrentViewId != viewState.id) {
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index f185443..fbdb183 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -483,6 +483,7 @@
description.currentDestinationString = currentDestinationString;
description.dataManagementIntent = dataManagementIntent;
description.dataManagementLabel = dataManagementLabel;
+ Slog.d(TAG, "Transport " + name + " updated its attributes");
}
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index a764808..d3ab125 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -988,12 +988,6 @@
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,
@@ -1001,6 +995,14 @@
OsConstants.UDP_ENCAP,
OsConstants.UDP_ENCAP_ESPINUDP);
+ mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(sockFd, callingUid);
+ if (port != 0) {
+ Log.v(TAG, "Binding to port " + port);
+ Os.bind(sockFd, INADDR_ANY, port);
+ } else {
+ port = bindToRandomPort(sockFd);
+ }
+
userRecord.mEncapSocketRecords.put(
resourceId,
new RefcountedResource<EncapSocketRecord>(
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index f3ccba5..4582430 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -116,6 +116,50 @@
return scheduleSyncLocked("remove-uid", UPDATE_CPU);
}
+ @Override
+ public Future<?> scheduleReadProcStateCpuTimes() {
+ synchronized (mStats) {
+ if (!mStats.mPerProcStateCpuTimesAvailable) {
+ return null;
+ }
+ }
+ synchronized (BatteryExternalStatsWorker.this) {
+ if (!mExecutorService.isShutdown()) {
+ return mExecutorService.submit(mReadProcStateCpuTimesTask);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+ synchronized (mStats) {
+ if (!mStats.mPerProcStateCpuTimesAvailable) {
+ return null;
+ }
+ }
+ synchronized (BatteryExternalStatsWorker.this) {
+ if (!mExecutorService.isShutdown()) {
+ return mExecutorService.submit(mCopyFromAllUidsCpuTimesTask);
+ }
+ }
+ return null;
+ }
+
+ private final Runnable mReadProcStateCpuTimesTask = new Runnable() {
+ @Override
+ public void run() {
+ mStats.updateProcStateCpuTimes();
+ }
+ };
+
+ private final Runnable mCopyFromAllUidsCpuTimesTask = new Runnable() {
+ @Override
+ public void run() {
+ mStats.copyFromAllUidsCpuTimes();
+ }
+ };
+
public synchronized Future<?> scheduleWrite() {
if (mExecutorService.isShutdown()) {
return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
@@ -185,6 +229,10 @@
}
}
+ if ((updateFlags & UPDATE_CPU) != 0) {
+ mStats.copyFromAllUidsCpuTimes();
+ }
+
// Clean up any UIDs if necessary.
synchronized (mStats) {
for (int uid : uidsToRemove) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 93fb3e3..b920b57 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -38,6 +38,7 @@
import android.os.UserHandle;
import android.os.UserManagerInternal;
import android.os.WorkSource;
+import android.os.connectivity.CellularBatteryStats;
import android.os.health.HealthStatsParceler;
import android.os.health.HealthStatsWriter;
import android.os.health.UidHealthStats;
@@ -207,6 +208,10 @@
}
}
+ private void syncStats(String reason, int flags) {
+ awaitUninterruptibly(mWorker.scheduleSync(reason, flags));
+ }
+
/**
* At the time when the constructor runs, the power manager has not yet been
* initialized. So we initialize the low power observer later.
@@ -225,7 +230,7 @@
public void shutdown() {
Slog.w("BatteryStats", "Writing battery stats before shutdown...");
- awaitUninterruptibly(mWorker.scheduleSync("shutdown", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("shutdown", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.shutdownLocked();
@@ -357,7 +362,7 @@
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
Parcel out = Parcel.obtain();
- awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.writeToParcel(out, 0);
}
@@ -372,7 +377,7 @@
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
Parcel out = Parcel.obtain();
- awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.writeToParcel(out, 0);
}
@@ -1237,8 +1242,7 @@
}
mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL);
} else if ("--write".equals(arg)) {
- awaitUninterruptibly(mWorker.scheduleSync("dump",
- BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.writeSyncLocked();
pw.println("Battery stats written.");
@@ -1302,7 +1306,7 @@
flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY;
}
// Fetch data from external sources and update the BatteryStatsImpl object with them.
- awaitUninterruptibly(mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1405,6 +1409,16 @@
}
/**
+ * Gets a snapshot of cellular stats
+ * @hide
+ */
+ public CellularBatteryStats getCellularBatteryStats() {
+ synchronized (mStats) {
+ return mStats.getCellularBatteryStats();
+ }
+ }
+
+ /**
* Gets a snapshot of the system health for a particular uid.
*/
@Override
@@ -1415,8 +1429,7 @@
}
long ident = Binder.clearCallingIdentity();
try {
- awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
- BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-health-stats-for-uids", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
return getHealthStatsForUidLocked(requestUid);
}
@@ -1440,8 +1453,7 @@
long ident = Binder.clearCallingIdentity();
int i=-1;
try {
- awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
- BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-health-stats-for-uids", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
final int N = requestUids.length;
final HealthStatsParceler[] results = new HealthStatsParceler[N];
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 2d7a6ad..6e7b43e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2203,7 +2203,7 @@
return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
}
- /** @see AudioManager#getStreamMinVolume(int) */
+ /** @see AudioManager#getStreamMinVolumeInt(int) */
public int getStreamMinVolume(int streamType) {
ensureValidStreamType(streamType);
return (mStreamStates[streamType].getMinIndex() + 5) / 10;
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index c2167eb..85686ae 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -99,7 +99,7 @@
private final float mProjMatrix[] = new float[16];
private final int[] mGLBuffers = new int[2];
private int mTexCoordLoc, mVertexLoc, mTexUnitLoc, mProjMatrixLoc, mTexMatrixLoc;
- private int mOpacityLoc, mScaleLoc, mGammaLoc, mSaturationLoc;
+ private int mOpacityLoc, mGammaLoc, mSaturationLoc;
private int mProgram;
// Vertex and corresponding texture coordinates.
@@ -246,7 +246,6 @@
mOpacityLoc = GLES20.glGetUniformLocation(mProgram, "opacity");
mGammaLoc = GLES20.glGetUniformLocation(mProgram, "gamma");
mSaturationLoc = GLES20.glGetUniformLocation(mProgram, "saturation");
- mScaleLoc = GLES20.glGetUniformLocation(mProgram, "scale");
mTexUnitLoc = GLES20.glGetUniformLocation(mProgram, "texUnit");
GLES20.glUseProgram(mProgram);
@@ -395,9 +394,8 @@
double sign = cos < 0 ? -1 : 1;
float opacity = (float) -Math.pow(one_minus_level, 2) + 1;
float saturation = (float) Math.pow(level, 4);
- float scale = (float) ((-Math.pow(one_minus_level, 2) + 1) * 0.1d + 0.9d);
float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d);
- drawFaded(opacity, 1.f / gamma, saturation, scale);
+ drawFaded(opacity, 1.f / gamma, saturation);
if (checkGlErrors("drawFrame")) {
return false;
}
@@ -409,10 +407,10 @@
return showSurface(1.0f);
}
- private void drawFaded(float opacity, float gamma, float saturation, float scale) {
+ private void drawFaded(float opacity, float gamma, float saturation) {
if (DEBUG) {
Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma +
- ", saturation=" + saturation + ", scale=" + scale);
+ ", saturation=" + saturation);
}
// Use shaders
GLES20.glUseProgram(mProgram);
@@ -423,7 +421,6 @@
GLES20.glUniform1f(mOpacityLoc, opacity);
GLES20.glUniform1f(mGammaLoc, gamma);
GLES20.glUniform1f(mSaturationLoc, saturation);
- GLES20.glUniform1f(mScaleLoc, scale);
// Use textures
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 26edf62..dc95d41 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -482,7 +482,7 @@
IContextHubClient client = mDefaultClientMap.get(contextHubHandle);
success = (client.sendMessageToNanoApp(message) ==
- ContextHubTransaction.TRANSACTION_SUCCESS);
+ ContextHubTransaction.RESULT_SUCCESS);
} else {
Log.e(TAG, "Failed to send nanoapp message - nanoapp with handle "
+ nanoAppHandle + " does not exist.");
@@ -642,7 +642,7 @@
if (nanoAppBinary == null) {
Log.e(TAG, "NanoAppBinary cannot be null in loadNanoAppOnHub");
transactionCallback.onTransactionComplete(
- ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS);
+ ContextHubTransaction.RESULT_FAILED_BAD_PARAMS);
return;
}
@@ -817,7 +817,7 @@
if (mContextHubProxy == null) {
try {
callback.onTransactionComplete(
- ContextHubTransaction.TRANSACTION_FAILED_HAL_UNAVAILABLE);
+ ContextHubTransaction.RESULT_FAILED_HAL_UNAVAILABLE);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while calling onTransactionComplete", e);
}
@@ -828,7 +828,7 @@
+ ContextHubTransaction.typeToString(transactionType, false /* upperCase */)
+ " transaction for invalid hub ID " + contextHubId);
try {
- callback.onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS);
+ callback.onTransactionComplete(ContextHubTransaction.RESULT_FAILED_BAD_PARAMS);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while calling onTransactionComplete", e);
}
diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
index 7a57dd3..c356b63 100644
--- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -215,17 +215,17 @@
static int toTransactionResult(int halResult) {
switch (halResult) {
case Result.OK:
- return ContextHubTransaction.TRANSACTION_SUCCESS;
+ return ContextHubTransaction.RESULT_SUCCESS;
case Result.BAD_PARAMS:
- return ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS;
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
case Result.NOT_INIT:
- return ContextHubTransaction.TRANSACTION_FAILED_UNINITIALIZED;
+ return ContextHubTransaction.RESULT_FAILED_UNINITIALIZED;
case Result.TRANSACTION_PENDING:
- return ContextHubTransaction.TRANSACTION_FAILED_PENDING;
+ return ContextHubTransaction.RESULT_FAILED_PENDING;
case Result.TRANSACTION_FAILED:
case Result.UNKNOWN_FAILURE:
default: /* fall through */
- return ContextHubTransaction.TRANSACTION_FAILED_UNKNOWN;
+ return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
}
}
}
diff --git a/services/core/java/com/android/server/location/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
index 412d43d..cced781 100644
--- a/services/core/java/com/android/server/location/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
@@ -120,7 +120,7 @@
@Override
/* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
- if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
// NOTE: The legacy JNI code used to do a query right after a load success
// to synchronize the service cache. Instead store the binary that was
// requested to load to update the cache later without doing a query.
@@ -130,7 +130,7 @@
}
try {
onCompleteCallback.onTransactionComplete(result);
- if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId());
}
} catch (RemoteException e) {
@@ -166,12 +166,12 @@
@Override
/* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
- if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
mNanoAppStateManager.removeNanoAppInstance(contextHubId, nanoAppId);
}
try {
onCompleteCallback.onTransactionComplete(result);
- if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId);
}
} catch (RemoteException e) {
@@ -334,8 +334,8 @@
transaction.onTransactionComplete(
(result == TransactionResult.SUCCESS) ?
- ContextHubTransaction.TRANSACTION_SUCCESS :
- ContextHubTransaction.TRANSACTION_FAILED_AT_HUB);
+ ContextHubTransaction.RESULT_SUCCESS :
+ ContextHubTransaction.RESULT_FAILED_AT_HUB);
removeTransactionAndStartNext();
}
@@ -356,7 +356,7 @@
return;
}
- transaction.onQueryResponse(ContextHubTransaction.TRANSACTION_SUCCESS, nanoAppStateList);
+ transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList);
removeTransactionAndStartNext();
}
@@ -416,7 +416,7 @@
if (!transaction.isComplete()) {
Log.d(TAG, transaction + " timed out");
transaction.onTransactionComplete(
- ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
+ ContextHubTransaction.RESULT_FAILED_TIMEOUT);
removeTransactionAndStartNext();
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index aa55930..eef4d9b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -38,6 +38,7 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.PasswordMetrics;
import android.app.backup.BackupManager;
import android.app.trust.IStrongAuthTracker;
@@ -90,6 +91,7 @@
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
@@ -100,6 +102,7 @@
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
@@ -891,14 +894,26 @@
String managedUserPassword) {
checkWritePermission(userId);
synchronized (mSeparateChallengeLock) {
- setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
- if (enabled) {
- mStorage.removeChildProfileLock(userId);
- removeKeystoreProfileKey(userId);
- } else {
- tieManagedProfileLockIfNecessary(userId, managedUserPassword);
- }
+ setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword);
}
+ notifySeparateProfileChallengeChanged(userId);
+ }
+
+ @GuardedBy("mSeparateChallengeLock")
+ private void setSeparateProfileChallengeEnabledLocked(@UserIdInt int userId, boolean enabled,
+ String managedUserPassword) {
+ setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
+ if (enabled) {
+ mStorage.removeChildProfileLock(userId);
+ removeKeystoreProfileKey(userId);
+ } else {
+ tieManagedProfileLockIfNecessary(userId, managedUserPassword);
+ }
+ }
+
+ private void notifySeparateProfileChallengeChanged(int userId) {
+ LocalServices.getService(DevicePolicyManagerInternal.class)
+ .reportSeparateProfileChallengeChanged(userId);
}
@Override
@@ -1234,9 +1249,10 @@
checkWritePermission(userId);
synchronized (mSeparateChallengeLock) {
setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId);
- setSeparateProfileChallengeEnabled(userId, true, null);
+ setSeparateProfileChallengeEnabledLocked(userId, true, null);
notifyPasswordChanged(userId);
}
+ notifySeparateProfileChallengeChanged(userId);
}
private void setLockCredentialInternal(String credential, int credentialType,
@@ -1741,6 +1757,10 @@
}
}
}
+ // Use credentials to create recoverable keystore snapshot.
+ mRecoverableKeyStoreManager.lockScreenSecretAvailable(storedHash.type, credential,
+ userId);
+
} else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
if (response.getTimeout() > 0) {
requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
@@ -1931,50 +1951,63 @@
@Override
public void initRecoveryService(@NonNull String rootCertificateAlias,
- @NonNull byte[] signedPublicKeyList, int userId)
+ @NonNull byte[] signedPublicKeyList, @UserIdInt int userId)
throws RemoteException {
mRecoverableKeyStoreManager.initRecoveryService(rootCertificateAlias,
signedPublicKeyList, userId);
}
@Override
- public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
+ public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, @UserIdInt int userId)
throws RemoteException {
return mRecoverableKeyStoreManager.getRecoveryData(account, userId);
}
+ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+ throws RemoteException {
+ mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, userId);
+ }
+
+ public Map getRecoverySnapshotVersions(int userId) throws RemoteException {
+ return mRecoverableKeyStoreManager.getRecoverySnapshotVersions(userId);
+ }
+
@Override
- public void setServerParameters(long serverParameters, int userId) throws RemoteException {
+ public void setServerParameters(long serverParameters, @UserIdInt int userId)
+ throws RemoteException {
mRecoverableKeyStoreManager.setServerParameters(serverParameters, userId);
}
@Override
public void setRecoveryStatus(@NonNull String packageName, @Nullable String[] aliases,
- int status, int userId) throws RemoteException {
+ int status, @UserIdInt int userId) throws RemoteException {
mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId);
}
+ public Map getRecoveryStatus(@Nullable String packageName, int userId) throws RemoteException {
+ return mRecoverableKeyStoreManager.getRecoveryStatus(packageName, userId);
+ }
+
@Override
public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
- int[] secretTypes, int userId) throws RemoteException {
+ int[] secretTypes, @UserIdInt int userId) throws RemoteException {
mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes, userId);
}
@Override
- public int[] getRecoverySecretTypes(int userId) throws RemoteException {
+ public int[] getRecoverySecretTypes(@UserIdInt int userId) throws RemoteException {
return mRecoverableKeyStoreManager.getRecoverySecretTypes(userId);
}
@Override
- public int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
+ public int[] getPendingRecoverySecretTypes(@UserIdInt int userId) throws RemoteException {
throw new SecurityException("Not implemented");
}
@Override
public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret,
- int userId)
- throws RemoteException {
+ @UserIdInt int userId) throws RemoteException {
mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret, userId);
}
@@ -1982,14 +2015,14 @@
public byte[] startRecoverySession(@NonNull String sessionId,
@NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams,
@NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets,
- int userId) throws RemoteException {
+ @UserIdInt 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)
+ @NonNull List<KeyEntryRecoveryData> applicationKeys, @UserIdInt int userId)
throws RemoteException {
mRecoverableKeyStoreManager.recoverKeys(sessionId, recoveryKeyBlob, applicationKeys,
userId);
@@ -2425,9 +2458,10 @@
}
if (result) {
synchronized (mSeparateChallengeLock) {
- setSeparateProfileChallengeEnabled(userId, true, null);
+ setSeparateProfileChallengeEnabledLocked(userId, true, null);
}
notifyPasswordChanged(userId);
+ notifySeparateProfileChallengeChanged(userId);
}
return result;
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java
new file mode 100644
index 0000000..9a4d051
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import android.security.keystore.AndroidKeyStoreProvider;
+
+import java.security.KeyStoreException;
+import java.security.NoSuchProviderException;
+
+public interface AndroidKeyStoreFactory {
+ KeyStoreProxy getKeyStoreForUid(int uid) throws KeyStoreException, NoSuchProviderException;
+
+ class Impl implements AndroidKeyStoreFactory {
+ @Override
+ public KeyStoreProxy getKeyStoreForUid(int uid)
+ throws KeyStoreException, NoSuchProviderException {
+ return new KeyStoreProxyImpl(AndroidKeyStoreProvider.getKeyStoreForUid(uid));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
index 7c9b395..8103177 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
@@ -40,4 +40,7 @@
/** @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
index ceee381..59132da 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
@@ -52,4 +52,9 @@
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/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
new file mode 100644
index 0000000..00a8203
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.content.Context;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+/**
+ * Task to sync application keys to a remote vault service.
+ *
+ * TODO: implement fully
+ */
+public class KeySyncTask implements Runnable {
+ private static final String TAG = "KeySyncTask";
+
+ private static final String RECOVERY_KEY_ALGORITHM = "AES";
+ private static final int RECOVERY_KEY_SIZE_BITS = 256;
+ private static final int SALT_LENGTH_BYTES = 16;
+ private static final int LENGTH_PREFIX_BYTES = Integer.BYTES;
+ private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256";
+
+ private final Context mContext;
+ private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+ private final int mUserId;
+ private final int mCredentialType;
+ private final String mCredential;
+
+ public static KeySyncTask newInstance(
+ Context context,
+ RecoverableKeyStoreDb recoverableKeyStoreDb,
+ int userId,
+ int credentialType,
+ String credential
+ ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
+ return new KeySyncTask(
+ context.getApplicationContext(),
+ recoverableKeyStoreDb,
+ userId,
+ credentialType,
+ credential);
+ }
+
+ /**
+ * A new task.
+ *
+ * @param recoverableKeyStoreDb Database where the keys are stored.
+ * @param userId The uid of the user whose profile has been unlocked.
+ * @param credentialType The type of credential - i.e., pattern or password.
+ * @param credential The credential, encoded as a {@link String}.
+ */
+ @VisibleForTesting
+ KeySyncTask(
+ Context context,
+ RecoverableKeyStoreDb recoverableKeyStoreDb,
+ int userId,
+ int credentialType,
+ String credential) {
+ mContext = context;
+ mRecoverableKeyStoreDb = recoverableKeyStoreDb;
+ mUserId = userId;
+ mCredentialType = credentialType;
+ mCredential = credential;
+ }
+
+ @Override
+ public void run() {
+ try {
+ syncKeys();
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
+ }
+ }
+
+ private void syncKeys() {
+ byte[] salt = generateSalt();
+ byte[] localLskfHash = hashCredentials(salt, mCredential);
+
+ // TODO: decrypt local wrapped application keys, ready for sync
+
+ SecretKey recoveryKey;
+ try {
+ recoveryKey = generateRecoveryKey();
+ } catch (NoSuchAlgorithmException e) {
+ Log.wtf("AES should never be unavailable", e);
+ return;
+ }
+
+ // TODO: encrypt each application key with recovery key
+
+ PublicKey vaultKey = getVaultPublicKey();
+
+ // TODO: construct vault params and vault metadata
+ byte[] vaultParams = {};
+
+ byte[] locallyEncryptedRecoveryKey;
+ try {
+ locallyEncryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey(
+ vaultKey,
+ localLskfHash,
+ vaultParams,
+ recoveryKey);
+ } catch (NoSuchAlgorithmException e) {
+ Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e);
+ return;
+ } catch (InvalidKeyException e) {
+ Log.e(TAG,"Could not encrypt with recovery key", e);
+ return;
+ }
+
+ // TODO: send RECOVERABLE_KEYSTORE_SNAPSHOT intent
+ }
+
+ private PublicKey getVaultPublicKey() {
+ // TODO: fill this in
+ throw new UnsupportedOperationException("TODO: get vault public key.");
+ }
+
+ /**
+ * The UI best suited to entering the given lock screen. This is synced with the vault so the
+ * user can be shown the same UI when recovering the vault on another device.
+ *
+ * @return The format - either pattern, pin, or password.
+ */
+ @VisibleForTesting
+ @KeyStoreRecoveryMetadata.LockScreenUiFormat static int getUiFormat(
+ int credentialType, String credential) {
+ if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
+ return KeyStoreRecoveryMetadata.TYPE_PATTERN;
+ } else if (isPin(credential)) {
+ return KeyStoreRecoveryMetadata.TYPE_PIN;
+ } else {
+ return KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+ }
+ }
+
+ /**
+ * Generates a salt to include with the lock screen hash.
+ *
+ * @return The salt.
+ */
+ private byte[] generateSalt() {
+ byte[] salt = new byte[SALT_LENGTH_BYTES];
+ new SecureRandom().nextBytes(salt);
+ return salt;
+ }
+
+ /**
+ * Returns {@code true} if {@code credential} looks like a pin.
+ */
+ @VisibleForTesting
+ static boolean isPin(@NonNull String credential) {
+ int length = credential.length();
+ for (int i = 0; i < length; i++) {
+ if (!Character.isDigit(credential.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Hashes {@code credentials} with the given {@code salt}.
+ *
+ * @return The SHA-256 hash.
+ */
+ @VisibleForTesting
+ static byte[] hashCredentials(byte[] salt, String credentials) {
+ byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
+ ByteBuffer byteBuffer = ByteBuffer.allocate(
+ salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ byteBuffer.putInt(salt.length);
+ byteBuffer.put(salt);
+ byteBuffer.putInt(credentialsBytes.length);
+ byteBuffer.put(credentialsBytes);
+ byte[] bytes = byteBuffer.array();
+
+ try {
+ return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes);
+ } catch (NoSuchAlgorithmException e) {
+ // Impossible, SHA-256 must be supported on Android.
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
+ keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
+ return keyGenerator.generateKey();
+ }
+}
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 37aeb3a..4597fad 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 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 @@
*/
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;
@@ -50,6 +54,8 @@
"V1 encrypted_application_key".getBytes(StandardCharsets.UTF_8);
private static final byte[] RECOVERY_CLAIM_HEADER =
"V1 KF_claim".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] RECOVERY_RESPONSE_HEADER =
+ "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8);
@@ -70,7 +76,7 @@
*
* @hide
*/
- public byte[] thmEncryptRecoveryKey(
+ public static byte[] thmEncryptRecoveryKey(
PublicKey publicKey,
byte[] lockScreenHash,
byte[] vaultParams,
@@ -112,7 +118,8 @@
* @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable.
* @throws InvalidKeyException if the hash cannot be used to encrypt for some reason.
*/
- private static byte[] locallyEncryptRecoveryKey(byte[] lockScreenHash, SecretKey recoveryKey)
+ @VisibleForTesting
+ static byte[] locallyEncryptRecoveryKey(byte[] lockScreenHash, SecretKey recoveryKey)
throws NoSuchAlgorithmException, InvalidKeyException {
return SecureBox.encrypt(
/*theirPublicKey=*/ null,
@@ -199,6 +206,28 @@
}
/**
+ * Decrypts response from recovery claim, returning the locally encrypted key.
+ *
+ * @param keyClaimant The key claimant, used by the remote service to encrypt the response.
+ * @param vaultParams Vault params associated with the claim.
+ * @param encryptedResponse The encrypted response.
+ * @return The locally encrypted recovery key.
+ * @throws NoSuchAlgorithmException if any SecureBox algorithm is not present.
+ * @throws InvalidKeyException if the {@code keyClaimant} could not be used to decrypt.
+ * @throws AEADBadTagException if the message has been tampered with or was encrypted with a
+ * different key.
+ */
+ public static byte[] decryptRecoveryClaimResponse(
+ byte[] keyClaimant, byte[] vaultParams, byte[] encryptedResponse)
+ throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+ return SecureBox.decrypt(
+ /*ourPrivateKey=*/ null,
+ /*sharedSecret=*/ keyClaimant,
+ /*header=*/ concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*encryptedPayload=*/ encryptedResponse);
+ }
+
+ /**
* Decrypts a recovery key, after having retrieved it from a remote server.
*
* @param lskfHash The lock screen hash associated with the key.
@@ -237,6 +266,21 @@
}
/**
+ * 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 0000000..0f17294
--- /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
index 074c596..24f3f65 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -18,16 +18,14 @@
import android.app.KeyguardManager;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Environment;
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.File;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -64,8 +62,6 @@
private static final String KEY_ALGORITHM = "AES";
private static final int KEY_SIZE_BITS = 256;
- private static final String SHARED_PREFS_KEY_GENERATION_ID = "generationId";
- private static final String SHARED_PREFS_PATH = "/system/recoverablekeystore/platform_keys.xml";
private static final String KEY_ALIAS_PREFIX =
"com.android.server.locksettings.recoverablekeystore/platform/";
private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt";
@@ -74,7 +70,7 @@
private final Context mContext;
private final KeyStoreProxy mKeyStore;
- private final SharedPreferences mSharedPreferences;
+ private final RecoverableKeyStoreDb mDatabase;
private final int mUserId;
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
@@ -92,17 +88,14 @@
*
* @hide
*/
- public static PlatformKeyManager getInstance(Context context, int userId)
+ public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database, int userId)
throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
context = context.getApplicationContext();
- File sharedPreferencesFile = new File(
- Environment.getDataDirectory().getAbsoluteFile(), SHARED_PREFS_PATH);
- sharedPreferencesFile.mkdirs();
PlatformKeyManager keyManager = new PlatformKeyManager(
userId,
context,
new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()),
- context.getSharedPreferences(sharedPreferencesFile, Context.MODE_PRIVATE));
+ database);
keyManager.init();
return keyManager;
}
@@ -112,11 +105,11 @@
int userId,
Context context,
KeyStoreProxy keyStore,
- SharedPreferences sharedPreferences) {
+ RecoverableKeyStoreDb database) {
mUserId = userId;
mKeyStore = keyStore;
mContext = context;
- mSharedPreferences = sharedPreferences;
+ mDatabase = database;
}
/**
@@ -127,7 +120,11 @@
* @hide
*/
public int getGenerationId() {
- return mSharedPreferences.getInt(getGenerationIdKey(), 1);
+ int generationId = mDatabase.getPlatformKeyGenerationId(mUserId);
+ if (generationId == -1) {
+ return 1;
+ }
+ return generationId;
}
/**
@@ -150,9 +147,9 @@
* @hide
*/
public void regenerate() throws NoSuchAlgorithmException, KeyStoreException {
- int generationId = getGenerationId();
- generateAndLoadKey(generationId + 1);
- setGenerationId(generationId + 1);
+ int nextId = getGenerationId() + 1;
+ generateAndLoadKey(nextId);
+ setGenerationId(nextId);
}
/**
@@ -252,14 +249,7 @@
* Sets the current generation ID to {@code generationId}.
*/
private void setGenerationId(int generationId) {
- mSharedPreferences.edit().putInt(getGenerationIdKey(), generationId).commit();
- }
-
- /**
- * Returns the current user's generation ID key in the shared preferences.
- */
- private String getGenerationIdKey() {
- return SHARED_PREFS_KEY_GENERATION_ID + "/" + mUserId;
+ mDatabase.setPlatformKeyGenerationId(mUserId, generationId);
}
/**
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 54deec2..bb40fd8 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
@@ -16,16 +16,18 @@
package com.android.server.locksettings.recoverablekeystore;
-import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.util.Log;
-import java.io.IOException;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
import java.security.InvalidKeyException;
+import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
+import java.security.NoSuchProviderException;
+import java.util.Locale;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -42,39 +44,39 @@
*/
public class RecoverableKeyGenerator {
private static final String TAG = "RecoverableKeyGenerator";
+
+ private static final int RESULT_CANNOT_INSERT_ROW = -1;
private static final String KEY_GENERATOR_ALGORITHM = "AES";
private static final int KEY_SIZE_BITS = 256;
/**
* A new {@link RecoverableKeyGenerator} instance.
*
- * @param platformKey Secret key used to wrap generated keys before persisting to disk.
- * @param recoverableKeyStorage Class that manages persisting wrapped keys to disk.
* @throws NoSuchAlgorithmException if "AES" key generation or "AES/GCM/NoPadding" cipher is
* unavailable. Should never happen.
*
* @hide
*/
- public static RecoverableKeyGenerator newInstance(
- 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;
}
/**
@@ -84,50 +86,72 @@
* 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 userId The user ID of the profile to which the calling app belongs.
+ * @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 userId, 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(userId, uid, alias, wrappedKey);
- mRecoverableKeyStorage.persistToDisk(alias, wrappedKey);
+ if (result == RESULT_CANNOT_INSERT_ROW) {
+ // Attempt to clean up
+ try {
+ keyStore.deleteEntry(alias);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, String.format(Locale.US,
+ "Could not delete recoverable key (%d, %s) from "
+ + "AndroidKeyStore after error writing to database.", uid, alias),
+ e);
+ }
- try {
- // Reload from the keystore, so that the caller is only provided with the handle of the
- // key, not the raw key material.
- return mRecoverableKeyStorage.loadFromAndroidKeyStore(alias);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(
- "Impossible: NoSuchAlgorithmException when attempting to retrieve a key "
- + "that has only just been stored in AndroidKeyStore.", e);
+ throw new RecoverableKeyStorageException(
+ String.format(
+ Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java
deleted file mode 100644
index 6a189ef..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import android.security.keystore.KeyProtection;
-
-import java.io.IOException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
-
-import javax.crypto.SecretKey;
-
-/**
- * Stores wrapped keys to disk, so they can be synced on the next screen unlock event.
- *
- * @hide
- */
-public interface RecoverableKeyStorage {
-
- /**
- * Writes {@code wrappedKey} to disk, keyed by the application's uid and the {@code alias}.
- *
- * @throws IOException if an error occurred writing to disk.
- *
- * @hide
- */
- void persistToDisk(String alias, WrappedKey wrappedKey) throws IOException;
-
- /**
- * Imports {@code key} into AndroidKeyStore, keyed by the application's uid and
- * the {@code alias}.
- *
- * @param alias The alias of the key.
- * @param key The key.
- * @param keyProtection Protection params denoting what the key can be used for. (e.g., what
- * Cipher modes, whether for encrpyt/decrypt or signing, etc.)
- * @throws KeyStoreException if an error occurred loading the key into the AndroidKeyStore.
- *
- * @hide
- */
- void importIntoAndroidKeyStore(String alias, SecretKey key, KeyProtection keyProtection) throws
- KeyStoreException;
-
- /**
- * Loads a key handle from AndroidKeyStore.
- *
- * @param alias Alias of the key to load.
- * @return The key handle.
- * @throws KeyStoreException if an error occurred loading the key from AndroidKeyStore.
- *
- * @hide
- */
- SecretKey loadFromAndroidKeyStore(String alias) throws KeyStoreException,
- NoSuchAlgorithmException,
- UnrecoverableEntryException;
-
- /**
- * Removes the entry with the given {@code alias} from AndroidKeyStore.
- *
- * @throws KeyStoreException if an error occurred deleting the key from AndroidKeyStore.
- *
- * @hide
- */
- void removeFromAndroidKeyStore(String alias) throws KeyStoreException;
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java
new file mode 100644
index 0000000..f9d28f1
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+/**
+ * Error thrown when there was a problem writing or reading recoverable key information to or from
+ * storage.
+ *
+ * <p>Storage is typically
+ * {@link com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb} or
+ * AndroidKeyStore.
+ */
+public class RecoverableKeyStorageException extends Exception {
+ public RecoverableKeyStorageException(String message) {
+ super(message);
+ }
+
+ public RecoverableKeyStorageException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java
deleted file mode 100644
index d4dede1..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import android.security.keystore.AndroidKeyStoreProvider;
-import android.security.keystore.KeyProtection;
-
-import java.io.IOException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.UnrecoverableEntryException;
-
-import javax.crypto.SecretKey;
-
-/**
- * Implementation of {@link RecoverableKeyStorage} for a specific application.
- *
- * <p>Persists wrapped keys to disk, and loads raw keys into AndroidKeyStore.
- *
- * @hide
- */
-public class RecoverableKeyStorageImpl implements RecoverableKeyStorage {
- private final KeyStore mKeyStore;
-
- /**
- * A new instance, storing recoverable keys for the given {@code userId}.
- *
- * @throws KeyStoreException if unable to load AndroidKeyStore.
- * @throws NoSuchProviderException if AndroidKeyStore is not in this version of Android. Should
- * never occur.
- *
- * @hide
- */
- public static RecoverableKeyStorageImpl newInstance(int userId) throws KeyStoreException,
- NoSuchProviderException {
- KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(userId);
- return new RecoverableKeyStorageImpl(keyStore);
- }
-
- private RecoverableKeyStorageImpl(KeyStore keyStore) {
- mKeyStore = keyStore;
- }
-
- /**
- * Writes {@code wrappedKey} to disk, keyed by the application's uid and the {@code alias}.
- *
- * @throws IOException if an error occurred writing to disk.
- *
- * @hide
- */
- @Override
- public void persistToDisk(String alias, WrappedKey wrappedKey) throws IOException {
- // TODO(robertberry) Add implementation.
- throw new UnsupportedOperationException();
- }
-
- /**
- * Imports {@code key} into the application's AndroidKeyStore, keyed by {@code alias}.
- *
- * @param alias The alias of the key.
- * @param key The key.
- * @param keyProtection Protection params denoting what the key can be used for. (e.g., what
- * Cipher modes, whether for encrpyt/decrypt or signing, etc.)
- * @throws KeyStoreException if an error occurred loading the key into the AndroidKeyStore.
- *
- * @hide
- */
- @Override
- public void importIntoAndroidKeyStore(String alias, SecretKey key, KeyProtection keyProtection)
- throws KeyStoreException {
- mKeyStore.setEntry(alias, new KeyStore.SecretKeyEntry(key), keyProtection);
- }
-
- /**
- * Loads a key handle from the application's AndroidKeyStore.
- *
- * @param alias Alias of the key to load.
- * @return The key handle.
- * @throws KeyStoreException if an error occurred loading the key from AndroidKeyStore.
- *
- * @hide
- */
- @Override
- public SecretKey loadFromAndroidKeyStore(String alias)
- throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
- return ((SecretKey) mKeyStore.getKey(alias, /*password=*/ null));
- }
-
- /**
- * Removes the entry with the given {@code alias} from the application's AndroidKeyStore.
- *
- * @throws KeyStoreException if an error occurred deleting the key from AndroidKeyStore.
- *
- * @hide
- */
- @Override
- public void removeFromAndroidKeyStore(String alias) throws KeyStoreException {
- mKeyStore.deleteEntry(alias);
- }
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index e459f28..cfeaaf8 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Context;
import android.os.Binder;
import android.os.RemoteException;
@@ -28,11 +29,26 @@
import android.security.recoverablekeystore.KeyStoreRecoveryData;
import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.crypto.AEADBadTagException;
/**
* Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact
@@ -41,10 +57,13 @@
* @hide
*/
public class RecoverableKeyStoreManager {
- private static final String TAG = "RecoverableKeyStoreManager";
-
+ private static final String TAG = "RecoverableKeyStoreMgr";
private static RecoverableKeyStoreManager mInstance;
- private Context mContext;
+
+ private final Context mContext;
+ private final RecoverableKeyStoreDb mDatabase;
+ private final RecoverySessionStorage mRecoverySessionStorage;
+ private final ExecutorService mExecutorService;
/**
* Returns a new or existing instance.
@@ -53,14 +72,26 @@
*/
public static synchronized RecoverableKeyStoreManager getInstance(Context mContext) {
if (mInstance == null) {
- mInstance = new RecoverableKeyStoreManager(mContext);
+ RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(mContext);
+ mInstance = new RecoverableKeyStoreManager(
+ mContext.getApplicationContext(),
+ db,
+ new RecoverySessionStorage(),
+ Executors.newSingleThreadExecutor());
}
return mInstance;
}
@VisibleForTesting
- RecoverableKeyStoreManager(Context context) {
+ RecoverableKeyStoreManager(
+ Context context,
+ RecoverableKeyStoreDb recoverableKeyStoreDb,
+ RecoverySessionStorage recoverySessionStorage,
+ ExecutorService executorService) {
mContext = context;
+ mDatabase = recoverableKeyStoreDb;
+ mRecoverySessionStorage = recoverySessionStorage;
+ mExecutorService = executorService;
}
public int initRecoveryService(
@@ -77,7 +108,7 @@
* @return recovery data
* @hide
*/
- public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
+ public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
throws RemoteException {
checkRecoverKeyStorePermission();
final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
@@ -100,6 +131,24 @@
RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY);
}
+ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Gets recovery snapshot versions for all accounts. Note that snapshot may have 0 application
+ * keys, but it still needs to be synced, if previous versions were not empty.
+ *
+ * @return Map from Recovery agent account to snapshot version.
+ */
+ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions(int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
public void setServerParameters(long serverParameters, int userId) throws RemoteException {
checkRecoverKeyStorePermission();
throw new UnsupportedOperationException();
@@ -113,6 +162,21 @@
}
/**
+ * Gets recovery status for keys {@code packageName}.
+ *
+ * @param packageName which recoverable keys statuses will be returned
+ * @return Map from KeyStore alias to recovery status
+ */
+ public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName, int userId)
+ throws RemoteException {
+ // Any application should be able to check status for its own keys.
+ // If caller is a recovery agent it can check statuses for other packages, but
+ // only for recoverable keys it manages.
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Sets recovery secrets list used by all recovery agents for given {@code userId}
*
* @hide
@@ -130,7 +194,7 @@
* @return secret types
* @hide
*/
- public int[] getRecoverySecretTypes(int userId) throws RemoteException {
+ public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException {
checkRecoverKeyStorePermission();
throw new UnsupportedOperationException();
}
@@ -141,7 +205,7 @@
* @return secret types
* @hide
*/
- public int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
+ public @NonNull int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
checkRecoverKeyStorePermission();
throw new UnsupportedOperationException();
}
@@ -161,10 +225,16 @@
/**
* Initializes recovery session.
*
- * @return recovery claim
+ * @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. For now only a single secret is supported.
+ * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
+ *
* @hide
*/
- public byte[] startRecoverySession(
+ public @NonNull byte[] startRecoverySession(
@NonNull String sessionId,
@NonNull byte[] verifierPublicKey,
@NonNull byte[] vaultParams,
@@ -173,32 +243,168 @@
int userId)
throws RemoteException {
checkRecoverKeyStorePermission();
- throw new UnsupportedOperationException();
+
+ 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, vaultParams));
+
+ 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);
+ }
}
+ /**
+ * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
+ * service.
+ *
+ * <p>TODO: should also load into AndroidKeyStore.
+ *
+ * @param sessionId The session ID used to generate the claim. See
+ * {@link #startRecoverySession(String, byte[], byte[], byte[], List, int)}.
+ * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
+ * service.
+ * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
+ * were wrapped with the recovery key.
+ * @param uid The uid of the recovery agent.
+ * @throws RemoteException if an error occurred recovering the keys.
+ */
public void recoverKeys(
@NonNull String sessionId,
- @NonNull byte[] recoveryKeyBlob,
+ @NonNull byte[] encryptedRecoveryKey,
@NonNull List<KeyEntryRecoveryData> applicationKeys,
- int userId)
+ int uid)
throws RemoteException {
checkRecoverKeyStorePermission();
- throw new UnsupportedOperationException();
+
+ RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
+ if (sessionEntry == null) {
+ throw new RemoteException(String.format(Locale.US,
+ "User %d does not have pending session '%s'", uid, sessionId));
+ }
+
+ try {
+ byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
+ recoverApplicationKeys(recoveryKey, applicationKeys);
+ } finally {
+ sessionEntry.destroy();
+ mRecoverySessionStorage.remove(uid);
+ }
}
- /** This function can only be used inside LockSettingsService. */
+ private byte[] decryptRecoveryKey(
+ RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
+ throws RemoteException {
+ try {
+ byte[] locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
+ sessionEntry.getKeyClaimant(),
+ sessionEntry.getVaultParams(),
+ encryptedClaimResponse);
+ return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
+ } catch (InvalidKeyException | AEADBadTagException e) {
+ throw new RemoteException(
+ "Failed to decrypt recovery key",
+ e,
+ /*enableSuppression=*/ true,
+ /*writeableStackTrace=*/ true);
+ } 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);
+ }
+ }
+
+ /**
+ * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
+ *
+ * <p>TODO: and load them into store?
+ *
+ * @throws RemoteException if an error occurred decrypting the keys.
+ */
+ private void recoverApplicationKeys(
+ @NonNull byte[] recoveryKey,
+ @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException {
+ for (KeyEntryRecoveryData applicationKey : applicationKeys) {
+ String alias = new String(applicationKey.getAlias(), StandardCharsets.UTF_8);
+ byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
+
+ try {
+ // TODO: put decrypted key material in appropriate AndroidKeyStore
+ KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
+ } 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 (InvalidKeyException | AEADBadTagException e) {
+ throw new RemoteException(
+ "Failed to recover key with alias '" + alias + "'",
+ e,
+ /*enableSuppression=*/ true,
+ /*writeableStackTrace=*/ true);
+ }
+ }
+ }
+
+ /**
+ * This function can only be used inside LockSettingsService.
+ *
+ * @param storedHashType from {@Code CredentialHash}
+ * @param credential - unencrypted String. Password length should be at most 16 symbols {@code
+ * mPasswordMaxLength}
+ * @param userId for user who just unlocked the device.
+ * @hide
+ */
public void lockScreenSecretAvailable(
- @KeyStoreRecoveryMetadata.LockScreenUiFormat int type,
- String unencryptedPassword,
- int userId) {
- // TODO: compute SHA256 or Argon2id depending on secret type.
- throw new UnsupportedOperationException();
+ int storedHashType, @NonNull String credential, int userId) {
+ // So as not to block the critical path unlocking the phone, defer to another thread.
+ try {
+ mExecutorService.execute(KeySyncTask.newInstance(
+ mContext, mDatabase, userId, storedHashType, credential));
+ } catch (NoSuchAlgorithmException e) {
+ Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, "Key store error encountered during recoverable key sync", e);
+ } catch (InsecureUserException e) {
+ Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
+ }
}
/** This function can only be used inside LockSettingsService. */
public void lockScreenSecretChanged(
@KeyStoreRecoveryMetadata.LockScreenUiFormat int type,
- @Nullable String unencryptedPassword,
+ @Nullable String credential,
int userId) {
throw new UnsupportedOperationException();
}
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 d8a2d31..801d4de 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -230,7 +230,7 @@
* @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
+ * cannot be validated, or if the payload is not a valid SecureBox V2 payload.
* @hide
*/
public static byte[] decrypt(
@@ -244,12 +244,14 @@
throw new IllegalArgumentException("Both the private key and shared secret are empty");
}
header = emptyByteArrayIfNull(header);
- encryptedPayload = emptyByteArrayIfNull(encryptedPayload);
+ if (encryptedPayload == null) {
+ throw new NullPointerException("Encrypted payload must not be null.");
+ }
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");
+ throw new AEADBadTagException("The payload was not encrypted by SecureBox v2");
}
byte[] senderPublicKeyBytes;
@@ -271,12 +273,13 @@
return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
}
- private static byte[] readEncryptedPayload(ByteBuffer buffer, int length) {
+ private static byte[] readEncryptedPayload(ByteBuffer buffer, int length)
+ throws AEADBadTagException {
byte[] output = new byte[length];
try {
buffer.get(output);
} catch (BufferUnderflowException ex) {
- throw new IllegalArgumentException("The encrypted payload is too short");
+ throw new AEADBadTagException("The encrypted payload is too short");
}
return output;
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index 3644d36..ed570c3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -39,6 +39,7 @@
public class RecoverableKeyStoreDb {
private static final String TAG = "RecoverableKeyStoreDb";
private static final int IDLE_TIMEOUT_SECONDS = 30;
+ private static final int LAST_SYNCED_AT_UNSYNCED = -1;
private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper;
@@ -61,6 +62,7 @@
/**
* Inserts a key into the database.
*
+ * @param userId The uid of the profile the application is running under.
* @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.
@@ -68,14 +70,15 @@
*
* @hide
*/
- public long insertKey(int uid, String alias, WrappedKey wrappedKey) {
+ public long insertKey(int userId, int uid, String alias, WrappedKey wrappedKey) {
SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
+ values.put(KeysEntry.COLUMN_NAME_USER_ID, userId);
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_LAST_SYNCED_AT, LAST_SYNCED_AT_UNSYNCED);
values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId());
return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
}
@@ -130,16 +133,16 @@
}
/**
- * Returns all keys for the given {@code uid} and {@code platformKeyGenerationId}.
+ * Returns all keys for the given {@code userId} and {@code platformKeyGenerationId}.
*
- * @param uid User id of the profile to which all the keys are associated.
+ * @param userId 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) {
+ public Map<String, WrappedKey> getAllKeys(int userId, int platformKeyGenerationId) {
SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
String[] projection = {
KeysEntry._ID,
@@ -147,10 +150,10 @@
KeysEntry.COLUMN_NAME_WRAPPED_KEY,
KeysEntry.COLUMN_NAME_ALIAS};
String selection =
- KeysEntry.COLUMN_NAME_UID + " = ? AND "
+ KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?";
String[] selectionArguments = {
- Integer.toString(uid), Integer.toString(platformKeyGenerationId) };
+ Integer.toString(userId), Integer.toString(platformKeyGenerationId) };
try (
Cursor cursor = db.query(
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index b6c168f..dcd1827 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -29,6 +29,11 @@
static final String TABLE_NAME = "keys";
/**
+ * The user id of the profile the application is running under.
+ */
+ static final String COLUMN_NAME_USER_ID = "user_id";
+
+ /**
* The uid of the application that generated the key.
*/
static final String COLUMN_NAME_UID = "uid";
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index 6868203..b017fbb 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -17,6 +17,7 @@
private static final String SQL_CREATE_KEYS_ENTRY =
"CREATE TABLE " + KeysEntry.TABLE_NAME + "( "
+ KeysEntry._ID + " INTEGER PRIMARY KEY,"
+ + KeysEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+ KeysEntry.COLUMN_NAME_UID + " INTEGER,"
+ KeysEntry.COLUMN_NAME_ALIAS + " TEXT,"
+ KeysEntry.COLUMN_NAME_NONCE + " BLOB,"
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 0000000..f7633e4
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 byte[] mVaultParams;
+ private final String mSessionId;
+
+ /**
+ * @hide
+ */
+ public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant, byte[] vaultParams) {
+ mLskfHash = lskfHash;
+ mSessionId = sessionId;
+ mKeyClaimant = keyClaimant;
+ mVaultParams = vaultParams;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Returns the vault params associated with the session.
+ *
+ * @hide
+ */
+ public byte[] getVaultParams() {
+ return mVaultParams;
+ }
+
+ /**
+ * 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/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 3af5265..db61ef5 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -873,6 +873,21 @@
}
}
+ @Override
+ public long getUidStats(int uid, int type) {
+ return nativeGetUidStat(uid, type);
+ }
+
+ @Override
+ public long getIfaceStats(String iface, int type) {
+ return nativeGetIfaceStat(iface, type);
+ }
+
+ @Override
+ public long getTotalStats(int type) {
+ return nativeGetTotalStat(type);
+ }
+
/**
* Update {@link NetworkStatsRecorder} and {@link #mGlobalAlertBytes} to
* reflect current {@link #mPersistThreshold} value. Always defers to
@@ -1626,4 +1641,15 @@
return getGlobalLong(NETSTATS_UID_TAG_PERSIST_BYTES, def);
}
}
+
+ private static int TYPE_RX_BYTES;
+ private static int TYPE_RX_PACKETS;
+ private static int TYPE_TX_BYTES;
+ private static int TYPE_TX_PACKETS;
+ private static int TYPE_TCP_RX_PACKETS;
+ private static int TYPE_TCP_TX_PACKETS;
+
+ private static native long nativeGetTotalStat(int type);
+ private static native long nativeGetIfaceStat(String iface, int type);
+ private static native long nativeGetUidStat(int uid, int type);
}
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 5b3d1ec..2a2ff06 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -178,14 +178,21 @@
}
}
+ /** Currently MasterClearConfirm will call isOemUnlockAllowed()
+ * to sync PersistentDataBlockOemUnlockAllowedBit which
+ * is needed before factory reset
+ * TODO: Figure out better place to run sync e.g. adding new API
+ */
@Override
public boolean isOemUnlockAllowed() {
enforceOemUnlockReadPermission();
final long token = Binder.clearCallingIdentity();
try {
- return mOemLock.isOemUnlockAllowedByCarrier() &&
- mOemLock.isOemUnlockAllowedByDevice();
+ boolean allowed = mOemLock.isOemUnlockAllowedByCarrier()
+ && mOemLock.isOemUnlockAllowedByDevice();
+ setPersistentDataBlockOemUnlockAllowedBit(allowed);
+ return allowed;
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -213,7 +220,8 @@
final PersistentDataBlockManager pdbm = (PersistentDataBlockManager)
mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
// if mOemLock is PersistentDataBlockLock, then the bit should have already been set
- if (pdbm != null && !(mOemLock instanceof PersistentDataBlockLock)) {
+ if (pdbm != null && !(mOemLock instanceof PersistentDataBlockLock)
+ && pdbm.getOemUnlockEnabled() != allowed) {
Slog.i(TAG, "Update OEM Unlock bit in pst partition to " + allowed);
pdbm.setOemUnlockEnabled(allowed);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 03cd4f1..768eb8f 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.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.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 @@
/**
* 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 @@
}
@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 @@
}
}
- @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;
+ // 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;
}
-
- 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);
+ final Intent callBackIntent = new Intent(
+ ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK);
+ if (target != null) {
+ callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
}
- return false;
+ 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/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index c40d1fa..01f3c57 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -640,9 +640,9 @@
if (globalSearchPickerPackage != null
&& doesPackageSupportRuntimePermissions(globalSearchPickerPackage)) {
grantRuntimePermissions(globalSearchPickerPackage,
- MICROPHONE_PERMISSIONS, true, userId);
+ MICROPHONE_PERMISSIONS, false, userId);
grantRuntimePermissions(globalSearchPickerPackage,
- LOCATION_PERMISSIONS, true, userId);
+ LOCATION_PERMISSIONS, false, userId);
}
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 8ee26f29..e5a23ea 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -16,6 +16,7 @@
package com.android.server.power;
+import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
@@ -27,6 +28,7 @@
import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;
+import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -82,6 +84,7 @@
private static final int MSG_BROADCAST = 2;
private static final int MSG_WIRELESS_CHARGING_STARTED = 3;
private static final int MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED = 4;
+ private static final int MSG_PROFILE_TIMED_OUT = 5;
private final Object mLock = new Object();
@@ -93,6 +96,7 @@
private final ActivityManagerInternal mActivityManagerInternal;
private final InputManagerInternal mInputManagerInternal;
private final InputMethodManagerInternal mInputMethodManagerInternal;
+ private final TrustManager mTrustManager;
private final NotifierHandler mHandler;
private final Intent mScreenOnIntent;
@@ -138,6 +142,7 @@
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
+ mTrustManager = mContext.getSystemService(TrustManager.class);
mHandler = new NotifierHandler(looper);
mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
@@ -559,6 +564,16 @@
mHandler.sendMessage(msg);
}
+ /**
+ * Called when profile screen lock timeout has expired.
+ */
+ public void onProfileTimeout(@UserIdInt int userId) {
+ final Message msg = mHandler.obtainMessage(MSG_PROFILE_TIMED_OUT);
+ msg.setAsynchronous(true);
+ msg.arg1 = userId;
+ mHandler.sendMessage(msg);
+ }
+
private void updatePendingBroadcastLocked() {
if (!mBroadcastInProgress
&& mPendingInteractiveState != INTERACTIVE_STATE_UNKNOWN
@@ -710,28 +725,33 @@
mSuspendBlocker.release();
}
+ private void lockProfile(@UserIdInt int userId) {
+ mTrustManager.setDeviceLockedForUser(userId, true /*locked*/);
+ }
+
private final class NotifierHandler extends Handler {
+
public NotifierHandler(Looper looper) {
super(looper, null, true /*async*/);
}
-
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_USER_ACTIVITY:
sendUserActivity();
break;
-
case MSG_BROADCAST:
sendNextBroadcast();
break;
-
case MSG_WIRELESS_CHARGING_STARTED:
playWirelessChargingStartedSound();
break;
case MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED:
sendBrightnessBoostChangedBroadcast();
break;
+ case MSG_PROFILE_TIMED_OUT:
+ lockProfile(msg.arg1);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 7f1a534..0b590bc 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -16,9 +16,10 @@
package com.android.server.power;
-import android.Manifest;
import android.annotation.IntDef;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.SynchronousUserSwitchObserver;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -410,12 +411,12 @@
private boolean mDozeAfterScreenOffConfig;
// The minimum screen off timeout, in milliseconds.
- private int mMinimumScreenOffTimeoutConfig;
+ private long mMinimumScreenOffTimeoutConfig;
// The screen dim duration, in milliseconds.
// This is subtracted from the end of the screen off timeout so the
// minimum screen off timeout should be longer than this.
- private int mMaximumScreenDimDurationConfig;
+ private long mMaximumScreenDimDurationConfig;
// The maximum screen dim time expressed as a ratio relative to the screen
// off timeout. If the screen off timeout is very short then we want the
@@ -427,14 +428,14 @@
private boolean mSupportsDoubleTapWakeConfig;
// The screen off timeout setting value in milliseconds.
- private int mScreenOffTimeoutSetting;
+ private long mScreenOffTimeoutSetting;
// The sleep timeout setting value in milliseconds.
- private int mSleepTimeoutSetting;
+ private long mSleepTimeoutSetting;
// The maximum allowable screen off timeout according to the device
// administration policy. Overrides other settings.
- private int mMaximumScreenOffTimeoutFromDeviceAdmin = Integer.MAX_VALUE;
+ private long mMaximumScreenOffTimeoutFromDeviceAdmin = Long.MAX_VALUE;
// The stay on while plugged in setting.
// A bitfield of battery conditions under which to make the screen stay on.
@@ -555,6 +556,46 @@
// True if we are currently in VR Mode.
private boolean mIsVrModeEnabled;
+ private final class ForegroundProfileObserver extends SynchronousUserSwitchObserver {
+ @Override
+ public void onUserSwitching(int newUserId) throws RemoteException {}
+
+ @Override
+ public void onForegroundProfileSwitch(@UserIdInt int newProfileId) throws RemoteException {
+ final long now = SystemClock.uptimeMillis();
+ synchronized(mLock) {
+ mForegroundProfile = newProfileId;
+ maybeUpdateForegroundProfileLastActivityLocked(now);
+ }
+ }
+ }
+
+ // User id corresponding to activity the user is currently interacting with.
+ private @UserIdInt int mForegroundProfile;
+
+ // Per-profile state to track when a profile should be locked.
+ private final SparseArray<ProfilePowerState> mProfilePowerState = new SparseArray<>();
+
+ private static final class ProfilePowerState {
+ // Profile user id.
+ final @UserIdInt int mUserId;
+ // Maximum time to lock set by admin.
+ long mScreenOffTimeout;
+ // Like top-level mWakeLockSummary, but only for wake locks that affect current profile.
+ int mWakeLockSummary;
+ // Last user activity that happened in an app running in the profile.
+ long mLastUserActivityTime;
+ // Whether profile has been locked last time it timed out.
+ boolean mLockingNotified;
+
+ public ProfilePowerState(@UserIdInt int userId, long screenOffTimeout) {
+ mUserId = userId;
+ mScreenOffTimeout = screenOffTimeout;
+ // Not accurate but at least won't cause immediate locking of the profile.
+ mLastUserActivityTime = SystemClock.uptimeMillis();
+ }
+ }
+
/**
* All times are in milliseconds. These constants are kept synchronized with the system
* global Settings. Any access to this class or its fields should be done while
@@ -752,6 +793,12 @@
mDisplayManagerInternal.initPowerManagement(
mDisplayPowerCallbacks, mHandler, sensorManager);
+ try {
+ final ForegroundProfileObserver observer = new ForegroundProfileObserver();
+ ActivityManager.getService().registerUserSwitchObserver(observer, TAG);
+ } catch (RemoteException e) {
+ // Shouldn't happen since in-process.
+ }
// Go.
readConfigurationLocked();
@@ -1333,6 +1380,8 @@
return false;
}
+ maybeUpdateForegroundProfileLastActivityLocked(eventTime);
+
if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
if (eventTime > mLastUserActivityTimeNoChangeLights
&& eventTime > mLastUserActivityTime) {
@@ -1360,6 +1409,13 @@
return false;
}
+ private void maybeUpdateForegroundProfileLastActivityLocked(long eventTime) {
+ final ProfilePowerState profile = mProfilePowerState.get(mForegroundProfile);
+ if (profile != null && eventTime > profile.mLastUserActivityTime) {
+ profile.mLastUserActivityTime = eventTime;
+ }
+ }
+
private void wakeUpInternal(long eventTime, String reason, int uid, String opPackageName,
int opUid) {
synchronized (mLock) {
@@ -1648,16 +1704,19 @@
}
}
- // Phase 2: Update display power state.
- boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);
+ // Phase 2: Lock profiles that became inactive/not kept awake.
+ updateProfilesLocked(now);
- // Phase 3: Update dream state (depends on display ready signal).
+ // Phase 3: Update display power state.
+ final boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);
+
+ // Phase 4: Update dream state (depends on display ready signal).
updateDreamLocked(dirtyPhase2, displayBecameReady);
- // Phase 4: Send notifications, if needed.
+ // Phase 5: Send notifications, if needed.
finishWakefulnessChangeIfNeededLocked();
- // Phase 5: Update suspend blocker.
+ // Phase 6: Update suspend blocker.
// Because we might release the last suspend blocker here, we need to make sure
// we finished everything else first!
updateSuspendBlockerLocked();
@@ -1667,6 +1726,29 @@
}
/**
+ * Check profile timeouts and notify profiles that should be locked.
+ */
+ private void updateProfilesLocked(long now) {
+ final int numProfiles = mProfilePowerState.size();
+ for (int i = 0; i < numProfiles; i++) {
+ final ProfilePowerState profile = mProfilePowerState.valueAt(i);
+ if (isProfileBeingKeptAwakeLocked(profile, now)) {
+ profile.mLockingNotified = false;
+ } else if (!profile.mLockingNotified) {
+ profile.mLockingNotified = true;
+ mNotifier.onProfileTimeout(profile.mUserId);
+ }
+ }
+ }
+
+ private boolean isProfileBeingKeptAwakeLocked(ProfilePowerState profile, long now) {
+ return (profile.mLastUserActivityTime + profile.mScreenOffTimeout > now)
+ || (profile.mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0
+ || (mProximityPositive &&
+ (profile.mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0);
+ }
+
+ /**
* Updates the value of mIsPowered.
* Sets DIRTY_IS_POWERED if a change occurred.
*/
@@ -1800,60 +1882,28 @@
if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS)) != 0) {
mWakeLockSummary = 0;
+ final int numProfiles = mProfilePowerState.size();
+ for (int i = 0; i < numProfiles; i++) {
+ mProfilePowerState.valueAt(i).mWakeLockSummary = 0;
+ }
+
final int numWakeLocks = mWakeLocks.size();
for (int i = 0; i < numWakeLocks; i++) {
final WakeLock wakeLock = mWakeLocks.get(i);
- switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
- case PowerManager.PARTIAL_WAKE_LOCK:
- if (!wakeLock.mDisabled) {
- // We only respect this if the wake lock is not disabled.
- mWakeLockSummary |= WAKE_LOCK_CPU;
- }
- break;
- case PowerManager.FULL_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT;
- break;
- case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_SCREEN_BRIGHT;
- break;
- case PowerManager.SCREEN_DIM_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_SCREEN_DIM;
- break;
- case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_PROXIMITY_SCREEN_OFF;
- break;
- case PowerManager.DOZE_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_DOZE;
- break;
- case PowerManager.DRAW_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_DRAW;
- break;
+ final int wakeLockFlags = getWakeLockSummaryFlags(wakeLock);
+ mWakeLockSummary |= wakeLockFlags;
+ for (int j = 0; j < numProfiles; j++) {
+ final ProfilePowerState profile = mProfilePowerState.valueAt(j);
+ if (wakeLockAffectsUser(wakeLock, profile.mUserId)) {
+ profile.mWakeLockSummary |= wakeLockFlags;
+ }
}
}
- // Cancel wake locks that make no sense based on the current state.
- if (mWakefulness != WAKEFULNESS_DOZING) {
- mWakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW);
- }
- if (mWakefulness == WAKEFULNESS_ASLEEP
- || (mWakeLockSummary & WAKE_LOCK_DOZE) != 0) {
- mWakeLockSummary &= ~(WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM
- | WAKE_LOCK_BUTTON_BRIGHT);
- if (mWakefulness == WAKEFULNESS_ASLEEP) {
- mWakeLockSummary &= ~WAKE_LOCK_PROXIMITY_SCREEN_OFF;
- }
- }
-
- // Infer implied wake locks where necessary based on the current state.
- if ((mWakeLockSummary & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM)) != 0) {
- if (mWakefulness == WAKEFULNESS_AWAKE) {
- mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_STAY_AWAKE;
- } else if (mWakefulness == WAKEFULNESS_DREAMING) {
- mWakeLockSummary |= WAKE_LOCK_CPU;
- }
- }
- if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
- mWakeLockSummary |= WAKE_LOCK_CPU;
+ mWakeLockSummary = adjustWakeLockSummaryLocked(mWakeLockSummary);
+ for (int i = 0; i < numProfiles; i++) {
+ final ProfilePowerState profile = mProfilePowerState.valueAt(i);
+ profile.mWakeLockSummary = adjustWakeLockSummaryLocked(profile.mWakeLockSummary);
}
if (DEBUG_SPEW) {
@@ -1864,6 +1914,72 @@
}
}
+ private int adjustWakeLockSummaryLocked(int wakeLockSummary) {
+ // Cancel wake locks that make no sense based on the current state.
+ if (mWakefulness != WAKEFULNESS_DOZING) {
+ wakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW);
+ }
+ if (mWakefulness == WAKEFULNESS_ASLEEP
+ || (wakeLockSummary & WAKE_LOCK_DOZE) != 0) {
+ wakeLockSummary &= ~(WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM
+ | WAKE_LOCK_BUTTON_BRIGHT);
+ if (mWakefulness == WAKEFULNESS_ASLEEP) {
+ wakeLockSummary &= ~WAKE_LOCK_PROXIMITY_SCREEN_OFF;
+ }
+ }
+
+ // Infer implied wake locks where necessary based on the current state.
+ if ((wakeLockSummary & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM)) != 0) {
+ if (mWakefulness == WAKEFULNESS_AWAKE) {
+ wakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_STAY_AWAKE;
+ } else if (mWakefulness == WAKEFULNESS_DREAMING) {
+ wakeLockSummary |= WAKE_LOCK_CPU;
+ }
+ }
+ if ((wakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+ wakeLockSummary |= WAKE_LOCK_CPU;
+ }
+
+ return wakeLockSummary;
+ }
+
+ /** Get wake lock summary flags that correspond to the given wake lock. */
+ private int getWakeLockSummaryFlags(WakeLock wakeLock) {
+ switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+ case PowerManager.PARTIAL_WAKE_LOCK:
+ if (!wakeLock.mDisabled) {
+ // We only respect this if the wake lock is not disabled.
+ return WAKE_LOCK_CPU;
+ }
+ break;
+ case PowerManager.FULL_WAKE_LOCK:
+ return WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT;
+ case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
+ return WAKE_LOCK_SCREEN_BRIGHT;
+ case PowerManager.SCREEN_DIM_WAKE_LOCK:
+ return WAKE_LOCK_SCREEN_DIM;
+ case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
+ return WAKE_LOCK_PROXIMITY_SCREEN_OFF;
+ case PowerManager.DOZE_WAKE_LOCK:
+ return WAKE_LOCK_DOZE;
+ case PowerManager.DRAW_WAKE_LOCK:
+ return WAKE_LOCK_DRAW;
+ }
+ return 0;
+ }
+
+ private boolean wakeLockAffectsUser(WakeLock wakeLock, @UserIdInt int userId) {
+ if (wakeLock.mWorkSource != null) {
+ for (int k = 0; k < wakeLock.mWorkSource.size(); k++) {
+ final int uid = wakeLock.mWorkSource.get(k);
+ if (userId == UserHandle.getUserId(uid)) {
+ return true;
+ }
+ }
+ }
+ return userId == UserHandle.getUserId(wakeLock.mOwnerUid);
+ }
+
void checkForLongWakeLocks() {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
@@ -1917,10 +2033,11 @@
if (mWakefulness == WAKEFULNESS_AWAKE
|| mWakefulness == WAKEFULNESS_DREAMING
|| mWakefulness == WAKEFULNESS_DOZING) {
- final int sleepTimeout = getSleepTimeoutLocked();
- final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
- final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+ final long sleepTimeout = getSleepTimeoutLocked();
+ final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
+ final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager;
+ final long nextProfileTimeout = getNextProfileTimeoutLocked(now);
mUserActivitySummary = 0;
if (mLastUserActivityTime >= mLastWakeTime) {
@@ -1977,10 +2094,12 @@
nextTimeout = -1;
}
+ if (nextProfileTimeout > 0) {
+ nextTimeout = Math.min(nextTimeout, nextProfileTimeout);
+ }
+
if (mUserActivitySummary != 0 && nextTimeout >= 0) {
- Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT);
- msg.setAsynchronous(true);
- mHandler.sendMessageAtTime(msg, nextTimeout);
+ scheduleUserInactivityTimeout(nextTimeout);
}
} else {
mUserActivitySummary = 0;
@@ -1995,6 +2114,28 @@
}
}
+ private void scheduleUserInactivityTimeout(long timeMs) {
+ final Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageAtTime(msg, timeMs);
+ }
+
+ /**
+ * Finds the next profile timeout time or returns -1 if there are no profiles to be locked.
+ */
+ private long getNextProfileTimeoutLocked(long now) {
+ long nextTimeout = -1;
+ final int numProfiles = mProfilePowerState.size();
+ for (int i = 0; i < numProfiles; i++) {
+ final ProfilePowerState profile = mProfilePowerState.valueAt(i);
+ final long timeout = profile.mLastUserActivityTime + profile.mScreenOffTimeout;
+ if (timeout > now && (nextTimeout == -1 || timeout < nextTimeout)) {
+ nextTimeout = timeout;
+ }
+ }
+ return nextTimeout;
+ }
+
/**
* Called when a user activity timeout has occurred.
* Simply indicates that something about user activity has changed so that the new
@@ -2014,21 +2155,21 @@
}
}
- private int getSleepTimeoutLocked() {
- int timeout = mSleepTimeoutSetting;
+ private long getSleepTimeoutLocked() {
+ final long timeout = mSleepTimeoutSetting;
if (timeout <= 0) {
return -1;
}
return Math.max(timeout, mMinimumScreenOffTimeoutConfig);
}
- private int getScreenOffTimeoutLocked(int sleepTimeout) {
- int timeout = mScreenOffTimeoutSetting;
+ private long getScreenOffTimeoutLocked(long sleepTimeout) {
+ long timeout = mScreenOffTimeoutSetting;
if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) {
timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin);
}
if (mUserActivityTimeoutOverrideFromWindowManager >= 0) {
- timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromWindowManager);
+ timeout = Math.min(timeout, mUserActivityTimeoutOverrideFromWindowManager);
}
if (sleepTimeout >= 0) {
timeout = Math.min(timeout, sleepTimeout);
@@ -2036,9 +2177,9 @@
return Math.max(timeout, mMinimumScreenOffTimeoutConfig);
}
- private int getScreenDimDurationLocked(int screenOffTimeout) {
+ private long getScreenDimDurationLocked(long screenOffTimeout) {
return Math.min(mMaximumScreenDimDurationConfig,
- (int)(screenOffTimeout * mMaximumScreenDimRatioConfig));
+ (long)(screenOffTimeout * mMaximumScreenDimRatioConfig));
}
/**
@@ -2781,9 +2922,27 @@
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, val);
}
- void setMaximumScreenOffTimeoutFromDeviceAdminInternal(int timeMs) {
+ void setMaximumScreenOffTimeoutFromDeviceAdminInternal(@UserIdInt int userId, long timeMs) {
+ if (userId < 0) {
+ Slog.wtf(TAG, "Attempt to set screen off timeout for invalid user: " + userId);
+ return;
+ }
synchronized (mLock) {
- mMaximumScreenOffTimeoutFromDeviceAdmin = timeMs;
+ // System-wide timeout
+ if (userId == UserHandle.USER_SYSTEM) {
+ mMaximumScreenOffTimeoutFromDeviceAdmin = timeMs;
+ } else if (timeMs == Long.MAX_VALUE || timeMs == 0) {
+ mProfilePowerState.delete(userId);
+ } else {
+ final ProfilePowerState profile = mProfilePowerState.get(userId);
+ if (profile != null) {
+ profile.mScreenOffTimeout = timeMs;
+ } else {
+ mProfilePowerState.put(userId, new ProfilePowerState(userId, timeMs));
+ // We need to recalculate wake locks for the new profile state.
+ mDirty |= DIRTY_WAKE_LOCKS;
+ }
+ }
mDirty |= DIRTY_SETTINGS;
updatePowerStateLocked();
}
@@ -2981,7 +3140,7 @@
private boolean isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() {
return mMaximumScreenOffTimeoutFromDeviceAdmin >= 0
- && mMaximumScreenOffTimeoutFromDeviceAdmin < Integer.MAX_VALUE;
+ && mMaximumScreenOffTimeoutFromDeviceAdmin < Long.MAX_VALUE;
}
private void setAttentionLightInternal(boolean on, int color) {
@@ -3325,10 +3484,11 @@
pw.println(" mScreenBrightnessForVrSetting=" + mScreenBrightnessForVrSetting);
pw.println(" mDoubleTapWakeEnabled=" + mDoubleTapWakeEnabled);
pw.println(" mIsVrModeEnabled=" + mIsVrModeEnabled);
+ pw.println(" mForegroundProfile=" + mForegroundProfile);
- final int sleepTimeout = getSleepTimeoutLocked();
- final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
- final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+ final long sleepTimeout = getSleepTimeoutLocked();
+ final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
+ final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
pw.println();
pw.println("Sleep timeout: " + sleepTimeout + " ms");
pw.println("Screen off timeout: " + screenOffTimeout + " ms");
@@ -3373,6 +3533,23 @@
mBatterySaverPolicy.dump(pw);
+ pw.println();
+ final int numProfiles = mProfilePowerState.size();
+ pw.println("Profile power states: size=" + numProfiles);
+ for (int i = 0; i < numProfiles; i++) {
+ final ProfilePowerState profile = mProfilePowerState.valueAt(i);
+ pw.print(" mUserId=");
+ pw.print(profile.mUserId);
+ pw.print(" mScreenOffTimeout=");
+ pw.print(profile.mScreenOffTimeout);
+ pw.print(" mWakeLockSummary=");
+ pw.print(profile.mWakeLockSummary);
+ pw.print(" mLastUserActivityTime=");
+ pw.print(profile.mLastUserActivityTime);
+ pw.print(" mLockingNotified=");
+ pw.println(profile.mLockingNotified);
+ }
+
wcd = mWirelessChargerDetector;
}
@@ -3590,7 +3767,8 @@
proto.write(
PowerServiceSettingsAndConfigurationDumpProto
.MAXIMUM_SCREEN_OFF_TIMEOUT_FROM_DEVICE_ADMIN_MS,
- mMaximumScreenOffTimeoutFromDeviceAdmin);
+ // Clamp to int32
+ Math.min(mMaximumScreenOffTimeoutFromDeviceAdmin, Integer.MAX_VALUE));
proto.write(
PowerServiceSettingsAndConfigurationDumpProto
.IS_MAXIMUM_SCREEN_OFF_TIMEOUT_FROM_DEVICE_ADMIN_ENFORCED_LOCKED,
@@ -3686,9 +3864,9 @@
mIsVrModeEnabled);
proto.end(settingsAndConfigurationToken);
- final int sleepTimeout = getSleepTimeoutLocked();
- final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
- final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+ final long sleepTimeout = getSleepTimeoutLocked();
+ final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
+ final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
proto.write(PowerManagerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout);
proto.write(PowerManagerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout);
proto.write(PowerManagerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration);
@@ -4697,8 +4875,8 @@
}
@Override
- public void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs) {
- setMaximumScreenOffTimeoutFromDeviceAdminInternal(timeMs);
+ public void setMaximumScreenOffTimeoutFromDeviceAdmin(@UserIdInt int userId, long timeMs) {
+ setMaximumScreenOffTimeoutFromDeviceAdminInternal(userId, timeMs);
}
@Override
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 1382894..ca0a450 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -512,7 +512,7 @@
} else {
mTrustAgentService.onConfigure(Collections.EMPTY_LIST, null);
}
- final long maxTimeToLock = dpm.getMaximumTimeToLockForUserAndProfiles(mUserId);
+ final long maxTimeToLock = dpm.getMaximumTimeToLock(null, mUserId);
if (maxTimeToLock != mMaximumTimeToLock) {
// If the timeout changes, cancel the alarm and send a timeout event to have
// the agent re-evaluate trust.
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 21c6889..de723c6 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -59,6 +59,8 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+
+import com.android.server.FgThread;
import com.android.server.wm.WindowManagerInternal;
import android.view.inputmethod.InputMethodManagerInternal;
@@ -825,9 +827,11 @@
@Override
public void onSwitchUser(int userHandle) {
- synchronized (mLock) {
- mComponentObserver.onUsersChanged();
- }
+ FgThread.getHandler().post(() -> {
+ synchronized (mLock) {
+ mComponentObserver.onUsersChanged();
+ }
+ });
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2bda80d..163b160 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 @@
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/Android.bp b/services/core/jni/Android.bp
index e53aa81..b18c1a0 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -32,6 +32,7 @@
"com_android_server_lights_LightsService.cpp",
"com_android_server_location_GnssLocationProvider.cpp",
"com_android_server_locksettings_SyntheticPasswordManager.cpp",
+ "com_android_server_net_NetworkStatsService.cpp",
"com_android_server_power_PowerManagerService.cpp",
"com_android_server_SerialService.cpp",
"com_android_server_storage_AppFuseBridge.cpp",
diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
index 35e65bc..79f482c6 100644
--- a/services/core/jni/com_android_server_UsbDescriptorParser.cpp
+++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
@@ -81,16 +81,20 @@
return NULL;
}
- char* c_str = usb_device_get_string(device, stringId, 0 /*timeout*/);
+ // Get Raw UCS2 Bytes
+ jbyte* byteBuffer = NULL;
+ size_t numUSC2Bytes = 0;
+ int retVal =
+ usb_device_get_string_ucs2(device, stringId, 0 /*timeout*/,
+ (void**)&byteBuffer, &numUSC2Bytes);
- jstring j_str = env->NewStringUTF(c_str);
+ jstring j_str = NULL;
- free(c_str);
- usb_device_close(device);
-
+ if (retVal == 0) {
+ j_str = env->NewString((jchar*)byteBuffer, numUSC2Bytes/2);
+ free(byteBuffer);
+ }
return j_str;
}
} // extern "C"
-
-
diff --git a/core/jni/android_net_TrafficStats.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
similarity index 79%
rename from core/jni/android_net_TrafficStats.cpp
rename to services/core/jni/com_android_server_net_NetworkStatsService.cpp
index d0c237d..8de24e5 100644
--- a/core/jni/android_net_TrafficStats.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "TrafficStats"
+#define LOG_TAG "NetworkStatsNative"
#include <dirent.h>
#include <errno.h>
@@ -191,8 +191,24 @@
{"nativeGetUidStat", "(II)J", (void*) getUidStat},
};
-int register_android_net_TrafficStats(JNIEnv* env) {
- return RegisterMethodsOrDie(env, "android/net/TrafficStats", gMethods, NELEM(gMethods));
+int register_android_server_net_NetworkStatsService(JNIEnv* env) {
+ jclass netStatsService = env->FindClass("com/android/server/net/NetworkStatsService");
+ jfieldID rxBytesId = env->GetStaticFieldID(netStatsService, "TYPE_RX_BYTES", "I");
+ jfieldID rxPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_RX_PACKETS", "I");
+ jfieldID txBytesId = env->GetStaticFieldID(netStatsService, "TYPE_TX_BYTES", "I");
+ jfieldID txPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_TX_PACKETS", "I");
+ jfieldID tcpRxPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_TCP_RX_PACKETS", "I");
+ jfieldID tcpTxPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_TCP_TX_PACKETS", "I");
+
+ env->SetStaticIntField(netStatsService, rxBytesId, RX_BYTES);
+ env->SetStaticIntField(netStatsService, rxPacketsId, RX_PACKETS);
+ env->SetStaticIntField(netStatsService, txBytesId, TX_BYTES);
+ env->SetStaticIntField(netStatsService, txPacketsId, TX_PACKETS);
+ env->SetStaticIntField(netStatsService, tcpRxPacketsId, TCP_RX_PACKETS);
+ env->SetStaticIntField(netStatsService, tcpTxPacketsId, TCP_TX_PACKETS);
+
+ return jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsService", gMethods,
+ NELEM(gMethods));
}
}
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 46d5043..071b6b8 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -51,6 +51,7 @@
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
int register_android_server_GraphicsStatsService(JNIEnv* env);
int register_android_hardware_display_DisplayViewport(JNIEnv* env);
+int register_android_server_net_NetworkStatsService(JNIEnv* env);
};
using namespace android;
@@ -95,6 +96,7 @@
register_android_server_SyntheticPasswordManager(env);
register_android_server_GraphicsStatsService(env);
register_android_hardware_display_DisplayViewport(env);
+ register_android_server_net_NetworkStatsService(env);
return JNI_VERSION_1_4;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 387818b..e5351b4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4674,56 +4674,56 @@
}
}
- void updateMaximumTimeToLockLocked(int userHandle) {
- // Calculate the min timeout for all profiles - including the ones with a separate
- // challenge. Ideally if the timeout only affected the profile challenge we'd lock that
- // challenge only and keep the screen on. However there is no easy way of doing that at the
- // moment so we set the screen off timeout regardless of whether it affects the parent user
- // or the profile challenge only.
- long timeMs = Long.MAX_VALUE;
- int[] profileIds = mUserManager.getProfileIdsWithDisabled(userHandle);
- for (int profileId : profileIds) {
- DevicePolicyData policy = getUserDataUnchecked(profileId);
- final int N = policy.mAdminList.size();
- for (int i = 0; i < N; i++) {
- ActiveAdmin admin = policy.mAdminList.get(i);
- if (admin.maximumTimeToUnlock > 0
- && timeMs > admin.maximumTimeToUnlock) {
- timeMs = admin.maximumTimeToUnlock;
- }
- // If userInfo.id is a managed profile, we also need to look at
- // the policies set on the parent.
- if (admin.hasParentActiveAdmin()) {
- final ActiveAdmin parentAdmin = admin.getParentActiveAdmin();
- if (parentAdmin.maximumTimeToUnlock > 0
- && timeMs > parentAdmin.maximumTimeToUnlock) {
- timeMs = parentAdmin.maximumTimeToUnlock;
- }
- }
- }
+ private void updateMaximumTimeToLockLocked(@UserIdInt int userId) {
+ // Update the profile's timeout
+ if (isManagedProfile(userId)) {
+ updateProfileLockTimeoutLocked(userId);
}
- // We only store the last maximum time to lock on the parent profile. So if calling from a
- // managed profile, retrieve the policy for the parent.
- DevicePolicyData policy = getUserDataUnchecked(getProfileParentId(userHandle));
- if (policy.mLastMaximumTimeToLock == timeMs) {
- return;
- }
- policy.mLastMaximumTimeToLock = timeMs;
-
+ final long timeMs;
final long ident = mInjector.binderClearCallingIdentity();
try {
+ // Update the device timeout
+ final int parentId = getProfileParentId(userId);
+ timeMs = getMaximumTimeToLockPolicyFromAdmins(
+ getActiveAdminsForLockscreenPoliciesLocked(parentId, false));
+
+ final DevicePolicyData policy = getUserDataUnchecked(parentId);
+ if (policy.mLastMaximumTimeToLock == timeMs) {
+ return;
+ }
+ policy.mLastMaximumTimeToLock = timeMs;
+
if (policy.mLastMaximumTimeToLock != Long.MAX_VALUE) {
// Make sure KEEP_SCREEN_ON is disabled, since that
// would allow bypassing of the maximum time to lock.
mInjector.settingsGlobalPutInt(Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
}
-
- mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
- (int) Math.min(policy.mLastMaximumTimeToLock, Integer.MAX_VALUE));
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
+
+ mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
+ UserHandle.USER_SYSTEM, timeMs);
+ }
+
+ private void updateProfileLockTimeoutLocked(@UserIdInt int userId) {
+ final long timeMs;
+ if (isSeparateProfileChallengeEnabled(userId)) {
+ timeMs = getMaximumTimeToLockPolicyFromAdmins(
+ getActiveAdminsForLockscreenPoliciesLocked(userId, false /* parent */));
+ } else {
+ timeMs = Long.MAX_VALUE;
+ }
+
+ final DevicePolicyData policy = getUserDataUnchecked(userId);
+ if (policy.mLastMaximumTimeToLock == timeMs) {
+ return;
+ }
+ policy.mLastMaximumTimeToLock = timeMs;
+
+ mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
+ userId, policy.mLastMaximumTimeToLock);
}
@Override
@@ -4734,50 +4734,21 @@
enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
if (who != null) {
- ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
+ final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
return admin != null ? admin.maximumTimeToUnlock : 0;
}
// Return the strictest policy across all participating admins.
- List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(
+ final List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(
userHandle, parent);
- return getMaximumTimeToLockPolicyFromAdmins(admins);
- }
- }
-
- @Override
- public long getMaximumTimeToLockForUserAndProfiles(int userHandle) {
- if (!mHasFeature) {
- return 0;
- }
- enforceFullCrossUsersPermission(userHandle);
- synchronized (this) {
- // All admins for this user.
- ArrayList<ActiveAdmin> admins = new ArrayList<ActiveAdmin>();
- for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
- DevicePolicyData policy = getUserData(userInfo.id);
- admins.addAll(policy.mAdminList);
- // If it is a managed profile, it may have parent active admins
- if (userInfo.isManagedProfile()) {
- for (ActiveAdmin admin : policy.mAdminList) {
- if (admin.hasParentActiveAdmin()) {
- admins.add(admin.getParentActiveAdmin());
- }
- }
- }
- }
- return getMaximumTimeToLockPolicyFromAdmins(admins);
+ final long timeMs = getMaximumTimeToLockPolicyFromAdmins(admins);
+ return timeMs == Long.MAX_VALUE ? 0 : timeMs;
}
}
private long getMaximumTimeToLockPolicyFromAdmins(List<ActiveAdmin> admins) {
- long time = 0;
- final int N = admins.size();
- for (int i = 0; i < N; i++) {
- ActiveAdmin admin = admins.get(i);
- if (time == 0) {
- time = admin.maximumTimeToUnlock;
- } else if (admin.maximumTimeToUnlock != 0
- && time > admin.maximumTimeToUnlock) {
+ long time = Long.MAX_VALUE;
+ for (final ActiveAdmin admin : admins) {
+ if (admin.maximumTimeToUnlock > 0 && admin.maximumTimeToUnlock < time) {
time = admin.maximumTimeToUnlock;
}
}
@@ -9451,7 +9422,7 @@
// ignore if it contradicts an existing policy
long timeMs = getMaximumTimeToLock(
who, mInjector.userHandleGetCallingUserId(), /* parent */ false);
- if (timeMs > 0 && timeMs < Integer.MAX_VALUE) {
+ if (timeMs > 0 && timeMs < Long.MAX_VALUE) {
return;
}
}
@@ -9913,6 +9884,13 @@
public boolean isUserAffiliatedWithDevice(int userId) {
return DevicePolicyManagerService.this.isUserAffiliatedWithDeviceLocked(userId);
}
+
+ @Override
+ public void reportSeparateProfileChallengeChanged(@UserIdInt int userId) {
+ synchronized (DevicePolicyManagerService.this) {
+ updateMaximumTimeToLockLocked(userId);
+ }
+ }
}
private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 5c3a37a..a9fd8e5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -19,11 +19,13 @@
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.os.Process;
import android.os.SystemClock;
import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
import java.util.ArrayList;
@@ -32,8 +34,6 @@
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
-import android.os.Process;
-
/**
* A class managing access to the security logs. It maintains an internal buffer of pending
* logs to be retrieved by the device owner. The logs are retrieved from the logd daemon via
@@ -48,7 +48,13 @@
private final Lock mLock = new ReentrantLock();
SecurityLogMonitor(DevicePolicyManagerService service) {
- mService = service;
+ this(service, 0 /* id */);
+ }
+
+ @VisibleForTesting
+ SecurityLogMonitor(DevicePolicyManagerService service, long id) {
+ this.mService = service;
+ this.mId = id;
}
private static final boolean DEBUG = false; // STOPSHIP if true.
@@ -58,7 +64,7 @@
* it should be less than 100 bytes), setting 1024 entries as the threshold to notify Device
* Owner.
*/
- private static final int BUFFER_ENTRIES_NOTIFICATION_LEVEL = 1024;
+ @VisibleForTesting static final int BUFFER_ENTRIES_NOTIFICATION_LEVEL = 1024;
/**
* The maximum number of entries we should store before dropping earlier logs, to limit the
* memory usage.
@@ -87,6 +93,8 @@
@GuardedBy("mLock")
private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<>();
@GuardedBy("mLock")
+ private long mId;
+ @GuardedBy("mLock")
private boolean mAllowedToRetrieve = false;
/**
@@ -112,6 +120,7 @@
try {
if (mMonitorThread == null) {
mPendingLogs = new ArrayList<>();
+ mId = 0;
mAllowedToRetrieve = false;
mNextAllowedRetrievalTimeMillis = -1;
mPaused = false;
@@ -137,6 +146,7 @@
}
// Reset state and clear buffer
mPendingLogs = new ArrayList<>();
+ mId = 0;
mAllowedToRetrieve = false;
mNextAllowedRetrievalTimeMillis = -1;
mPaused = false;
@@ -305,6 +315,7 @@
if (lastNanos > currentNanos) {
// New event older than the last we've seen so far, must be due to reordering.
if (DEBUG) Slog.d(TAG, "New event in the overlap: " + currentNanos);
+ assignLogId(curEvent);
mPendingLogs.add(curEvent);
curPos++;
} else if (lastNanos < currentNanos) {
@@ -317,6 +328,7 @@
if (DEBUG) Slog.d(TAG, "Skipped dup event with timestamp: " + lastNanos);
} else {
// Wow, what a coincidence, or probably the clock is too coarse.
+ assignLogId(curEvent);
mPendingLogs.add(curEvent);
if (DEBUG) Slog.d(TAG, "Event timestamp collision: " + lastNanos);
}
@@ -324,8 +336,13 @@
curPos++;
}
}
+ // Assign an id to the new logs, after the overlap with mLastEvents.
+ List<SecurityEvent> idLogs = newLogs.subList(curPos, newLogs.size());
+ for (SecurityEvent event : idLogs) {
+ assignLogId(event);
+ }
// Save the rest of the new batch.
- mPendingLogs.addAll(newLogs.subList(curPos, newLogs.size()));
+ mPendingLogs.addAll(idLogs);
if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
// Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL.
@@ -334,7 +351,20 @@
mPendingLogs.size()));
Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
}
- if (DEBUG) Slog.d(TAG, mPendingLogs.size() + " pending events in the buffer after merging");
+ if (DEBUG) Slog.d(TAG, mPendingLogs.size() + " pending events in the buffer after merging,"
+ + " with ids " + mPendingLogs.get(0).getId()
+ + " to " + mPendingLogs.get(mPendingLogs.size() - 1).getId());
+ }
+
+ @GuardedBy("mLock")
+ private void assignLogId(SecurityEvent event) {
+ event.setId(mId);
+ if (mId == Long.MAX_VALUE) {
+ Slog.i(TAG, "Reached maximum id value; wrapping around.");
+ mId = 0;
+ } else {
+ mId++;
+ }
}
@Override
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index b5e4af7..1ca6f26 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -44,7 +44,8 @@
android-support-test \
mockito-robolectric-prebuilt \
platform-test-annotations \
- truth-prebuilt
+ truth-prebuilt \
+ testng
LOCAL_JAVA_LIBRARIES := \
junit \
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index 13623e5..ced9b1e 100644
--- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -23,6 +23,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.robolectric.shadow.api.Shadow.extract;
+import static org.testng.Assert.expectThrows;
import android.annotation.Nullable;
import android.app.backup.BackupManager;
@@ -37,7 +38,6 @@
import android.platform.test.annotations.Presubmit;
import com.android.internal.backup.IBackupTransport;
-import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.server.backup.testing.ShadowBackupTransportStub;
import com.android.server.backup.testing.ShadowContextImplForBackup;
import com.android.server.backup.testing.ShadowPackageManagerForBackup;
@@ -58,6 +58,7 @@
import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPackageManager;
+import org.testng.Assert.ThrowingRunnable;
import java.util.ArrayList;
import java.util.Arrays;
@@ -710,16 +711,6 @@
return transportManager;
}
- private static <T extends Throwable> void expectThrows(
- Class<T> throwableClass, ThrowingRunnable runnable) {
- try {
- runnable.runOrThrow();
- fail("Expected to throw " + throwableClass.getSimpleName());
- } catch (Throwable t) {
- assertThat(t).isInstanceOf(throwableClass);
- }
- }
-
private static class TransportInfo {
public final String packageName;
public final String name;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index c14f74c..0462b14 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -62,7 +62,7 @@
@Mock AccessibilityServiceInfo mMockServiceInfo;
@Mock ResolveInfo mMockResolveInfo;
@Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
- @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+ @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock WindowManagerInternal mMockWindowManagerInternal;
@Mock GlobalActionPerformer mMockGlobalActionPerformer;
@Mock KeyEventDispatcher mMockKeyEventDispatcher;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 45ecbfb..8853db2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -59,7 +59,7 @@
@Mock AccessibilityServiceInfo mMockServiceInfo;
@Mock ResolveInfo mMockResolveInfo;
@Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
- @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+ @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock WindowManagerInternal mMockWindowManagerInternal;
@Mock GlobalActionPerformer mMockGlobalActionPerformer;
@Mock IBinder mMockOwner;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 4779474..60783db 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -34,7 +34,6 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.nullable;
import static org.mockito.Mockito.reset;
@@ -2244,27 +2243,32 @@
reset(getServices().settings);
dpm.setMaximumTimeToLock(admin1, 0);
- verifyScreenTimeoutCall(null, false);
+ verifyScreenTimeoutCall(null, UserHandle.USER_SYSTEM);
+ verifyStayOnWhilePluggedCleared(false);
reset(getServices().powerManagerInternal);
reset(getServices().settings);
dpm.setMaximumTimeToLock(admin1, 1);
- verifyScreenTimeoutCall(1, true);
+ verifyScreenTimeoutCall(1L, UserHandle.USER_SYSTEM);
+ verifyStayOnWhilePluggedCleared(true);
reset(getServices().powerManagerInternal);
reset(getServices().settings);
dpm.setMaximumTimeToLock(admin2, 10);
- verifyScreenTimeoutCall(null, false);
+ verifyScreenTimeoutCall(null, UserHandle.USER_SYSTEM);
+ verifyStayOnWhilePluggedCleared(false);
reset(getServices().powerManagerInternal);
reset(getServices().settings);
dpm.setMaximumTimeToLock(admin1, 5);
- verifyScreenTimeoutCall(5, true);
+ verifyScreenTimeoutCall(5L, UserHandle.USER_SYSTEM);
+ verifyStayOnWhilePluggedCleared(true);
reset(getServices().powerManagerInternal);
reset(getServices().settings);
dpm.setMaximumTimeToLock(admin2, 4);
- verifyScreenTimeoutCall(4, true);
+ verifyScreenTimeoutCall(4L, UserHandle.USER_SYSTEM);
+ verifyStayOnWhilePluggedCleared(true);
reset(getServices().powerManagerInternal);
reset(getServices().settings);
@@ -2272,24 +2276,89 @@
reset(getServices().powerManagerInternal);
reset(getServices().settings);
- dpm.setMaximumTimeToLock(admin2, Integer.MAX_VALUE);
- verifyScreenTimeoutCall(Integer.MAX_VALUE, true);
- reset(getServices().powerManagerInternal);
- reset(getServices().settings);
-
- dpm.setMaximumTimeToLock(admin2, Integer.MAX_VALUE + 1);
- verifyScreenTimeoutCall(Integer.MAX_VALUE, true);
+ dpm.setMaximumTimeToLock(admin2, Long.MAX_VALUE);
+ verifyScreenTimeoutCall(Long.MAX_VALUE, UserHandle.USER_SYSTEM);
+ verifyStayOnWhilePluggedCleared(true);
reset(getServices().powerManagerInternal);
reset(getServices().settings);
dpm.setMaximumTimeToLock(admin2, 10);
- verifyScreenTimeoutCall(10, true);
+ verifyScreenTimeoutCall(10L, UserHandle.USER_SYSTEM);
+ verifyStayOnWhilePluggedCleared(true);
reset(getServices().powerManagerInternal);
reset(getServices().settings);
- // There's no restriction; shold be set to MAX.
+ // There's no restriction; should be set to MAX.
dpm.setMaximumTimeToLock(admin2, 0);
- verifyScreenTimeoutCall(Integer.MAX_VALUE, false);
+ verifyScreenTimeoutCall(Long.MAX_VALUE, UserHandle.USER_SYSTEM);
+ verifyStayOnWhilePluggedCleared(false);
+ }
+
+ // Test if lock timeout on managed profile is handled correctly depending on whether profile
+ // uses separate challenge.
+ public void testSetMaximumTimeToLockProfile() throws Exception {
+ final int PROFILE_USER = 15;
+ final int PROFILE_ADMIN = UserHandle.getUid(PROFILE_USER, 19436);
+ addManagedProfile(admin1, PROFILE_ADMIN, admin1);
+ mContext.binder.callingUid = PROFILE_ADMIN;
+ final DevicePolicyManagerInternal dpmi =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+
+ dpm.setMaximumTimeToLock(admin1, 0);
+
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
+
+ // First add timeout for the profile.
+ dpm.setMaximumTimeToLock(admin1, 10);
+ verifyScreenTimeoutCall(10L, UserHandle.USER_SYSTEM);
+
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
+
+ // Add separate challenge
+ when(getServices().lockPatternUtils
+ .isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(true);
+ dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER);
+
+ verifyScreenTimeoutCall(10L, PROFILE_USER);
+ verifyScreenTimeoutCall(Long.MAX_VALUE, UserHandle.USER_SYSTEM);
+
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
+
+ // Remove the timeout.
+ dpm.setMaximumTimeToLock(admin1, 0);
+ verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER);
+ verifyScreenTimeoutCall(null , UserHandle.USER_SYSTEM);
+
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
+
+ // Add it back.
+ dpm.setMaximumTimeToLock(admin1, 10);
+ verifyScreenTimeoutCall(10L, PROFILE_USER);
+ verifyScreenTimeoutCall(null, UserHandle.USER_SYSTEM);
+
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
+
+ // Remove separate challenge.
+ reset(getServices().lockPatternUtils);
+ when(getServices().lockPatternUtils
+ .isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(false);
+ dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER);
+
+ verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER);
+ verifyScreenTimeoutCall(10L , UserHandle.USER_SYSTEM);
+
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
+
+ // Remove the timeout.
+ dpm.setMaximumTimeToLock(admin1, 0);
+ verifyScreenTimeoutCall(null, PROFILE_USER);
+ verifyScreenTimeoutCall(Long.MAX_VALUE, UserHandle.USER_SYSTEM);
}
public void testSetRequiredStrongAuthTimeout_DeviceOwner() throws Exception {
@@ -2365,15 +2434,17 @@
() -> dpm.setRequiredStrongAuthTimeout(admin1, -ONE_MINUTE));
}
- private void verifyScreenTimeoutCall(Integer expectedTimeout,
- boolean shouldStayOnWhilePluggedInBeCleared) {
+ private void verifyScreenTimeoutCall(Long expectedTimeout, int userId) {
if (expectedTimeout == null) {
verify(getServices().powerManagerInternal, times(0))
- .setMaximumScreenOffTimeoutFromDeviceAdmin(anyInt());
+ .setMaximumScreenOffTimeoutFromDeviceAdmin(eq(userId), anyLong());
} else {
verify(getServices().powerManagerInternal, times(1))
- .setMaximumScreenOffTimeoutFromDeviceAdmin(eq(expectedTimeout));
+ .setMaximumScreenOffTimeoutFromDeviceAdmin(eq(userId), eq(expectedTimeout));
}
+ }
+
+ private void verifyStayOnWhilePluggedCleared(boolean cleared) {
// TODO Verify calls to settingsGlobalPutInt. Tried but somehow mockito threw
// UnfinishedVerificationException.
}
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 8cb0459..4232c44 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -181,6 +181,13 @@
return getUserInfo(userId1);
}
);
+ when(userManager.getProfileParent(anyInt())).thenAnswer(
+ invocation -> {
+ final int userId1 = (int) invocation.getArguments()[0];
+ final UserInfo ui = getUserInfo(userId1);
+ return ui == null ? null : getUserInfo(ui.profileGroupId);
+ }
+ );
when(userManager.getProfiles(anyInt())).thenAnswer(
invocation -> {
final int userId12 = (int) invocation.getArguments()[0];
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
new file mode 100644
index 0000000..0f05212
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
@@ -0,0 +1,61 @@
+package com.android.server.devicepolicy;
+
+import static android.app.admin.SecurityLog.TAG_ADB_SHELL_CMD;
+
+import android.app.admin.SecurityLog.SecurityEvent;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.EventLog;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+public class SecurityEventTest extends DpmTestBase {
+ private static long ID = 549;
+ private static String DATA = "adb shell some_command";
+
+ public void testSecurityEventId() {
+ SecurityEvent event = buildSecurityEvents(1 /* generate a single event */, ID).get(0);
+ assertEquals(ID, event.getId());
+ event.setId(20);
+ assertEquals(20, event.getId());
+ }
+
+ public void testSecurityEventParceling() {
+ // GIVEN an event.
+ SecurityEvent event = buildSecurityEvents(1 /* generate a single event */, ID).get(0);
+ // WHEN parceling the event.
+ Parcel p = Parcel.obtain();
+ p.writeParcelable(event, 0);
+ p.setDataPosition(0);
+ SecurityEvent unparceledEvent = p.readParcelable(SecurityEventTest.class.getClassLoader());
+ p.recycle();
+ // THEN the event state is preserved.
+ assertEquals(event.getTag(), unparceledEvent.getTag());
+ assertEquals(event.getData(), unparceledEvent.getData());
+ assertEquals(event.getTimeNanos(), unparceledEvent.getTimeNanos());
+ assertEquals(event.getId(), unparceledEvent.getId());
+ }
+
+ private List<SecurityEvent> buildSecurityEvents(int numEvents, long id) {
+ // Write an event to the EventLog.
+ for (int i = 0; i < numEvents; i++) {
+ EventLog.writeEvent(TAG_ADB_SHELL_CMD, DATA + "_" + i);
+ }
+ List<EventLog.Event> events = new ArrayList<>();
+ try {
+ EventLog.readEvents(new int[]{TAG_ADB_SHELL_CMD}, events);
+ } catch (IOException e) {
+ fail("Reading a test event from storage failed: " + e);
+ }
+ assertTrue("Unexpected number of events read from the log.", events.size() >= numEvents);
+ // Read events generated by test, from the end of the log.
+ List<SecurityEvent> securityEvents = new ArrayList<>();
+ for (int i = events.size() - numEvents; i < events.size(); i++) {
+ securityEvents.add(new SecurityEvent(id++, events.get(i).getBytes()));
+ }
+ return securityEvents;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
new file mode 100644
index 0000000..da552b9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_PASSWORD;
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PATTERN;
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PIN;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+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 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;
+import java.util.Random;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KeySyncTaskTest {
+
+ @Test
+ public void isPin_isTrueForNumericString() {
+ assertTrue(KeySyncTask.isPin("3298432574398654376547"));
+ }
+
+ @Test
+ public void isPin_isFalseForStringContainingLetters() {
+ assertFalse(KeySyncTask.isPin("398i54369548654"));
+ }
+
+ @Test
+ public void isPin_isFalseForStringContainingSymbols() {
+ assertFalse(KeySyncTask.isPin("-3987543643"));
+ }
+
+ @Test
+ public void hashCredentials_returnsSameHashForSameCredentialsAndSalt() {
+ String credentials = "password1234";
+ byte[] salt = randomBytes(16);
+
+ assertArrayEquals(
+ KeySyncTask.hashCredentials(salt, credentials),
+ KeySyncTask.hashCredentials(salt, credentials));
+ }
+
+ @Test
+ public void hashCredentials_returnsDifferentHashForDifferentCredentials() {
+ byte[] salt = randomBytes(16);
+
+ assertFalse(
+ Arrays.equals(
+ KeySyncTask.hashCredentials(salt, "password1234"),
+ KeySyncTask.hashCredentials(salt, "password12345")));
+ }
+
+ @Test
+ public void hashCredentials_returnsDifferentHashForDifferentSalt() {
+ String credentials = "wowmuch";
+
+ assertFalse(
+ Arrays.equals(
+ KeySyncTask.hashCredentials(randomBytes(64), credentials),
+ KeySyncTask.hashCredentials(randomBytes(64), credentials)));
+ }
+
+ @Test
+ public void hashCredentials_returnsDifferentHashEvenIfConcatIsSame() {
+ assertFalse(
+ Arrays.equals(
+ KeySyncTask.hashCredentials(utf8Bytes("123"), "4567"),
+ KeySyncTask.hashCredentials(utf8Bytes("1234"), "567")));
+ }
+
+ @Test
+ public void getUiFormat_returnsPinIfPin() {
+ assertEquals(TYPE_PIN,
+ KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234"));
+ }
+
+ @Test
+ public void getUiFormat_returnsPasswordIfPassword() {
+ assertEquals(TYPE_PASSWORD,
+ KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a"));
+ }
+
+ @Test
+ public void getUiFormat_returnsPatternIfPattern() {
+ assertEquals(TYPE_PATTERN,
+ KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234"));
+
+ }
+
+ private static byte[] utf8Bytes(String s) {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
+
+ private static byte[] randomBytes(int n) {
+ byte[] bytes = new byte[n];
+ new Random().nextBytes(bytes);
+ return bytes;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index ac3abed..6254d52 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -16,6 +16,8 @@
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.junit.Assert.assertFalse;
@@ -23,13 +25,20 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import com.google.common.collect.ImmutableMap;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
import java.security.MessageDigest;
import java.util.Arrays;
+import java.util.Map;
+import java.util.Random;
+import javax.crypto.AEADBadTagException;
+import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@SmallTest
@@ -39,6 +48,15 @@
private static final int THM_KF_HASH_SIZE = 256;
private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
private static final String SHA_256_ALGORITHM = "SHA-256";
+ private static final String APPLICATION_KEY_ALGORITHM = "AES";
+ private static final byte[] LOCK_SCREEN_HASH_1 =
+ utf8Bytes("g09TEvo6XqVdNaYdRggzn5w2C5rCeE1F");
+ private static final byte[] LOCK_SCREEN_HASH_2 =
+ utf8Bytes("snQzsbvclkSsG6PwasAp1oFLzbq3KtFe");
+ private static final byte[] RECOVERY_CLAIM_HEADER =
+ "V1 KF_claim".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] RECOVERY_RESPONSE_HEADER =
+ "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
@Test
public void calculateThmKfHash_isShaOfLockScreenHashWithPrefix() throws Exception {
@@ -97,6 +115,233 @@
utf8Bytes("!")));
}
+ @Test
+ public void decryptApplicationKey_decryptsAnApplicationKeyEncryptedWithSecureBox()
+ throws Exception {
+ String alias = "phoebe";
+ SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
+ SecretKey applicationKey = generateApplicationKey();
+ Map<String, byte[]> encryptedKeys =
+ KeySyncUtils.encryptKeysWithRecoveryKey(
+ recoveryKey, ImmutableMap.of(alias, applicationKey));
+ byte[] encryptedKey = encryptedKeys.get(alias);
+
+ byte[] keyMaterial =
+ KeySyncUtils.decryptApplicationKey(recoveryKey.getEncoded(), encryptedKey);
+
+ assertArrayEquals(applicationKey.getEncoded(), keyMaterial);
+ }
+
+ @Test
+ public void decryptApplicationKey_throwsIfUnableToDecrypt() throws Exception {
+ String alias = "casper";
+ Map<String, byte[]> encryptedKeys =
+ KeySyncUtils.encryptKeysWithRecoveryKey(
+ KeySyncUtils.generateRecoveryKey(),
+ ImmutableMap.of("casper", generateApplicationKey()));
+ byte[] encryptedKey = encryptedKeys.get(alias);
+
+ try {
+ KeySyncUtils.decryptApplicationKey(
+ KeySyncUtils.generateRecoveryKey().getEncoded(), encryptedKey);
+ fail("Did not throw decrypting with bad key.");
+ } catch (AEADBadTagException error) {
+ // expected
+ }
+ }
+
+ @Test
+ public void decryptRecoveryKey_decryptsALocallyEncryptedKey() throws Exception {
+ SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
+ byte[] encrypted = KeySyncUtils.locallyEncryptRecoveryKey(
+ LOCK_SCREEN_HASH_1, recoveryKey);
+
+ byte[] keyMaterial = KeySyncUtils.decryptRecoveryKey(LOCK_SCREEN_HASH_1, encrypted);
+
+ assertArrayEquals(recoveryKey.getEncoded(), keyMaterial);
+ }
+
+ @Test
+ public void decryptRecoveryKey_throwsIfCannotDecrypt() throws Exception {
+ SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
+ byte[] encrypted = KeySyncUtils.locallyEncryptRecoveryKey(LOCK_SCREEN_HASH_1, recoveryKey);
+
+ try {
+ KeySyncUtils.decryptRecoveryKey(LOCK_SCREEN_HASH_2, encrypted);
+ fail("Did not throw decrypting with bad key.");
+ } catch (AEADBadTagException error) {
+ // expected
+ }
+ }
+
+ @Test
+ public void decryptRecoveryClaimResponse_decryptsAValidResponse() throws Exception {
+ byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+ byte[] vaultParams = randomBytes(100);
+ byte[] recoveryKey = randomBytes(32);
+ byte[] encryptedPayload = SecureBox.encrypt(
+ /*theirPublicKey=*/ null,
+ /*sharedSecret=*/ keyClaimant,
+ /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*payload=*/ recoveryKey);
+
+ byte[] decrypted = KeySyncUtils.decryptRecoveryClaimResponse(
+ keyClaimant, vaultParams, encryptedPayload);
+
+ assertArrayEquals(recoveryKey, decrypted);
+ }
+
+ @Test
+ public void decryptRecoveryClaimResponse_throwsIfCannotDecrypt() throws Exception {
+ byte[] vaultParams = randomBytes(100);
+ byte[] recoveryKey = randomBytes(32);
+ byte[] encryptedPayload = SecureBox.encrypt(
+ /*theirPublicKey=*/ null,
+ /*sharedSecret=*/ KeySyncUtils.generateKeyClaimant(),
+ /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*payload=*/ recoveryKey);
+
+ try {
+ KeySyncUtils.decryptRecoveryClaimResponse(
+ KeySyncUtils.generateKeyClaimant(), vaultParams, encryptedPayload);
+ fail("Did not throw decrypting with bad keyClaimant");
+ } catch (AEADBadTagException error) {
+ // expected
+ }
+ }
+
+ @Test
+ public void encryptRecoveryClaim_encryptsLockScreenAndKeyClaimant() throws Exception {
+ KeyPair keyPair = SecureBox.genKeyPair();
+ byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+ byte[] challenge = randomBytes(32);
+ byte[] vaultParams = randomBytes(100);
+
+ byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
+ keyPair.getPublic(),
+ vaultParams,
+ challenge,
+ LOCK_SCREEN_HASH_1,
+ keyClaimant);
+
+ byte[] decrypted = SecureBox.decrypt(
+ keyPair.getPrivate(),
+ /*sharedSecret=*/ null,
+ /*header=*/ KeySyncUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+ encryptedRecoveryClaim);
+ assertArrayEquals(KeySyncUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted);
+ }
+
+ @Test
+ public void encryptRecoveryClaim_cannotBeDecryptedWithoutChallenge() throws Exception {
+ KeyPair keyPair = SecureBox.genKeyPair();
+ byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+ byte[] vaultParams = randomBytes(100);
+
+ byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
+ keyPair.getPublic(),
+ vaultParams,
+ /*challenge=*/ randomBytes(32),
+ LOCK_SCREEN_HASH_1,
+ keyClaimant);
+
+ try {
+ SecureBox.decrypt(
+ keyPair.getPrivate(),
+ /*sharedSecret=*/ null,
+ /*header=*/ KeySyncUtils.concat(
+ RECOVERY_CLAIM_HEADER, vaultParams, randomBytes(32)),
+ encryptedRecoveryClaim);
+ fail("Should throw if challenge is incorrect.");
+ } catch (AEADBadTagException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectSecretKey() throws Exception {
+ byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+ byte[] challenge = randomBytes(32);
+ byte[] vaultParams = randomBytes(100);
+
+ byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
+ SecureBox.genKeyPair().getPublic(),
+ vaultParams,
+ challenge,
+ LOCK_SCREEN_HASH_1,
+ keyClaimant);
+
+ try {
+ SecureBox.decrypt(
+ SecureBox.genKeyPair().getPrivate(),
+ /*sharedSecret=*/ null,
+ /*header=*/ KeySyncUtils.concat(
+ RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+ encryptedRecoveryClaim);
+ fail("Should throw if secret key is incorrect.");
+ } catch (AEADBadTagException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectVaultParams() throws Exception {
+ KeyPair keyPair = SecureBox.genKeyPair();
+ byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+ byte[] challenge = randomBytes(32);
+
+ byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
+ keyPair.getPublic(),
+ /*vaultParams=*/ randomBytes(100),
+ challenge,
+ LOCK_SCREEN_HASH_1,
+ keyClaimant);
+
+ try {
+ SecureBox.decrypt(
+ keyPair.getPrivate(),
+ /*sharedSecret=*/ null,
+ /*header=*/ KeySyncUtils.concat(
+ RECOVERY_CLAIM_HEADER, randomBytes(100), challenge),
+ encryptedRecoveryClaim);
+ fail("Should throw if vault params is incorrect.");
+ } catch (AEADBadTagException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectHeader() throws Exception {
+ KeyPair keyPair = SecureBox.genKeyPair();
+ byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+ byte[] challenge = randomBytes(32);
+ byte[] vaultParams = randomBytes(100);
+
+ byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
+ keyPair.getPublic(),
+ vaultParams,
+ challenge,
+ LOCK_SCREEN_HASH_1,
+ keyClaimant);
+
+ try {
+ SecureBox.decrypt(
+ keyPair.getPrivate(),
+ /*sharedSecret=*/ null,
+ /*header=*/ KeySyncUtils.concat(randomBytes(10), vaultParams, challenge),
+ encryptedRecoveryClaim);
+ fail("Should throw if header is incorrect.");
+ } catch (AEADBadTagException e) {
+ // expected
+ }
+ }
+
+ private static byte[] randomBytes(int n) {
+ byte[] bytes = new byte[n];
+ new Random().nextBytes(bytes);
+ return bytes;
+ }
+
private static byte[] utf8Bytes(String s) {
return s.getBytes(StandardCharsets.UTF_8);
}
@@ -106,4 +351,10 @@
messageDigest.update(bytes);
return messageDigest.digest();
}
+
+ private static SecretKey generateApplicationKey() throws Exception {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(APPLICATION_KEY_ALGORITHM);
+ keyGenerator.init(/*keySize=*/ 256);
+ return keyGenerator.generateKey();
+ }
}
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
index a997770..e20f664 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -36,6 +36,8 @@
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;
@@ -45,6 +47,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.File;
import java.security.KeyStore;
import java.util.List;
@@ -52,9 +55,9 @@
@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;
- private static final String TEST_SHARED_PREFS_NAME = "PlatformKeyManagerTestPrefs";
@Mock private Context mContext;
@Mock private KeyStoreProxy mKeyStoreProxy;
@@ -63,18 +66,20 @@
@Captor private ArgumentCaptor<KeyStore.ProtectionParameter> mProtectionParameterCaptor;
@Captor private ArgumentCaptor<KeyStore.Entry> mEntryArgumentCaptor;
- private SharedPreferences mSharedPreferences;
+ private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+ private File mDatabaseFile;
+
private PlatformKeyManager mPlatformKeyManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- Context testContext = InstrumentationRegistry.getTargetContext();
- mSharedPreferences = testContext.getSharedPreferences(
- TEST_SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+ mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
mPlatformKeyManager = new PlatformKeyManager(
- USER_ID_FIXTURE, mContext, mKeyStoreProxy, mSharedPreferences);
+ USER_ID_FIXTURE, mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
when(mContext.getSystemServiceName(any())).thenReturn("test");
@@ -83,7 +88,8 @@
@After
public void tearDown() {
- mSharedPreferences.edit().clear().commit();
+ mRecoverableKeyStoreDb.close();
+ mDatabaseFile.delete();
}
@Test
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 12dbdb3..b3dbf93 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,62 +16,72 @@
package com.android.server.locksettings.recoverablekeystore;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
+import com.google.common.collect.ImmutableMap;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
import java.security.KeyStore;
+import java.util.Arrays;
+import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RecoverableKeyGeneratorTest {
+ private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
private static final int TEST_GENERATION_ID = 3;
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String KEY_ALGORITHM = "AES";
+ private static final String SUPPORTED_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+ private static final String UNSUPPORTED_CIPHER_ALGORITHM = "AES/CTR/NoPadding";
private static final String TEST_ALIAS = "karlin";
private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyGeneratorTestWrappingKey";
-
- @Mock
- RecoverableKeyStorage mRecoverableKeyStorage;
-
- @Captor ArgumentCaptor<KeyProtection> mKeyProtectionArgumentCaptor;
+ private static final int TEST_USER_ID = 1000;
+ 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);
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+ mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
- when(mRecoverableKeyStorage.loadFromAndroidKeyStore(any())).thenReturn(mKeyHandle);
+ AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+ mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, platformKey);
+ mDecryptKey = new PlatformDecryptionKey(TEST_GENERATION_ID, platformKey);
+ mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mRecoverableKeyStoreDb);
}
@After
@@ -79,67 +89,73 @@
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
keyStore.load(/*param=*/ null);
keyStore.deleteEntry(WRAPPING_KEY_ALIAS);
+
+ mRecoverableKeyStoreDb.close();
+ mDatabaseFile.delete();
}
@Test
public void generateAndStoreKey_setsKeyInKeyStore() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ mRecoverableKeyGenerator.generateAndStoreKey(
+ mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
- verify(mRecoverableKeyStorage, times(1))
- .importIntoAndroidKeyStore(eq(TEST_ALIAS), any(), any());
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ assertTrue(keyStore.containsAlias(TEST_ALIAS));
}
@Test
- public void generateAndStoreKey_storesKeyEnabledForEncryptDecrypt() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ public void generateAndStoreKey_storesKeyEnabledForAesGcmNoPaddingEncryptDecrypt()
+ throws Exception {
+ mRecoverableKeyGenerator.generateAndStoreKey(
+ mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
- KeyProtection keyProtection = getKeyProtectionUsed();
- assertEquals(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
- keyProtection.getPurposes());
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+ Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
+ Arrays.fill(nonce, (byte) 0);
+ cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
}
@Test
- public void generateAndStoreKey_storesKeyEnabledForGCM() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ public void generateAndStoreKey_storesKeyDisabledForOtherModes() throws Exception {
+ mRecoverableKeyGenerator.generateAndStoreKey(
+ mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
- KeyProtection keyProtection = getKeyProtectionUsed();
- assertArrayEquals(new String[] { KeyProperties.BLOCK_MODE_GCM },
- keyProtection.getBlockModes());
- }
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+ Cipher cipher = Cipher.getInstance(UNSUPPORTED_CIPHER_ALGORITHM);
- @Test
- public void generateAndStoreKey_storesKeyEnabledForNoPadding() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
- KeyProtection keyProtection = getKeyProtectionUsed();
- assertArrayEquals(new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
- keyProtection.getEncryptionPaddings());
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ fail("Should not be able to use key for " + UNSUPPORTED_CIPHER_ALGORITHM);
+ } catch (InvalidKeyException e) {
+ // expected
+ }
}
@Test
public void generateAndStoreKey_storesWrappedKey() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ mRecoverableKeyGenerator.generateAndStoreKey(
+ mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
- verify(mRecoverableKeyStorage, times(1)).persistToDisk(eq(TEST_ALIAS), any());
- }
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+ WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
+ SecretKey unwrappedKey = WrappedKey
+ .unwrapKeys(mDecryptKey, ImmutableMap.of(TEST_ALIAS, wrappedKey))
+ .get(TEST_ALIAS);
- @Test
- public void generateAndStoreKey_returnsKeyHandle() throws Exception {
- SecretKey secretKey = mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
- assertEquals(mKeyHandle, secretKey);
- }
-
- private KeyProtection getKeyProtectionUsed() throws Exception {
- verify(mRecoverableKeyStorage, times(1)).importIntoAndroidKeyStore(
- any(), any(), mKeyProtectionArgumentCaptor.capture());
- return mKeyProtectionArgumentCaptor.getValue();
- }
-
- private SecretKey generateKey() throws Exception {
- KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
- keyGenerator.init(/*keySize=*/ 256);
- return keyGenerator.generateKey();
+ // key and unwrappedKey should be equivalent. let's check!
+ byte[] plaintext = getUtf8Bytes("dtianpos");
+ Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] encrypted = cipher.doFinal(plaintext);
+ byte[] iv = cipher.getIV();
+ cipher.init(Cipher.DECRYPT_MODE, unwrappedKey, new GCMParameterSpec(128, iv));
+ byte[] decrypted = cipher.doFinal(encrypted);
+ assertArrayEquals(decrypted, plaintext);
}
private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
@@ -153,4 +169,8 @@
.build());
return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
}
+
+ private static byte[] getUtf8Bytes(String s) {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java
deleted file mode 100644
index fb4e75e..0000000
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.KeyStoreException;
-import java.util.Random;
-
-import javax.crypto.Cipher;
-import javax.crypto.KeyGenerator;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RecoverableKeyStorageImplTest {
- private static final String KEY_ALGORITHM = "AES";
- private static final int GCM_TAG_LENGTH_BYTES = 16;
- private static final int BITS_PER_BYTE = 8;
- private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
- private static final int GCM_NONCE_LENGTH_BYTES = 12;
- private static final String TEST_KEY_ALIAS = "RecoverableKeyStorageImplTestKey";
- private static final int KEYSTORE_UID_SELF = -1;
-
- private RecoverableKeyStorageImpl mRecoverableKeyStorage;
-
- @Before
- public void setUp() throws Exception {
- mRecoverableKeyStorage = RecoverableKeyStorageImpl.newInstance(
- /*userId=*/ KEYSTORE_UID_SELF);
- }
-
- @After
- public void tearDown() {
- try {
- mRecoverableKeyStorage.removeFromAndroidKeyStore(TEST_KEY_ALIAS);
- } catch (KeyStoreException e) {
- // Do nothing.
- }
- }
-
- @Test
- public void loadFromAndroidKeyStore_loadsAKeyThatWasImported() throws Exception {
- SecretKey key = generateKey();
- mRecoverableKeyStorage.importIntoAndroidKeyStore(
- TEST_KEY_ALIAS,
- key,
- getKeyProperties());
-
- assertKeysAreEquivalent(
- key, mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS));
- }
-
- @Test
- public void importIntoAndroidKeyStore_importsWithKeyProperties() throws Exception {
- mRecoverableKeyStorage.importIntoAndroidKeyStore(
- TEST_KEY_ALIAS,
- generateKey(),
- getKeyProperties());
-
- SecretKey key = mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS);
-
- Mac mac = Mac.getInstance("HmacSHA256");
- try {
- // Fails because missing PURPOSE_SIGN or PURPOSE_VERIFY
- mac.init(key);
- fail("Was able to initialize Mac with an ENCRYPT/DECRYPT-only key.");
- } catch (InvalidKeyException e) {
- // expect exception
- }
- }
-
- @Test
- public void removeFromAndroidKeyStore_removesAnEntry() throws Exception {
- mRecoverableKeyStorage.importIntoAndroidKeyStore(
- TEST_KEY_ALIAS,
- generateKey(),
- getKeyProperties());
-
- mRecoverableKeyStorage.removeFromAndroidKeyStore(TEST_KEY_ALIAS);
-
- assertNull(mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS));
- }
-
- private static KeyProtection getKeyProperties() {
- return new KeyProtection.Builder(
- KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
- .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .build();
- }
-
- /**
- * Asserts that {@code b} key can decrypt data encrypted with {@code a} key. Otherwise throws.
- */
- private static void assertKeysAreEquivalent(SecretKey a, SecretKey b) throws Exception {
- byte[] plaintext = "doge".getBytes(StandardCharsets.UTF_8);
- byte[] nonce = generateGcmNonce();
-
- Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
- cipher.init(Cipher.ENCRYPT_MODE, a, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
- byte[] encrypted = cipher.doFinal(plaintext);
-
- cipher.init(Cipher.DECRYPT_MODE, b, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
- byte[] decrypted = cipher.doFinal(encrypted);
-
- assertArrayEquals(decrypted, plaintext);
- }
-
- /**
- * Returns a new random GCM nonce.
- */
- private static byte[] generateGcmNonce() {
- Random random = new Random();
- byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
- random.nextBytes(nonce);
- return nonce;
- }
-
- private static SecretKey generateKey() throws Exception {
- KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
- keyGenerator.init(/*keySize=*/ 256);
- return keyGenerator.generateKey();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
new file mode 100644
index 0000000..fb2d341
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.junit.Assert.fail;
+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.KeyEntryRecoveryData;
+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 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.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.Executors;
+import java.util.Random;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+@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;
+ private static final byte[] RECOVERY_RESPONSE_HEADER =
+ "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
+ private static final String TEST_ALIAS = "nick";
+
+ @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,
+ Executors.newSingleThreadExecutor());
+ }
+
+ @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);
+ fail("should have thrown");
+ } 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);
+ fail("should have thrown");
+ } catch (RemoteException e) {
+ assertEquals("Not a valid X509 key",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void recoverKeys_throwsIfNoSessionIsPresent() throws Exception {
+ try {
+ mRecoverableKeyStoreManager.recoverKeys(
+ TEST_SESSION_ID,
+ /*recoveryKeyBlob=*/ randomBytes(32),
+ /*applicationKeys=*/ ImmutableList.of(
+ new KeyEntryRecoveryData(getUtf8Bytes("alias"), randomBytes(32))
+ ),
+ TEST_USER_ID);
+ fail("should have thrown");
+ } catch (RemoteException e) {
+ assertEquals("User 10009 does not have pending session 'karlin'",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void recoverKeys_throwsIfRecoveryClaimCannotBeDecrypted() 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);
+
+ try {
+ mRecoverableKeyStoreManager.recoverKeys(
+ TEST_SESSION_ID,
+ /*encryptedRecoveryKey=*/ randomBytes(60),
+ /*applicationKeys=*/ ImmutableList.of(),
+ /*uid=*/ TEST_USER_ID);
+ fail("should have thrown");
+ } catch (RemoteException e) {
+ assertEquals("Failed to decrypt recovery key", e.getMessage());
+ }
+ }
+
+ @Test
+ public void recoverKeys_throwsIfFailedToDecryptAnApplicationKey() 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);
+ byte[] keyClaimant = mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID)
+ .getKeyClaimant();
+ SecretKey recoveryKey = randomRecoveryKey();
+ byte[] encryptedClaimResponse = encryptClaimResponse(
+ keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
+ KeyEntryRecoveryData badApplicationKey = new KeyEntryRecoveryData(
+ TEST_ALIAS.getBytes(StandardCharsets.UTF_8),
+ randomBytes(32));
+
+ try {
+ mRecoverableKeyStoreManager.recoverKeys(
+ TEST_SESSION_ID,
+ /*encryptedRecoveryKey=*/ encryptedClaimResponse,
+ /*applicationKeys=*/ ImmutableList.of(badApplicationKey),
+ /*uid=*/ TEST_USER_ID);
+ fail("should have thrown");
+ } catch (RemoteException e) {
+ assertEquals("Failed to recover key with alias 'nick'", e.getMessage());
+ }
+ }
+
+ @Test
+ public void recoverKeys_doesNotThrowIfAllIsOk() 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);
+ byte[] keyClaimant = mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID)
+ .getKeyClaimant();
+ SecretKey recoveryKey = randomRecoveryKey();
+ byte[] encryptedClaimResponse = encryptClaimResponse(
+ keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
+ KeyEntryRecoveryData applicationKey = new KeyEntryRecoveryData(
+ TEST_ALIAS.getBytes(StandardCharsets.UTF_8),
+ randomEncryptedApplicationKey(recoveryKey)
+ );
+
+ mRecoverableKeyStoreManager.recoverKeys(
+ TEST_SESSION_ID,
+ encryptedClaimResponse,
+ ImmutableList.of(applicationKey),
+ TEST_USER_ID);
+ }
+
+ private static byte[] randomEncryptedApplicationKey(SecretKey recoveryKey) throws Exception {
+ return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(
+ "alias", new SecretKeySpec(randomBytes(32), "AES")
+ )).get("alias");
+ }
+
+ private static byte[] encryptClaimResponse(
+ byte[] keyClaimant,
+ byte[] lskfHash,
+ byte[] vaultParams,
+ SecretKey recoveryKey) throws Exception {
+ byte[] locallyEncryptedRecoveryKey = KeySyncUtils.locallyEncryptRecoveryKey(
+ lskfHash, recoveryKey);
+ return SecureBox.encrypt(
+ /*theirPublicKey=*/ null,
+ /*sharedSecret=*/ keyClaimant,
+ /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*payload=*/ locallyEncryptedRecoveryKey);
+ }
+
+ private static SecretKey randomRecoveryKey() {
+ return new SecretKeySpec(randomBytes(32), "AES");
+ }
+
+ private static byte[] getUtf8Bytes(String s) {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
+
+ private static byte[] randomBytes(int n) {
+ byte[] bytes = new byte[n];
+ new Random().nextBytes(bytes);
+ return bytes;
+ }
+}
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
index 72b69f0..35ec23b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
@@ -274,9 +274,9 @@
@Test
public void decrypt_nullEncryptedPayload() throws Exception {
- IllegalArgumentException expected =
+ NullPointerException expected =
expectThrows(
- IllegalArgumentException.class,
+ NullPointerException.class,
() ->
SecureBox.decrypt(
THM_PRIVATE_KEY,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index d5ad959..76cbea8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -60,22 +60,23 @@
@Test
public void insertKey_replacesOldKey() {
int userId = 12;
+ int uid = 10009;
String alias = "test";
WrappedKey oldWrappedKey = new WrappedKey(
getUtf8Bytes("nonce1"),
getUtf8Bytes("keymaterial1"),
/*platformKeyGenerationId=*/ 1);
mRecoverableKeyStoreDb.insertKey(
- userId, alias, oldWrappedKey);
+ userId, uid, alias, oldWrappedKey);
byte[] nonce = getUtf8Bytes("nonce2");
byte[] keyMaterial = getUtf8Bytes("keymaterial2");
WrappedKey newWrappedKey = new WrappedKey(
nonce, keyMaterial, /*platformKeyGenerationId=*/2);
mRecoverableKeyStoreDb.insertKey(
- userId, alias, newWrappedKey);
+ userId, uid, alias, newWrappedKey);
- WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias);
+ WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
assertArrayEquals(nonce, retrievedKey.getNonce());
assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
assertEquals(2, retrievedKey.getPlatformKeyGenerationId());
@@ -83,6 +84,7 @@
@Test
public void insertKey_allowsTwoUidsToHaveSameAlias() {
+ int userId = 6;
String alias = "pcoulton";
WrappedKey key1 = new WrappedKey(
getUtf8Bytes("nonce1"),
@@ -93,8 +95,8 @@
getUtf8Bytes("key2"),
/*platformKeyGenerationId=*/ 1);
- mRecoverableKeyStoreDb.insertKey(/*uid=*/ 1, alias, key1);
- mRecoverableKeyStoreDb.insertKey(/*uid=*/ 2, alias, key2);
+ mRecoverableKeyStoreDb.insertKey(userId, /*uid=*/ 1, alias, key1);
+ mRecoverableKeyStoreDb.insertKey(userId, /*uid=*/ 2, alias, key2);
assertArrayEquals(
getUtf8Bytes("nonce1"),
@@ -115,14 +117,15 @@
@Test
public void getKey_returnsInsertedKey() {
int userId = 12;
+ int uid = 1009;
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);
+ mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
- WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias);
+ WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
assertArrayEquals(nonce, retrievedKey.getNonce());
assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
@@ -132,12 +135,13 @@
@Test
public void getAllKeys_getsKeysWithUserIdAndGenerationId() {
int userId = 12;
+ int uid = 1009;
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);
+ mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, generationId);
@@ -152,12 +156,13 @@
@Test
public void getAllKeys_doesNotReturnKeysWithBadGenerationId() {
int userId = 12;
+ int uid = 6000;
WrappedKey wrappedKey = new WrappedKey(
getUtf8Bytes("nonce"),
getUtf8Bytes("keymaterial"),
/*platformKeyGenerationId=*/ 5);
mRecoverableKeyStoreDb.insertKey(
- userId, /*alias=*/ "test", wrappedKey);
+ userId, uid, /*alias=*/ "test", wrappedKey);
Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
userId, /*generationId=*/ 7);
@@ -168,10 +173,11 @@
@Test
public void getAllKeys_doesNotReturnKeysWithBadUserId() {
int generationId = 12;
+ int uid = 10009;
WrappedKey wrappedKey = new WrappedKey(
getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial"), generationId);
mRecoverableKeyStoreDb.insertKey(
- /*userId=*/ 1, /*alias=*/ "test", wrappedKey);
+ /*userId=*/ 1, uid, /*alias=*/ "test", wrappedKey);
Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
/*userId=*/ 2, generationId);
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 0000000..6f93fe4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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");
+ private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault params vault params");
+
+ @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(), vaultParamsFixture()));
+
+ 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(), vaultParamsFixture()));
+ 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(), vaultParamsFixture());
+ 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(), vaultParamsFixture());
+ 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(), vaultParamsFixture());
+ 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(), vaultParamsFixture());
+ 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[] vaultParamsFixture() {
+ return Arrays.copyOf(TEST_VAULT_PARAMS, TEST_VAULT_PARAMS.length);
+ }
+
+ private static byte[] getUtf8Bytes(String s) {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index c0685f9..44f5551 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -67,6 +67,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
@@ -389,11 +390,13 @@
}
public void switchUser(int userHandle) {
- synchronized (this) {
- mCurUser = userHandle;
- mCurUserUnlocked = false;
- switchImplementationIfNeededLocked(false);
- }
+ FgThread.getHandler().post(() -> {
+ synchronized (this) {
+ mCurUser = userHandle;
+ mCurUserUnlocked = false;
+ switchImplementationIfNeededLocked(false);
+ }
+ });
}
void switchImplementationIfNeeded(boolean force) {
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index dec7b76..6af01ae 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1466,8 +1466,9 @@
}
Log.d(this, "createConnection, connection: %s", connection);
if (connection == null) {
+ Log.i(this, "createConnection, implementation returned null connection.");
connection = Connection.createFailedConnection(
- new DisconnectCause(DisconnectCause.ERROR));
+ new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"));
}
connection.setTelecomCallId(callId);
diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java
index a554c69..059a2d0 100644
--- a/telephony/java/android/telephony/MbmsDownloadSession.java
+++ b/telephony/java/android/telephony/MbmsDownloadSession.java
@@ -502,8 +502,10 @@
* Asynchronous errors through the callback may include any error not specific to the
* streaming use-case.
* @param request The request that specifies what should be downloaded.
+ * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+ * and some other error code otherwise.
*/
- public void download(@NonNull DownloadRequest request) {
+ public int download(@NonNull DownloadRequest request) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new IllegalStateException("Middleware not yet bound");
@@ -519,13 +521,16 @@
setTempFileRootDirectory(tempRootDirectory);
}
- writeDownloadRequestToken(request);
try {
- downloadService.download(request);
+ int result = downloadService.download(request);
+ if (result == MbmsErrors.SUCCESS) {
+ writeDownloadRequestToken(request);
+ }
+ return result;
} catch (RemoteException e) {
mService.set(null);
sIsInitialized.set(false);
- sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return MbmsErrors.ERROR_MIDDLEWARE_LOST;
}
}
@@ -565,8 +570,10 @@
* @param callback The callback that should be called when the middleware has information to
* share on the download.
* @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
+ * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+ * and some other error code otherwise.
*/
- public void registerStateCallback(@NonNull DownloadRequest request,
+ public int registerStateCallback(@NonNull DownloadRequest request,
@NonNull DownloadStateCallback callback, @NonNull Handler handler) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
@@ -583,16 +590,15 @@
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
throw new IllegalArgumentException("Unknown download request.");
}
- sendErrorToApp(result, null);
- return;
+ return result;
}
} catch (RemoteException e) {
mService.set(null);
sIsInitialized.set(false);
- sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
- return;
+ return MbmsErrors.ERROR_MIDDLEWARE_LOST;
}
mInternalDownloadCallbacks.put(callback, internalCallback);
+ return MbmsErrors.SUCCESS;
}
/**
@@ -606,8 +612,10 @@
*
* @param request The {@link DownloadRequest} provided during registration
* @param callback The callback provided during registration.
+ * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+ * and some other error code otherwise.
*/
- public void unregisterStateCallback(@NonNull DownloadRequest request,
+ public int unregisterStateCallback(@NonNull DownloadRequest request,
@NonNull DownloadStateCallback callback) {
try {
IMbmsDownloadService downloadService = mService.get();
@@ -617,6 +625,9 @@
InternalDownloadStateCallback internalCallback =
mInternalDownloadCallbacks.get(callback);
+ if (internalCallback == null) {
+ throw new IllegalArgumentException("Provided callback was never registered");
+ }
try {
int result = downloadService.unregisterStateCallback(request, internalCallback);
@@ -624,12 +635,12 @@
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
throw new IllegalArgumentException("Unknown download request.");
}
- sendErrorToApp(result, null);
+ return result;
}
} catch (RemoteException e) {
mService.set(null);
sIsInitialized.set(false);
- sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return MbmsErrors.ERROR_MIDDLEWARE_LOST;
}
} finally {
InternalDownloadStateCallback internalCallback =
@@ -638,6 +649,7 @@
internalCallback.stop();
}
}
+ return MbmsErrors.SUCCESS;
}
/**
@@ -647,8 +659,10 @@
* this method will throw an {@link IllegalArgumentException}.
*
* @param downloadRequest The download request that you wish to cancel.
+ * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
+ * and some other error code otherwise.
*/
- public void cancelDownload(@NonNull DownloadRequest downloadRequest) {
+ public int cancelDownload(@NonNull DownloadRequest downloadRequest) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new IllegalStateException("Middleware not yet bound");
@@ -660,16 +674,15 @@
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
throw new IllegalArgumentException("Unknown download request.");
}
- sendErrorToApp(result, null);
- return;
+ } else {
+ deleteDownloadRequestToken(downloadRequest);
}
+ return result;
} catch (RemoteException e) {
mService.set(null);
sIsInitialized.set(false);
- sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
- return;
+ return MbmsErrors.ERROR_MIDDLEWARE_LOST;
}
- deleteDownloadRequestToken(downloadRequest);
}
/**
diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java
index f15fde8..a277212 100644
--- a/telephony/java/android/telephony/NetworkScan.java
+++ b/telephony/java/android/telephony/NetworkScan.java
@@ -19,50 +19,92 @@
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.annotation.IntDef;
import android.util.Log;
import com.android.internal.telephony.ITelephony;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
- * Allows applications to request the system to perform a network scan.
- *
- * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} will
- * receive a NetworkScan which contains the callback method to stop the scan requested.
- * @hide
+ * The caller of
+ * {@link TelephonyManager#requestNetworkScan(NetworkScanRequest, NetworkScanCallback)}
+ * will receive an instance of {@link NetworkScan}, which contains a callback method
+ * {@link #stop()} for stopping the in-progress scan.
*/
public class NetworkScan {
- public static final String TAG = "NetworkScan";
+ private static final String TAG = "NetworkScan";
// Below errors are mapped from RadioError which is returned from RIL. We will consolidate
// RadioErrors during the mapping if those RadioErrors mean no difference to the users.
+
+ /**
+ * Defines acceptable values of scan error code.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ERROR_MODEM_ERROR, ERROR_INVALID_SCAN, ERROR_MODEM_UNAVAILABLE, ERROR_UNSUPPORTED,
+ ERROR_RADIO_INTERFACE_ERROR, ERROR_INVALID_SCANID, ERROR_INTERRUPTED})
+ public @interface ScanErrorCode {}
+
+ /**
+ * The RIL has successfully performed the network scan.
+ */
public static final int SUCCESS = 0; // RadioError:NONE
+
+ /**
+ * The scan has failed due to some modem errors.
+ */
public static final int ERROR_MODEM_ERROR = 1; // RadioError:RADIO_NOT_AVAILABLE
// RadioError:NO_MEMORY
// RadioError:INTERNAL_ERR
// RadioError:MODEM_ERR
// RadioError:OPERATION_NOT_ALLOWED
+
+ /**
+ * The parameters of the scan is invalid.
+ */
public static final int ERROR_INVALID_SCAN = 2; // RadioError:INVALID_ARGUMENTS
- public static final int ERROR_MODEM_BUSY = 3; // RadioError:DEVICE_IN_USE
+
+ /**
+ * The modem can not perform the scan because it is doing something else.
+ */
+ public static final int ERROR_MODEM_UNAVAILABLE = 3; // RadioError:DEVICE_IN_USE
+
+ /**
+ * The modem does not support the request scan.
+ */
public static final int ERROR_UNSUPPORTED = 4; // RadioError:REQUEST_NOT_SUPPORTED
+
// Below errors are generated at the Telephony.
- public static final int ERROR_RIL_ERROR = 10000; // Nothing or only exception is
- // returned from RIL.
- public static final int ERROR_INVALID_SCANID = 10001; // The scanId is invalid. The user is
- // either trying to stop a scan which
- // does not exist or started by others.
- public static final int ERROR_INTERRUPTED = 10002; // Scan was interrupted by another scan
- // with higher priority.
+
+ /**
+ * The RIL returns nothing or exceptions.
+ */
+ public static final int ERROR_RADIO_INTERFACE_ERROR = 10000;
+
+ /**
+ * The scan ID is invalid. The user is either trying to stop a scan which does not exist
+ * or started by others.
+ */
+ public static final int ERROR_INVALID_SCANID = 10001;
+
+ /**
+ * The scan has been interrupted by another scan with higher priority.
+ */
+ public static final int ERROR_INTERRUPTED = 10002;
+
private final int mScanId;
private final int mSubId;
/**
* Stops the network scan
*
- * This is the callback method to stop an ongoing scan. When user requests a new scan,
- * a NetworkScan object will be returned, and the user can stop the scan by calling this
- * method.
+ * Use this method to stop an ongoing scan. When user requests a new scan, a {@link NetworkScan}
+ * object will be returned, and the user can stop the scan by calling this method.
*/
public void stop() throws RemoteException {
try {
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
index 9674c93..ea503c3 100644
--- a/telephony/java/android/telephony/NetworkScanRequest.java
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -16,11 +16,14 @@
package android.telephony;
+import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Arrays;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Defines a request to peform a network scan.
@@ -28,7 +31,6 @@
* This class defines whether the network scan will be performed only once or periodically until
* cancelled, when the scan is performed periodically, the time interval is not controlled by the
* user but defined by the modem vendor.
- * @hide
*/
public final class NetworkScanRequest implements Parcelable {
@@ -54,6 +56,14 @@
/** @hide */
public static final int MAX_INCREMENTAL_PERIODICITY_SEC = 10;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ SCAN_TYPE_ONE_SHOT,
+ SCAN_TYPE_PERIODIC,
+ })
+ public @interface ScanType {}
+
/** Performs the scan only once */
public static final int SCAN_TYPE_ONE_SHOT = 0;
/**
@@ -65,21 +75,21 @@
public static final int SCAN_TYPE_PERIODIC = 1;
/** Defines the type of the scan. */
- public int scanType;
+ private int mScanType;
/**
* Search periodicity (in seconds).
* Expected range for the input is [5s - 300s]
- * This value must be less than or equal to maxSearchTime
+ * This value must be less than or equal to mMaxSearchTime
*/
- public int searchPeriodicity;
+ private int mSearchPeriodicity;
/**
* Maximum duration of the periodic search (in seconds).
* Expected range for the input is [60s - 3600s]
* If the search lasts this long, it will be terminated.
*/
- public int maxSearchTime;
+ private int mMaxSearchTime;
/**
* Indicates whether the modem should report incremental
@@ -87,18 +97,18 @@
* FALSE – Incremental results are not reported.
* TRUE (default) – Incremental results are reported
*/
- public boolean incrementalResults;
+ private boolean mIncrementalResults;
/**
* Indicates the periodicity with which the modem should
* report incremental results to the client (in seconds).
* Expected range for the input is [1s - 10s]
- * This value must be less than or equal to maxSearchTime
+ * This value must be less than or equal to mMaxSearchTime
*/
- public int incrementalResultsPeriodicity;
+ private int mIncrementalResultsPeriodicity;
/** Describes the radio access technologies with bands or channels that need to be scanned. */
- public RadioAccessSpecifier[] specifiers;
+ private RadioAccessSpecifier[] mSpecifiers;
/**
* Describes the List of PLMN ids (MCC-MNC)
@@ -107,20 +117,24 @@
* If list not sent, search to be completed till end and all PLMNs found to be reported.
* Max size of array is MAX_MCC_MNC_LIST_SIZE
*/
- public ArrayList<String> mccMncs;
+ private ArrayList<String> mMccMncs;
/**
- * Creates a new NetworkScanRequest with scanType and network specifiers
+ * Creates a new NetworkScanRequest with mScanType and network mSpecifiers
*
- * @param scanType The type of the scan
+ * @param scanType The type of the scan, can be either one shot or periodic
* @param specifiers the radio network with bands / channels to be scanned
- * @param searchPeriodicity Search periodicity (in seconds)
- * @param maxSearchTime Maximum duration of the periodic search (in seconds)
+ * @param searchPeriodicity The modem will restart the scan every searchPeriodicity seconds if
+ * no network has been found, until it reaches the maxSearchTime. Only
+ * valid when scan type is periodic scan.
+ * @param maxSearchTime Maximum duration of the search (in seconds)
* @param incrementalResults Indicates whether the modem should report incremental
* results of the network scan to the client
* @param incrementalResultsPeriodicity Indicates the periodicity with which the modem should
- * report incremental results to the client (in seconds)
- * @param mccMncs Describes the List of PLMN ids (MCC-MNC)
+ * report incremental results to the client (in seconds),
+ * only valid when incrementalResults is true
+ * @param mccMncs Describes the list of PLMN ids (MCC-MNC), once any network in the list has
+ * been found, the scan will be terminated by the modem.
*/
public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers,
int searchPeriodicity,
@@ -128,19 +142,63 @@
boolean incrementalResults,
int incrementalResultsPeriodicity,
ArrayList<String> mccMncs) {
- this.scanType = scanType;
- this.specifiers = specifiers;
- this.searchPeriodicity = searchPeriodicity;
- this.maxSearchTime = maxSearchTime;
- this.incrementalResults = incrementalResults;
- this.incrementalResultsPeriodicity = incrementalResultsPeriodicity;
- if (mccMncs != null) {
- this.mccMncs = mccMncs;
+ this.mScanType = scanType;
+ this.mSpecifiers = specifiers.clone();
+ this.mSearchPeriodicity = searchPeriodicity;
+ this.mMaxSearchTime = maxSearchTime;
+ this.mIncrementalResults = incrementalResults;
+ this.mIncrementalResultsPeriodicity = incrementalResultsPeriodicity;
+ if (mMccMncs != null) {
+ this.mMccMncs = (ArrayList<String>) mccMncs.clone();
} else {
- this.mccMncs = new ArrayList<>();
+ this.mMccMncs = new ArrayList<>();
}
}
+ /** Returns the type of the scan. */
+ @ScanType
+ public int getScanType() {
+ return mScanType;
+ }
+
+ /** Returns the search periodicity in seconds. */
+ public int getSearchPeriodicity() {
+ return mSearchPeriodicity;
+ }
+
+ /** Returns maximum duration of the periodic search in seconds. */
+ public int getMaxSearchTime() {
+ return mMaxSearchTime;
+ }
+
+ /**
+ * Returns whether incremental result is enabled.
+ * FALSE – Incremental results is not enabled.
+ * TRUE – Incremental results is reported.
+ */
+ public boolean getIncrementalResults() {
+ return mIncrementalResults;
+ }
+
+ /** Returns the periodicity in seconds of incremental results. */
+ public int getIncrementalResultsPeriodicity() {
+ return mIncrementalResultsPeriodicity;
+ }
+
+ /** Returns the radio access technologies with bands or channels that need to be scanned. */
+ public RadioAccessSpecifier[] getSpecifiers() {
+ return mSpecifiers.clone();
+ }
+
+ /**
+ * Returns the List of PLMN ids (MCC-MNC) for early termination of scan.
+ * If any PLMN of this list is found, search should end at that point and
+ * results with all PLMN found till that point should be sent as response.
+ */
+ public ArrayList<String> getPlmns() {
+ return (ArrayList<String>) mMccMncs.clone();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -148,26 +206,26 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(scanType);
- dest.writeParcelableArray(specifiers, flags);
- dest.writeInt(searchPeriodicity);
- dest.writeInt(maxSearchTime);
- dest.writeBoolean(incrementalResults);
- dest.writeInt(incrementalResultsPeriodicity);
- dest.writeStringList(mccMncs);
+ dest.writeInt(mScanType);
+ dest.writeParcelableArray(mSpecifiers, flags);
+ dest.writeInt(mSearchPeriodicity);
+ dest.writeInt(mMaxSearchTime);
+ dest.writeBoolean(mIncrementalResults);
+ dest.writeInt(mIncrementalResultsPeriodicity);
+ dest.writeStringList(mMccMncs);
}
private NetworkScanRequest(Parcel in) {
- scanType = in.readInt();
- specifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
+ mScanType = in.readInt();
+ mSpecifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
Object.class.getClassLoader(),
RadioAccessSpecifier.class);
- searchPeriodicity = in.readInt();
- maxSearchTime = in.readInt();
- incrementalResults = in.readBoolean();
- incrementalResultsPeriodicity = in.readInt();
- mccMncs = new ArrayList<>();
- in.readStringList(mccMncs);
+ mSearchPeriodicity = in.readInt();
+ mMaxSearchTime = in.readInt();
+ mIncrementalResults = in.readBoolean();
+ mIncrementalResultsPeriodicity = in.readInt();
+ mMccMncs = new ArrayList<>();
+ in.readStringList(mMccMncs);
}
@Override
@@ -184,25 +242,25 @@
return false;
}
- return (scanType == nsr.scanType
- && Arrays.equals(specifiers, nsr.specifiers)
- && searchPeriodicity == nsr.searchPeriodicity
- && maxSearchTime == nsr.maxSearchTime
- && incrementalResults == nsr.incrementalResults
- && incrementalResultsPeriodicity == nsr.incrementalResultsPeriodicity
- && (((mccMncs != null)
- && mccMncs.equals(nsr.mccMncs))));
+ return (mScanType == nsr.mScanType
+ && Arrays.equals(mSpecifiers, nsr.mSpecifiers)
+ && mSearchPeriodicity == nsr.mSearchPeriodicity
+ && mMaxSearchTime == nsr.mMaxSearchTime
+ && mIncrementalResults == nsr.mIncrementalResults
+ && mIncrementalResultsPeriodicity == nsr.mIncrementalResultsPeriodicity
+ && (((mMccMncs != null)
+ && mMccMncs.equals(nsr.mMccMncs))));
}
@Override
public int hashCode () {
- return ((scanType * 31)
- + (Arrays.hashCode(specifiers)) * 37
- + (searchPeriodicity * 41)
- + (maxSearchTime * 43)
- + ((incrementalResults == true? 1 : 0) * 47)
- + (incrementalResultsPeriodicity * 53)
- + (mccMncs.hashCode() * 59));
+ return ((mScanType * 31)
+ + (Arrays.hashCode(mSpecifiers)) * 37
+ + (mSearchPeriodicity * 41)
+ + (mMaxSearchTime * 43)
+ + ((mIncrementalResults == true? 1 : 0) * 47)
+ + (mIncrementalResultsPeriodicity * 53)
+ + (mMccMncs.hashCode() * 59));
}
public static final Creator<NetworkScanRequest> CREATOR =
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.java b/telephony/java/android/telephony/RadioAccessSpecifier.java
index 33ce8b4..5412c61 100644
--- a/telephony/java/android/telephony/RadioAccessSpecifier.java
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.java
@@ -25,34 +25,40 @@
* Describes a particular radio access network to be scanned.
*
* The scan can be performed on either bands or channels for a specific radio access network type.
- * @hide
*/
public final class RadioAccessSpecifier implements Parcelable {
/**
* The radio access network that needs to be scanned
*
+ * This parameter must be provided or else the scan will be rejected.
+ *
* See {@link RadioNetworkConstants.RadioAccessNetworks} for details.
*/
- public int radioAccessNetwork;
+ private int mRadioAccessNetwork;
/**
* The frequency bands that need to be scanned
*
- * bands must be used together with radioAccessNetwork
+ * When no specific bands are specified (empty array or null), all the frequency bands
+ * supported by the modem will be scanned.
*
* See {@link RadioNetworkConstants} for details.
*/
- public int[] bands;
+ private int[] mBands;
/**
* The frequency channels that need to be scanned
*
- * channels must be used together with radioAccessNetwork
+ * When any specific channels are provided for scan, the corresponding frequency bands that
+ * contains those channels must also be provided, or else the channels will be ignored.
*
- * See {@link RadioNetworkConstants.RadioAccessNetworks} for details.
+ * When no specific channels are specified (empty array or null), all the frequency channels
+ * supported by the modem will be scanned.
+ *
+ * See {@link RadioNetworkConstants} for details.
*/
- public int[] channels;
+ private int[] mChannels;
/**
* Creates a new RadioAccessSpecifier with radio network, bands and channels
@@ -65,9 +71,34 @@
* @param channels the frequency bands to be scanned
*/
public RadioAccessSpecifier(int ran, int[] bands, int[] channels) {
- this.radioAccessNetwork = ran;
- this.bands = bands;
- this.channels = channels;
+ this.mRadioAccessNetwork = ran;
+ this.mBands = bands.clone();
+ this.mChannels = channels.clone();
+ }
+
+ /**
+ * Returns the radio access network that needs to be scanned.
+ *
+ * The returned value is define in {@link RadioNetworkConstants.RadioAccessNetworks};
+ */
+ public int getRadioAccessNetwork() {
+ return mRadioAccessNetwork;
+ }
+
+ /**
+ * Returns the frequency bands that need to be scanned.
+ *
+ * The returned value is defined in either of {@link RadioNetworkConstants.GeranBands},
+ * {@link RadioNetworkConstants.UtranBands} and {@link RadioNetworkConstants.EutranBands}, and
+ * it depends on the returned value of {@link #getRadioAccessNetwork()}.
+ */
+ public int[] getBands() {
+ return mBands.clone();
+ }
+
+ /** Returns the frequency channels that need to be scanned. */
+ public int[] getChannels() {
+ return mChannels.clone();
}
public static final Parcelable.Creator<RadioAccessSpecifier> CREATOR =
@@ -90,15 +121,15 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(radioAccessNetwork);
- dest.writeIntArray(bands);
- dest.writeIntArray(channels);
+ dest.writeInt(mRadioAccessNetwork);
+ dest.writeIntArray(mBands);
+ dest.writeIntArray(mChannels);
}
private RadioAccessSpecifier(Parcel in) {
- radioAccessNetwork = in.readInt();
- bands = in.createIntArray();
- channels = in.createIntArray();
+ mRadioAccessNetwork = in.readInt();
+ mBands = in.createIntArray();
+ mChannels = in.createIntArray();
}
@Override
@@ -115,15 +146,15 @@
return false;
}
- return (radioAccessNetwork == ras.radioAccessNetwork
- && Arrays.equals(bands, ras.bands)
- && Arrays.equals(channels, ras.channels));
+ return (mRadioAccessNetwork == ras.mRadioAccessNetwork
+ && Arrays.equals(mBands, ras.mBands)
+ && Arrays.equals(mChannels, ras.mChannels));
}
@Override
public int hashCode () {
- return ((radioAccessNetwork * 31)
- + (Arrays.hashCode(bands) * 37)
- + (Arrays.hashCode(channels)) * 39);
+ return ((mRadioAccessNetwork * 31)
+ + (Arrays.hashCode(mBands) * 37)
+ + (Arrays.hashCode(mChannels)) * 39);
}
}
diff --git a/telephony/java/android/telephony/RadioNetworkConstants.java b/telephony/java/android/telephony/RadioNetworkConstants.java
index 1a9072d..5f5dd82 100644
--- a/telephony/java/android/telephony/RadioNetworkConstants.java
+++ b/telephony/java/android/telephony/RadioNetworkConstants.java
@@ -18,7 +18,6 @@
/**
* Contains radio access network related constants.
- * @hide
*/
public final class RadioNetworkConstants {
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 7ab75f5..5d88cf0 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -344,6 +344,7 @@
* </p>
*
* <p>Requires Permission:
+ * {@link android.Manifest.permission#SEND_SMS} and
* {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
* privileges.
* </p>
@@ -351,6 +352,10 @@
* @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
*/
@SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ android.Manifest.permission.SEND_SMS
+ })
public void sendTextMessageWithoutPersisting(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
@@ -1129,6 +1134,8 @@
// SMS send failure result codes
+ /** No error. {@hide}*/
+ static public final int RESULT_ERROR_NONE = 0;
/** Generic failure cause */
static public final int RESULT_ERROR_GENERIC_FAILURE = 1;
/** Failed because radio was explicitly turned off */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9e77992..af5b190 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3155,6 +3155,7 @@
* Initial SIM activation state, unknown. Not set by any carrier apps.
* @hide
*/
+ @SystemApi
public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0;
/**
@@ -3165,12 +3166,14 @@
* @see #SIM_ACTIVATION_STATE_RESTRICTED
* @hide
*/
+ @SystemApi
public static final int SIM_ACTIVATION_STATE_ACTIVATING = 1;
/**
* Indicate SIM has been successfully activated with full service
* @hide
*/
+ @SystemApi
public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2;
/**
@@ -3180,6 +3183,7 @@
* deactivated sim state and set it back to activated after successfully run activation service.
* @hide
*/
+ @SystemApi
public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3;
/**
@@ -3187,14 +3191,47 @@
* note this is currently available for data activation state. For example out of byte sim.
* @hide
*/
+ @SystemApi
public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4;
+ /** @hide */
+ @IntDef({
+ SIM_ACTIVATION_STATE_UNKNOWN,
+ SIM_ACTIVATION_STATE_ACTIVATING,
+ SIM_ACTIVATION_STATE_ACTIVATED,
+ SIM_ACTIVATION_STATE_DEACTIVATED,
+ SIM_ACTIVATION_STATE_RESTRICTED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SimActivationState{}
+
+ /**
+ * Sets the voice activation state
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * Or the calling app has carrier privileges.
+ *
+ * @param activationState The voice activation state
+ * @see #SIM_ACTIVATION_STATE_UNKNOWN
+ * @see #SIM_ACTIVATION_STATE_ACTIVATING
+ * @see #SIM_ACTIVATION_STATE_ACTIVATED
+ * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+ * @see #hasCarrierPrivileges
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setVoiceActivationState(@SimActivationState int activationState) {
+ setVoiceActivationState(getSubId(), activationState);
+ }
+
/**
* Sets the voice activation state for the given subscriber.
*
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
- * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+ * Or the calling app has carrier privileges.
*
* @param subId The subscription id.
* @param activationState The voice activation state of the given subscriber.
@@ -3202,24 +3239,48 @@
* @see #SIM_ACTIVATION_STATE_ACTIVATING
* @see #SIM_ACTIVATION_STATE_ACTIVATED
* @see #SIM_ACTIVATION_STATE_DEACTIVATED
+ * @see #hasCarrierPrivileges
* @hide
*/
- public void setVoiceActivationState(int subId, int activationState) {
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setVoiceActivationState(int subId, @SimActivationState int activationState) {
try {
- ITelephony telephony = getITelephony();
- if (telephony != null)
- telephony.setVoiceActivationState(subId, activationState);
- } catch (RemoteException ex) {
- } catch (NullPointerException ex) {
- }
+ ITelephony telephony = getITelephony();
+ if (telephony != null)
+ telephony.setVoiceActivationState(subId, activationState);
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ }
+
+ /**
+ * Sets the data activation state
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * Or the calling app has carrier privileges.
+ *
+ * @param activationState The data activation state
+ * @see #SIM_ACTIVATION_STATE_UNKNOWN
+ * @see #SIM_ACTIVATION_STATE_ACTIVATING
+ * @see #SIM_ACTIVATION_STATE_ACTIVATED
+ * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+ * @see #SIM_ACTIVATION_STATE_RESTRICTED
+ * @see #hasCarrierPrivileges
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setDataActivationState(@SimActivationState int activationState) {
+ setDataActivationState(getSubId(), activationState);
}
/**
* Sets the data activation state for the given subscriber.
*
* <p>Requires Permission:
- * {@link android.Manifest.permission#MODIFY_PHONE_STATE}
- * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * Or the calling app has carrier privileges.
*
* @param subId The subscription id.
* @param activationState The data activation state of the given subscriber.
@@ -3228,9 +3289,11 @@
* @see #SIM_ACTIVATION_STATE_ACTIVATED
* @see #SIM_ACTIVATION_STATE_DEACTIVATED
* @see #SIM_ACTIVATION_STATE_RESTRICTED
+ * @see #hasCarrierPrivileges
* @hide
*/
- public void setDataActivationState(int subId, int activationState) {
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setDataActivationState(int subId, @SimActivationState int activationState) {
try {
ITelephony telephony = getITelephony();
if (telephony != null)
@@ -3241,8 +3304,33 @@
}
/**
+ * Returns the voice activation state
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ * Or the calling app has carrier privileges.
+ *
+ * @return voiceActivationState
+ * @see #SIM_ACTIVATION_STATE_UNKNOWN
+ * @see #SIM_ACTIVATION_STATE_ACTIVATING
+ * @see #SIM_ACTIVATION_STATE_ACTIVATED
+ * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+ * @see #hasCarrierPrivileges
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @SimActivationState int getVoiceActivationState() {
+ return getVoiceActivationState(getSubId());
+ }
+
+ /**
* Returns the voice activation state for the given subscriber.
*
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ * Or the calling app has carrier privileges.
+ *
* @param subId The subscription id.
*
* @return voiceActivationState for the given subscriber
@@ -3250,10 +3338,11 @@
* @see #SIM_ACTIVATION_STATE_ACTIVATING
* @see #SIM_ACTIVATION_STATE_ACTIVATED
* @see #SIM_ACTIVATION_STATE_DEACTIVATED
+ * @see #hasCarrierPrivileges
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public int getVoiceActivationState(int subId) {
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @SimActivationState int getVoiceActivationState(int subId) {
try {
ITelephony telephony = getITelephony();
if (telephony != null)
@@ -3265,8 +3354,34 @@
}
/**
+ * Returns the data activation state
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ * Or the calling app has carrier privileges.
+ *
+ * @return dataActivationState for the given subscriber
+ * @see #SIM_ACTIVATION_STATE_UNKNOWN
+ * @see #SIM_ACTIVATION_STATE_ACTIVATING
+ * @see #SIM_ACTIVATION_STATE_ACTIVATED
+ * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+ * @see #SIM_ACTIVATION_STATE_RESTRICTED
+ * @see #hasCarrierPrivileges
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @SimActivationState int getDataActivationState() {
+ return getDataActivationState(getSubId());
+ }
+
+ /**
* Returns the data activation state for the given subscriber.
*
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ * Or the calling app has carrier privileges.
+ *
* @param subId The subscription id.
*
* @return dataActivationState for the given subscriber
@@ -3275,10 +3390,11 @@
* @see #SIM_ACTIVATION_STATE_ACTIVATED
* @see #SIM_ACTIVATION_STATE_DEACTIVATED
* @see #SIM_ACTIVATION_STATE_RESTRICTED
+ * @see #hasCarrierPrivileges
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public int getDataActivationState(int subId) {
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @SimActivationState int getDataActivationState(int subId) {
try {
ITelephony telephony = getITelephony();
if (telephony != null)
@@ -4849,15 +4965,14 @@
* Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
* Or the calling app has carrier privileges. @see #hasCarrierPrivileges
- *
- * @hide
- * TODO: Add an overload that takes no args.
*/
- public void setNetworkSelectionModeAutomatic(int subId) {
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setNetworkSelectionModeAutomatic() {
try {
ITelephony telephony = getITelephony();
- if (telephony != null)
- telephony.setNetworkSelectionModeAutomatic(subId);
+ if (telephony != null) {
+ telephony.setNetworkSelectionModeAutomatic(getSubId());
+ }
} catch (RemoteException ex) {
Rlog.e(TAG, "setNetworkSelectionModeAutomatic RemoteException", ex);
} catch (NullPointerException ex) {
@@ -4905,9 +5020,9 @@
*
* @param request Contains all the RAT with bands/channels that need to be scanned.
* @param callback Returns network scan results or errors.
- * @return A NetworkScan obj which contains a callback which can stop the scan.
- * @hide
+ * @return A NetworkScan obj which contains a callback which can be used to stop the scan.
*/
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public NetworkScan requestNetworkScan(
NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) {
synchronized (this) {
@@ -4926,15 +5041,20 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
* Or the calling app has carrier privileges. @see #hasCarrierPrivileges
*
- * @hide
- * TODO: Add an overload that takes no args.
+ * @param operatorNumeric the PLMN ID of the network to select.
+ * @param persistSelection whether the selection will persist until reboot. If true, only allows
+ * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume
+ * normal network selection next time.
+ * @return true on success; false on any failure.
*/
- public boolean setNetworkSelectionModeManual(int subId, OperatorInfo operator,
- boolean persistSelection) {
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public boolean setNetworkSelectionModeManual(String operatorNumeric, boolean persistSelection) {
try {
ITelephony telephony = getITelephony();
- if (telephony != null)
- return telephony.setNetworkSelectionModeManual(subId, operator, persistSelection);
+ if (telephony != null) {
+ return telephony.setNetworkSelectionModeManual(
+ getSubId(), operatorNumeric, persistSelection);
+ }
} catch (RemoteException ex) {
Rlog.e(TAG, "setNetworkSelectionModeManual RemoteException", ex);
} catch (NullPointerException ex) {
@@ -4959,8 +5079,9 @@
public boolean setPreferredNetworkType(int subId, int networkType) {
try {
ITelephony telephony = getITelephony();
- if (telephony != null)
+ if (telephony != null) {
return telephony.setPreferredNetworkType(subId, networkType);
+ }
} catch (RemoteException ex) {
Rlog.e(TAG, "setPreferredNetworkType RemoteException", ex);
} catch (NullPointerException ex) {
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 7bcdcdc..c182e34 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -38,7 +38,6 @@
/**
* Manages the radio access network scan requests and callbacks.
- * @hide
*/
public final class TelephonyScanManager {
@@ -55,7 +54,8 @@
public static final int CALLBACK_SCAN_COMPLETE = 3;
/**
- * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} should
+ * The caller of
+ * {@link TelephonyManager#requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} should
* implement and provide this callback so that the scan results or errors can be returned.
*/
public static abstract class NetworkScanCallback {
@@ -75,8 +75,10 @@
*
* This callback will be called whenever there is any error about the scan, and the scan
* will be terminated. onComplete() will NOT be called.
+ *
+ * @param error Error code when the scan is failed, as defined in {@link NetworkScan}.
*/
- public void onError(int error) {}
+ public void onError(@NetworkScan.ScanErrorCode int error) {}
}
private static class NetworkScanInfo {
diff --git a/telephony/java/android/telephony/ims/feature/SmsFeature.java b/telephony/java/android/telephony/ims/feature/SmsFeature.java
deleted file mode 100644
index c1366db..0000000
--- a/telephony/java/android/telephony/ims/feature/SmsFeature.java
+++ /dev/null
@@ -1,237 +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 android.telephony.ims.feature;
-
-import android.annotation.SystemApi;
-import android.os.RemoteException;
-import com.android.ims.internal.IImsSmsFeature;
-import com.android.ims.internal.ISmsListener;
-
-/**
- * Base implementation of SMS over IMS functionality.
- *
- * @hide
- */
-public class SmsFeature extends ImsFeature {
- /**
- * SMS over IMS format is 3gpp.
- */
- public static final int IMS_SMS_FORMAT_3GPP = 1;
-
- /**
- * SMS over IMS format is 3gpp2.
- */
- public static final int IMS_SMS_FORMAT_3GPP2 = 2;
-
- /**
- * Message was sent successfully.
- */
- public static final int SEND_STATUS_OK = 1;
-
- /**
- * IMS provider failed to send the message and platform should not retry falling back to sending
- * the message using the radio.
- */
- public static final int SEND_STATUS_ERROR = 2;
-
- /**
- * IMS provider failed to send the message and platform should retry again after setting TP-RD bit
- * to high.
- */
- public static final int SEND_STATUS_ERROR_RETRY = 3;
-
- /**
- * IMS provider failed to send the message and platform should retry falling back to sending
- * the message using the radio.
- */
- public static final int SEND_STATUS_ERROR_FALLBACK = 4;
-
- /**
- * Message was delivered successfully.
- */
- public static final int DELIVER_STATUS_OK = 1;
-
- /**
- * Message was not delivered.
- */
- public static final int DELIVER_STATUS_ERROR = 2;
-
- // Lock for feature synchronization
- private final Object mLock = new Object();
- private ISmsListener mSmsListener;
-
- private final IImsSmsFeature mIImsSmsBinder = new IImsSmsFeature.Stub() {
- @Override
- public void registerSmsListener(ISmsListener listener) {
- synchronized (mLock) {
- SmsFeature.this.registerSmsListener(listener);
- }
- }
-
- @Override
- public void sendSms(int format, int messageRef, boolean retry, byte[] pdu) {
- synchronized (mLock) {
- SmsFeature.this.sendSms(format, messageRef, retry, pdu);
- }
- }
-
- @Override
- public void acknowledgeSms(int messageRef, int result) {
- synchronized (mLock) {
- SmsFeature.this.acknowledgeSms(messageRef, result);
- }
- }
-
- @Override
- public int getSmsFormat() {
- synchronized (mLock) {
- return SmsFeature.this.getSmsFormat();
- }
- }
- };
-
- /**
- * Registers a listener responsible for handling tasks like delivering messages.
-
- * @param listener listener to register.
- *
- * @hide
- */
- @SystemApi
- public final void registerSmsListener(ISmsListener listener) {
- synchronized (mLock) {
- mSmsListener = listener;
- }
- }
-
- /**
- * This method will be triggered by the platform when the user attempts to send an SMS. This
- * method should be implemented by the IMS providers to provide implementation of sending an SMS
- * over IMS.
- *
- * @param format the format of the message. One of {@link #IMS_SMS_FORMAT_3GPP} or
- * {@link #IMS_SMS_FORMAT_3GPP2}
- * @param messageRef the message reference.
- * @param retry whether it is a retry of an already attempted message or not.
- * @param pdu PDUs representing the contents of the message.
- */
- public void sendSms(int format, int messageRef, boolean isRetry, byte[] pdu) {
- }
-
- /**
- * This method will be triggered by the platform after {@link #deliverSms(int, byte[])} has been
- * called to deliver the result to the IMS provider. It will also be triggered after
- * {@link #setSentSmsResult(int, int)} has been called to provide the result of the operation.
- *
- * @param result Should be {@link #DELIVER_STATUS_OK} if the message was delivered successfully,
- * {@link #DELIVER_STATUS_ERROR} otherwise.
- * @param messageRef the message reference.
- */
- public void acknowledgeSms(int messageRef, int result) {
-
- }
-
- /**
- * This method should be triggered by the IMS providers when there is an incoming message. The
- * platform will deliver the message to the messages database and notify the IMS provider of the
- * result by calling {@link #acknowledgeSms(int)}.
- *
- * This method must not be called before {@link #onFeatureReady()} is called.
- *
- * @param format the format of the message.One of {@link #IMS_SMS_FORMAT_3GPP} or
- * {@link #IMS_SMS_FORMAT_3GPP2}
- * @param pdu PDUs representing the contents of the message.
- * @throws IllegalStateException if called before {@link #onFeatureReady()}
- */
- public final void deliverSms(int format, byte[] pdu) throws IllegalStateException {
- // TODO: Guard against NPE/ Check if feature is ready and thrown an exception
- // otherwise.
- try {
- mSmsListener.deliverSms(format, pdu);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * This method should be triggered by the IMS providers to pass the result of the sent message
- * to the platform.
- *
- * This method must not be called before {@link #onFeatureReady()} is called.
- *
- * @param messageRef the message reference.
- * @param result One of {@link #SEND_STATUS_OK}, {@link #SEND_STATUS_ERROR},
- * {@link #SEND_STATUS_ERROR_RETRY}, {@link #SEND_STATUS_ERROR_FALLBACK}
- * @throws IllegalStateException if called before {@link #onFeatureReady()}
- */
- public final void setSentSmsResult(int messageRef, int result) throws IllegalStateException {
- // TODO: Guard against NPE/ Check if feature is ready and thrown an exception
- // otherwise.
- try {
- mSmsListener.setSentSmsResult(messageRef, result);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * Sets the status report of the sent message.
- *
- * @param format Should be {@link #IMS_SMS_FORMAT_3GPP} or {@link #IMS_SMS_FORMAT_3GPP2}
- * @param pdu PDUs representing the content of the status report.
- * @throws IllegalStateException if called before {@link #onFeatureReady()}
- */
- public final void setSentSmsStatusReport(int format, byte[] pdu) {
- // TODO: Guard against NPE/ Check if feature is ready and thrown an exception
- // otherwise.
- try {
- mSmsListener.setSentSmsStatusReport(format, pdu);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * Returns the SMS format. Default is {@link #IMS_SMS_FORMAT_3GPP} unless overridden by IMS
- * Provider.
- *
- * @return sms format.
- */
- public int getSmsFormat() {
- return IMS_SMS_FORMAT_3GPP;
- }
-
- /**
- * {@inheritDoc}
- */
- public void onFeatureReady() {
-
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onFeatureRemoved() {
-
- }
-
- /**
- * @hide
- */
- @Override
- public final IImsSmsFeature getBinder() {
- return mIImsSmsBinder;
- }
-}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/internal/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
new file mode 100644
index 0000000..47414cf
--- /dev/null
+++ b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.ims.internal;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.os.RemoteException;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+import android.telephony.ims.internal.aidl.IImsSmsListener;
+import android.telephony.ims.internal.feature.MmTelFeature;
+import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base implementation for SMS over IMS.
+ *
+ * Any service wishing to provide SMS over IMS should extend this class and implement all methods
+ * that the service supports.
+ * @hide
+ */
+public class SmsImplBase {
+ private static final String LOG_TAG = "SmsImplBase";
+
+ @IntDef({
+ SEND_STATUS_OK,
+ SEND_STATUS_ERROR,
+ SEND_STATUS_ERROR_RETRY,
+ SEND_STATUS_ERROR_FALLBACK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SendStatusResult {}
+ /**
+ * Message was sent successfully.
+ */
+ public static final int SEND_STATUS_OK = 1;
+
+ /**
+ * IMS provider failed to send the message and platform should not retry falling back to sending
+ * the message using the radio.
+ */
+ public static final int SEND_STATUS_ERROR = 2;
+
+ /**
+ * IMS provider failed to send the message and platform should retry again after setting TP-RD bit
+ * to high.
+ */
+ public static final int SEND_STATUS_ERROR_RETRY = 3;
+
+ /**
+ * IMS provider failed to send the message and platform should retry falling back to sending
+ * the message using the radio.
+ */
+ public static final int SEND_STATUS_ERROR_FALLBACK = 4;
+
+ @IntDef({
+ DELIVER_STATUS_OK,
+ DELIVER_STATUS_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeliverStatusResult {}
+ /**
+ * Message was delivered successfully.
+ */
+ public static final int DELIVER_STATUS_OK = 1;
+
+ /**
+ * Message was not delivered.
+ */
+ public static final int DELIVER_STATUS_ERROR = 2;
+
+ @IntDef({
+ STATUS_REPORT_STATUS_OK,
+ STATUS_REPORT_STATUS_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StatusReportResult {}
+
+ /**
+ * Status Report was set successfully.
+ */
+ public static final int STATUS_REPORT_STATUS_OK = 1;
+
+ /**
+ * Error while setting status report.
+ */
+ public static final int STATUS_REPORT_STATUS_ERROR = 2;
+
+
+ // Lock for feature synchronization
+ private final Object mLock = new Object();
+ private IImsSmsListener mListener;
+
+ /**
+ * Registers a listener responsible for handling tasks like delivering messages.
+ *
+ * @param listener listener to register.
+ *
+ * @hide
+ */
+ public final void registerSmsListener(IImsSmsListener listener) {
+ synchronized (mLock) {
+ mListener = listener;
+ }
+ }
+
+ /**
+ * This method will be triggered by the platform when the user attempts to send an SMS. This
+ * method should be implemented by the IMS providers to provide implementation of sending an SMS
+ * over IMS.
+ *
+ * @param smsc the Short Message Service Center address.
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param messageRef the message reference.
+ * @param isRetry whether it is a retry of an already attempted message or not.
+ * @param pdu PDUs representing the contents of the message.
+ */
+ public void sendSms(int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) {
+ // Base implementation returns error. Should be overridden.
+ try {
+ onSendSmsResult(messageRef, SEND_STATUS_ERROR, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can not send sms: " + e.getMessage());
+ }
+ }
+
+ /**
+ * This method will be triggered by the platform after {@link #onSmsReceived(String, byte[])} has
+ * been called to deliver the result to the IMS provider.
+ *
+ * @param result result of delivering the message. Valid values are defined in
+ * {@link DeliverStatusResult}
+ * @param messageRef the message reference or -1 of unavailable.
+ */
+ public void acknowledgeSms(int messageRef, @DeliverStatusResult int result) {
+
+ }
+
+ /**
+ * This method will be triggered by the platform after
+ * {@link #onSmsStatusReportReceived(int, int, byte[])} has been called to provide the result to
+ * the IMS provider.
+ *
+ * @param result result of delivering the message. Valid values are defined in
+ * {@link StatusReportResult}
+ * @param messageRef the message reference or -1 of unavailable.
+ */
+ public void acknowledgeSmsReport(int messageRef, @StatusReportResult int result) {
+
+ }
+
+ /**
+ * This method should be triggered by the IMS providers when there is an incoming message. The
+ * platform will deliver the message to the messages database and notify the IMS provider of the
+ * result by calling {@link #acknowledgeSms(int, int)}.
+ *
+ * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
+ *
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param pdu PDUs representing the contents of the message.
+ * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+ */
+ public final void onSmsReceived(String format, byte[] pdu) throws IllegalStateException {
+ synchronized (mLock) {
+ if (mListener == null) {
+ throw new IllegalStateException("Feature not ready.");
+ }
+ try {
+ mListener.onSmsReceived(format, pdu);
+ acknowledgeSms(-1, DELIVER_STATUS_OK);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage());
+ acknowledgeSms(-1, DELIVER_STATUS_ERROR);
+ }
+ }
+ }
+
+ /**
+ * This method should be triggered by the IMS providers to pass the result of the sent message
+ * to the platform.
+ *
+ * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
+ *
+ * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+ * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult}
+ * @param reason reason in case status is failure. Valid values are:
+ * {@link SmsManager#RESULT_ERROR_NONE},
+ * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
+ * {@link SmsManager#RESULT_ERROR_RADIO_OFF},
+ * {@link SmsManager#RESULT_ERROR_NULL_PDU},
+ * {@link SmsManager#RESULT_ERROR_NO_SERVICE},
+ * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
+ * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
+ * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED}
+ * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+ * @throws RemoteException if the connection to the framework is not available. If this happens
+ * attempting to send the SMS should be aborted.
+ */
+ public final void onSendSmsResult(int messageRef, @SendStatusResult int status, int reason)
+ throws IllegalStateException, RemoteException {
+ synchronized (mLock) {
+ if (mListener == null) {
+ throw new IllegalStateException("Feature not ready.");
+ }
+ mListener.onSendSmsResult(messageRef, status, reason);
+ }
+ }
+
+ /**
+ * Sets the status report of the sent message.
+ *
+ * @param messageRef the message reference.
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param pdu PDUs representing the content of the status report.
+ * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+ */
+ public final void onSmsStatusReportReceived(int messageRef, String format, byte[] pdu) {
+ synchronized (mLock) {
+ if (mListener == null) {
+ throw new IllegalStateException("Feature not ready.");
+ }
+ try {
+ mListener.onSmsStatusReportReceived(messageRef, format, pdu);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
+ acknowledgeSmsReport(messageRef, STATUS_REPORT_STATUS_ERROR);
+ }
+ }
+ }
+
+ /**
+ * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
+ * Provider.
+ *
+ * @return the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ */
+ public String getSmsFormat() {
+ return SmsMessage.FORMAT_3GPP;
+ }
+
+}
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
index 7125781..d976686 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
@@ -18,6 +18,7 @@
import android.os.Message;
import android.telephony.ims.internal.aidl.IImsMmTelListener;
+import android.telephony.ims.internal.aidl.IImsSmsListener;
import android.telephony.ims.internal.aidl.IImsCapabilityCallback;
import android.telephony.ims.internal.aidl.IImsCallSessionListener;
import android.telephony.ims.internal.feature.CapabilityChangeRequest;
@@ -30,7 +31,7 @@
import com.android.ims.internal.IImsUt;
/**
- * See MmTelFeature for more information.
+ * See SmsImplBase for more information.
* {@hide}
*/
interface IImsMmTelFeature {
@@ -49,4 +50,10 @@
IImsCapabilityCallback c);
oneway void queryCapabilityConfiguration(int capability, int radioTech,
IImsCapabilityCallback c);
+ // SMS APIs
+ void setSmsListener(IImsSmsListener l);
+ oneway void sendSms(int messageRef, String format, String smsc, boolean retry, in byte[] pdu);
+ oneway void acknowledgeSms(int messageRef, int result);
+ oneway void acknowledgeSmsReport(int messageRef, int result);
+ String getSmsFormat();
}
diff --git a/telephony/java/com/android/ims/internal/ISmsListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
similarity index 65%
rename from telephony/java/com/android/ims/internal/ISmsListener.aidl
rename to telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
index 1266f04..468629a 100644
--- a/telephony/java/com/android/ims/internal/ISmsListener.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.ims.internal;
+package android.telephony.ims.internal.aidl;
/**
- * See SmsFeature for more information.
+ * See MMTelFeature for more information.
* {@hide}
*/
-interface ISmsListener {
- void setSentSmsResult(in int messageRef, in int result);
- void setSentSmsStatusReport(in int format, in byte[] pdu);
- void deliverSms(in int format, in byte[] pdu);
+interface IImsSmsListener {
+ void onSendSmsResult(in int messageRef, in int status, in int reason);
+ void onSmsStatusReportReceived(in int messageRef, in String format, in byte[] pdu);
+ void onSmsReceived(in String format, in byte[] pdu);
}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
index f183a57..2f350c8 100644
--- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
@@ -21,11 +21,15 @@
import android.os.RemoteException;
import android.telecom.TelecomManager;
import android.telephony.ims.internal.ImsCallSessionListener;
+import android.telephony.ims.internal.SmsImplBase;
+import android.telephony.ims.internal.SmsImplBase.DeliverStatusResult;
+import android.telephony.ims.internal.SmsImplBase.StatusReportResult;
import android.telephony.ims.internal.aidl.IImsCallSessionListener;
import android.telephony.ims.internal.aidl.IImsCapabilityCallback;
import android.telephony.ims.internal.aidl.IImsMmTelFeature;
import android.telephony.ims.internal.aidl.IImsMmTelListener;
import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
+import android.telephony.ims.internal.aidl.IImsSmsListener;
import android.telephony.ims.stub.ImsEcbmImplBase;
import android.telephony.ims.stub.ImsMultiEndpointImplBase;
import android.telephony.ims.stub.ImsUtImplBase;
@@ -64,6 +68,11 @@
}
@Override
+ public void setSmsListener(IImsSmsListener l) throws RemoteException {
+ MmTelFeature.this.setSmsListener(l);
+ }
+
+ @Override
public int getFeatureState() throws RemoteException {
synchronized (mLock) {
return MmTelFeature.this.getFeatureState();
@@ -143,6 +152,34 @@
IImsCapabilityCallback c) {
queryCapabilityConfigurationInternal(capability, radioTech, c);
}
+
+ @Override
+ public void sendSms(int messageRef, String format, String smsc, boolean retry, byte[] pdu) {
+ synchronized (mLock) {
+ MmTelFeature.this.sendSms(messageRef, format, smsc, retry, pdu);
+ }
+ }
+
+ @Override
+ public void acknowledgeSms(int messageRef, int result) {
+ synchronized (mLock) {
+ MmTelFeature.this.acknowledgeSms(messageRef, result);
+ }
+ }
+
+ @Override
+ public void acknowledgeSmsReport(int messageRef, int result) {
+ synchronized (mLock) {
+ MmTelFeature.this.acknowledgeSmsReport(messageRef, result);
+ }
+ }
+
+ @Override
+ public String getSmsFormat() {
+ synchronized (mLock) {
+ return MmTelFeature.this.getSmsFormat();
+ }
+ }
};
/**
@@ -171,7 +208,8 @@
value = {
CAPABILITY_TYPE_VOICE,
CAPABILITY_TYPE_VIDEO,
- CAPABILITY_TYPE_UT
+ CAPABILITY_TYPE_UT,
+ CAPABILITY_TYPE_SMS
})
@Retention(RetentionPolicy.SOURCE)
public @interface MmTelCapability {}
@@ -191,6 +229,11 @@
*/
public static final int CAPABILITY_TYPE_UT = 1 << 2;
+ /**
+ * This MmTelFeature supports SMS (IR.92)
+ */
+ public static final int CAPABILITY_TYPE_SMS = 1 << 3;
+
@Override
public final void addCapabilities(@MmTelCapability int capabilities) {
super.addCapabilities(capabilities);
@@ -239,6 +282,10 @@
}
}
+ private void setSmsListener(IImsSmsListener listener) {
+ getSmsImplementation().registerSmsListener(listener);
+ }
+
private void queryCapabilityConfigurationInternal(int capability, int radioTech,
IImsCapabilityCallback c) {
boolean enabled = queryCapabilityConfiguration(capability, radioTech);
@@ -400,6 +447,32 @@
// Base Implementation - Should be overridden
}
+ private void sendSms(int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) {
+ getSmsImplementation().sendSms(messageRef, format, smsc, isRetry, pdu);
+ }
+
+ private void acknowledgeSms(int messageRef, @DeliverStatusResult int result) {
+ getSmsImplementation().acknowledgeSms(messageRef, result);
+ }
+
+ private void acknowledgeSmsReport(int messageRef, @StatusReportResult int result) {
+ getSmsImplementation().acknowledgeSmsReport(messageRef, result);
+ }
+
+ private String getSmsFormat() {
+ return getSmsImplementation().getSmsFormat();
+ }
+
+ /**
+ * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default
+ * non-functional implementation is returned.
+ *
+ * @return an instance of {@link SmsImplBase} which should be implemented by the IMS Provider.
+ */
+ protected SmsImplBase getSmsImplementation() {
+ return new SmsImplBase();
+ }
+
/**{@inheritDoc}*/
@Override
public void onFeatureRemoved() {
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index 8529f52..f78e7a6 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -51,8 +51,8 @@
/** @hide */
public ServiceInfo(Map<Locale, String> newNames, String newClassName, List<Locale> newLocales,
String newServiceId, Date start, Date end) {
- if (newNames == null || newNames.isEmpty() || TextUtils.isEmpty(newClassName)
- || newLocales == null || newLocales.isEmpty() || TextUtils.isEmpty(newServiceId)
+ if (newNames == null || newClassName == null
+ || newLocales == null || newServiceId == null
|| start == null || end == null) {
throw new IllegalArgumentException("Bad ServiceInfo construction");
}
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java
index 6ad54c1..4f6f68c 100644
--- a/telephony/java/com/android/ims/ImsReasonInfo.java
+++ b/telephony/java/com/android/ims/ImsReasonInfo.java
@@ -104,6 +104,9 @@
// MT : No action from user after alerting the call
public static final int CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE = 203;
+ //Call was blocked by call barring
+ public static final int CODE_CALL_BARRED = 240;
+
//Call failures for FDN
public static final int CODE_FDN_BLOCKED = 241;
diff --git a/telephony/java/com/android/ims/internal/IImsSmsFeature.aidl b/telephony/java/com/android/ims/internal/IImsSmsFeature.aidl
deleted file mode 100644
index 5068128..0000000
--- a/telephony/java/com/android/ims/internal/IImsSmsFeature.aidl
+++ /dev/null
@@ -1,31 +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.ims.internal;
-
-import com.android.ims.internal.ISmsListener;
-
-/**
- * See SmsFeature for more information.
- *
- * {@hide}
- */
-interface IImsSmsFeature {
- void registerSmsListener(in ISmsListener listener);
- void sendSms(in int format, in int messageRef, in boolean retry, in byte[] pdu);
- void acknowledgeSms(in int messageRef, in int result);
- int getSmsFormat();
-}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 8ea53a5..416146f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -846,13 +846,13 @@
* Ask the radio to connect to the input network and change selection mode to manual.
*
* @param subId the id of the subscription.
- * @param operatorInfo the operator to attach to.
- * @param persistSelection should the selection persist till reboot or its
- * turned off? Will also result in notification being not shown to
- * the user if the signal is lost.
+ * @param operatorNumeric the PLMN of the operator to attach to.
+ * @param persistSelection Whether the selection will persist until reboot. If true, only allows
+ * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume
+ * normal network selection next time.
* @return true if the request suceeded.
*/
- boolean setNetworkSelectionModeManual(int subId, in OperatorInfo operator,
+ boolean setNetworkSelectionModeManual(int subId, in String operatorNumeric,
boolean persistSelection);
/**
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index 99a82ad..9f8b3a8 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -32,6 +32,12 @@
public class IccUtils {
static final String LOG_TAG="IccUtils";
+ // A table mapping from a number to a hex character for fast encoding hex strings.
+ private static final char[] HEX_CHARS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+ };
+
+
/**
* Many fields in GSM SIM's are stored as nibble-swizzled BCD
*
@@ -62,6 +68,41 @@
}
/**
+ * Converts a bcd byte array to String with offset 0 and byte array length.
+ */
+ public static String bcdToString(byte[] data) {
+ return bcdToString(data, 0, data.length);
+ }
+
+ /**
+ * Converts BCD string to bytes.
+ *
+ * @param bcd This should have an even length. If not, an "0" will be appended to the string.
+ */
+ public static byte[] bcdToBytes(String bcd) {
+ byte[] output = new byte[(bcd.length() + 1) / 2];
+ bcdToBytes(bcd, output);
+ return output;
+ }
+
+ /**
+ * Converts BCD string to bytes and put it into the given byte array.
+ *
+ * @param bcd This should have an even length. If not, an "0" will be appended to the string.
+ * @param bytes If the array size is less than needed, the rest of the BCD string isn't be
+ * converted. If the array size is more than needed, the rest of array remains unchanged.
+ */
+ public static void bcdToBytes(String bcd, byte[] bytes) {
+ if (bcd.length() % 2 != 0) {
+ bcd += "0";
+ }
+ int size = Math.min(bytes.length * 2, bcd.length());
+ for (int i = 0, j = 0; i + 1 < size; i += 2, j++) {
+ bytes[j] = (byte) (charToByte(bcd.charAt(i + 1)) << 4 | charToByte(bcd.charAt(i)));
+ }
+ }
+
+ /**
* PLMN (MCC/MNC) is encoded as per 24.008 10.5.1.3
* Returns a concatenated string of MCC+MNC, stripping
* all invalid character 'f'
@@ -94,10 +135,10 @@
int v;
v = data[i] & 0xf;
- ret.append("0123456789abcdef".charAt(v));
+ ret.append(HEX_CHARS[v]);
v = (data[i] >> 4) & 0xf;
- ret.append("0123456789abcdef".charAt(v));
+ ret.append(HEX_CHARS[v]);
}
return ret.toString();
@@ -305,7 +346,7 @@
return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
}
- static int
+ public static int
hexCharToInt(char c) {
if (c >= '0' && c <= '9') return (c - '0');
if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
@@ -361,11 +402,11 @@
b = 0x0f & (bytes[i] >> 4);
- ret.append("0123456789abcdef".charAt(b));
+ ret.append(HEX_CHARS[b]);
b = 0x0f & bytes[i];
- ret.append("0123456789abcdef".charAt(b));
+ ret.append(HEX_CHARS[b]);
}
return ret.toString();
@@ -416,7 +457,6 @@
if ((data[offset] & 0x40) != 0) {
// FIXME(mkf) add country initials here
-
}
return ret;
@@ -575,4 +615,239 @@
}
return iccId.substring( 0, position );
}
+
+ /**
+ * Converts a series of bytes to an integer. This method currently only supports positive 32-bit
+ * integers.
+ *
+ * @param src The source bytes.
+ * @param offset The position of the first byte of the data to be converted. The data is base
+ * 256 with the most significant digit first.
+ * @param length The length of the data to be converted. It must be <= 4.
+ * @throws IllegalArgumentException If {@code length} is bigger than 4 or {@code src} cannot be
+ * parsed as a positive integer.
+ * @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length}
+ * exceeds the bounds of {@code src}.
+ */
+ public static int bytesToInt(byte[] src, int offset, int length) {
+ if (length > 4) {
+ throw new IllegalArgumentException(
+ "length must be <= 4 (only 32-bit integer supported): " + length);
+ }
+ if (offset < 0 || length < 0 || offset + length > src.length) {
+ throw new IndexOutOfBoundsException(
+ "Out of the bounds: src=["
+ + src.length
+ + "], offset="
+ + offset
+ + ", length="
+ + length);
+ }
+ int result = 0;
+ for (int i = 0; i < length; i++) {
+ result = (result << 8) | (src[offset + i] & 0xFF);
+ }
+ if (result < 0) {
+ throw new IllegalArgumentException(
+ "src cannot be parsed as a positive integer: " + result);
+ }
+ return result;
+ }
+
+ /**
+ * Converts a series of bytes to a raw long variable which can be both positive and negative.
+ * This method currently only supports 64-bit long variable.
+ *
+ * @param src The source bytes.
+ * @param offset The position of the first byte of the data to be converted. The data is base
+ * 256 with the most significant digit first.
+ * @param length The length of the data to be converted. It must be <= 8.
+ * @throws IllegalArgumentException If {@code length} is bigger than 8.
+ * @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length}
+ * exceeds the bounds of {@code src}.
+ */
+ public static long bytesToRawLong(byte[] src, int offset, int length) {
+ if (length > 8) {
+ throw new IllegalArgumentException(
+ "length must be <= 8 (only 64-bit long supported): " + length);
+ }
+ if (offset < 0 || length < 0 || offset + length > src.length) {
+ throw new IndexOutOfBoundsException(
+ "Out of the bounds: src=["
+ + src.length
+ + "], offset="
+ + offset
+ + ", length="
+ + length);
+ }
+ long result = 0;
+ for (int i = 0; i < length; i++) {
+ result = (result << 8) | (src[offset + i] & 0xFF);
+ }
+ return result;
+ }
+
+ /**
+ * Converts an integer to a new byte array with base 256 and the most significant digit first.
+ *
+ * @throws IllegalArgumentException If {@code value} is negative.
+ */
+ public static byte[] unsignedIntToBytes(int value) {
+ if (value < 0) {
+ throw new IllegalArgumentException("value must be 0 or positive: " + value);
+ }
+ byte[] bytes = new byte[byteNumForUnsignedInt(value)];
+ unsignedIntToBytes(value, bytes, 0);
+ return bytes;
+ }
+
+ /**
+ * Converts an integer to a new byte array with base 256 and the most significant digit first.
+ * The first byte's highest bit is used for sign. If the most significant digit is larger than
+ * 127, an extra byte (0) will be prepended before it. This method currently doesn't support
+ * negative values.
+ *
+ * @throws IllegalArgumentException If {@code value} is negative.
+ */
+ public static byte[] signedIntToBytes(int value) {
+ if (value < 0) {
+ throw new IllegalArgumentException("value must be 0 or positive: " + value);
+ }
+ byte[] bytes = new byte[byteNumForSignedInt(value)];
+ signedIntToBytes(value, bytes, 0);
+ return bytes;
+ }
+
+ /**
+ * Converts an integer to a series of bytes with base 256 and the most significant digit first.
+ *
+ * @param value The integer to be converted.
+ * @param dest The destination byte array.
+ * @param offset The start offset of the byte array.
+ * @return The number of byte needeed.
+ * @throws IllegalArgumentException If {@code value} is negative.
+ * @throws IndexOutOfBoundsException If {@code offset} exceeds the bounds of {@code dest}.
+ */
+ public static int unsignedIntToBytes(int value, byte[] dest, int offset) {
+ return intToBytes(value, dest, offset, false);
+ }
+
+ /**
+ * Converts an integer to a series of bytes with base 256 and the most significant digit first.
+ * The first byte's highest bit is used for sign. If the most significant digit is larger than
+ * 127, an extra byte (0) will be prepended before it. This method currently doesn't support
+ * negative values.
+ *
+ * @throws IllegalArgumentException If {@code value} is negative.
+ * @throws IndexOutOfBoundsException If {@code offset} exceeds the bounds of {@code dest}.
+ */
+ public static int signedIntToBytes(int value, byte[] dest, int offset) {
+ return intToBytes(value, dest, offset, true);
+ }
+
+ /**
+ * Calculates the number of required bytes to represent {@code value}. The bytes will be base
+ * 256 with the most significant digit first.
+ *
+ * @throws IllegalArgumentException If {@code value} is negative.
+ */
+ public static int byteNumForUnsignedInt(int value) {
+ return byteNumForInt(value, false);
+ }
+
+ /**
+ * Calculates the number of required bytes to represent {@code value}. The bytes will be base
+ * 256 with the most significant digit first. If the most significant digit is larger than 127,
+ * an extra byte (0) will be prepended before it. This method currently only supports positive
+ * integers.
+ *
+ * @throws IllegalArgumentException If {@code value} is negative.
+ */
+ public static int byteNumForSignedInt(int value) {
+ return byteNumForInt(value, true);
+ }
+
+ private static int intToBytes(int value, byte[] dest, int offset, boolean signed) {
+ int l = byteNumForInt(value, signed);
+ if (offset < 0 || offset + l > dest.length) {
+ throw new IndexOutOfBoundsException("Not enough space to write. Required bytes: " + l);
+ }
+ for (int i = l - 1, v = value; i >= 0; i--, v >>>= 8) {
+ byte b = (byte) (v & 0xFF);
+ dest[offset + i] = b;
+ }
+ return l;
+ }
+
+ private static int byteNumForInt(int value, boolean signed) {
+ if (value < 0) {
+ throw new IllegalArgumentException("value must be 0 or positive: " + value);
+ }
+ if (signed) {
+ if (value <= 0x7F) {
+ return 1;
+ }
+ if (value <= 0x7FFF) {
+ return 2;
+ }
+ if (value <= 0x7FFFFF) {
+ return 3;
+ }
+ } else {
+ if (value <= 0xFF) {
+ return 1;
+ }
+ if (value <= 0xFFFF) {
+ return 2;
+ }
+ if (value <= 0xFFFFFF) {
+ return 3;
+ }
+ }
+ return 4;
+ }
+
+
+ /**
+ * Counts the number of trailing zero bits of a byte.
+ */
+ public static byte countTrailingZeros(byte b) {
+ if (b == 0) {
+ return 8;
+ }
+ int v = b & 0xFF;
+ byte c = 7;
+ if ((v & 0x0F) != 0) {
+ c -= 4;
+ }
+ if ((v & 0x33) != 0) {
+ c -= 2;
+ }
+ if ((v & 0x55) != 0) {
+ c -= 1;
+ }
+ return c;
+ }
+
+ /**
+ * Converts a byte to a hex string.
+ */
+ public static String byteToHex(byte b) {
+ return new String(new char[] {HEX_CHARS[(b & 0xFF) >>> 4], HEX_CHARS[b & 0xF]});
+ }
+
+ /**
+ * Converts a character of [0-9a-aA-F] to its hex value in a byte. If the character is not a
+ * hex number, 0 will be returned.
+ */
+ private static byte charToByte(char c) {
+ if (c >= 0x30 && c <= 0x39) {
+ return (byte) (c - 0x30);
+ } else if (c >= 0x41 && c <= 0x46) {
+ return (byte) (c - 0x37);
+ } else if (c >= 0x61 && c <= 0x66) {
+ return (byte) (c - 0x57);
+ }
+ return 0;
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Decoder.java b/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Decoder.java
new file mode 100644
index 0000000..1ad0b66
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Decoder.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.uicc.asn1;
+
+import com.android.internal.telephony.uicc.IccUtils;
+
+/**
+ * This represents a decoder helping decode an array of bytes or a hex string into
+ * {@link Asn1Node}s. This class tracks the next position for decoding. This class is not
+ * thread-safe.
+ */
+public final class Asn1Decoder {
+ // Source byte array.
+ private final byte[] mSrc;
+ // Next position of the byte in the source array for decoding.
+ private int mPosition;
+ // Exclusive end of the range in the array for decoding.
+ private final int mEnd;
+
+ /** Creates a decoder on a hex string. */
+ public Asn1Decoder(String hex) {
+ this(IccUtils.hexStringToBytes(hex));
+ }
+
+ /** Creates a decoder on a byte array. */
+ public Asn1Decoder(byte[] src) {
+ this(src, 0, src.length);
+ }
+
+ /**
+ * Creates a decoder on a byte array slice.
+ *
+ * @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length}
+ * exceeds the bounds of {@code bytes}.
+ */
+ public Asn1Decoder(byte[] bytes, int offset, int length) {
+ if (offset < 0 || length < 0 || offset + length > bytes.length) {
+ throw new IndexOutOfBoundsException(
+ "Out of the bounds: bytes=["
+ + bytes.length
+ + "], offset="
+ + offset
+ + ", length="
+ + length);
+ }
+ mSrc = bytes;
+ mPosition = offset;
+ mEnd = offset + length;
+ }
+
+ /** @return The next start position for decoding. */
+ public int getPosition() {
+ return mPosition;
+ }
+
+ /** Returns whether the node has a next node. */
+ public boolean hasNextNode() {
+ return mPosition < mEnd;
+ }
+
+ /**
+ * Parses the next node. If the node is a constructed node, its children will be parsed only
+ * when they are accessed, e.g., though {@link Asn1Node#getChildren()}.
+ *
+ * @return The next decoded {@link Asn1Node}. If success, the next decoding position will also
+ * be updated. If any error happens, e.g., moving over the end position, {@code null}
+ * will be returned and the next decoding position won't be modified.
+ * @throws InvalidAsn1DataException If the bytes cannot be parsed.
+ */
+ public Asn1Node nextNode() throws InvalidAsn1DataException {
+ if (mPosition >= mEnd) {
+ throw new IllegalStateException("No bytes to parse.");
+ }
+
+ int offset = mPosition;
+
+ // Extracts the tag.
+ int tagStart = offset;
+ byte b = mSrc[offset++];
+ if ((b & 0x1F) == 0x1F) {
+ // High-tag-number form
+ while (offset < mEnd && (mSrc[offset++] & 0x80) != 0) {
+ // Do nothing.
+ }
+ }
+ if (offset >= mEnd) {
+ // No length bytes or the tag is too long.
+ throw new InvalidAsn1DataException(0, "Invalid length at position: " + offset);
+ }
+ int tag;
+ try {
+ tag = IccUtils.bytesToInt(mSrc, tagStart, offset - tagStart);
+ } catch (IllegalArgumentException e) {
+ // Cannot parse the tag as an integer.
+ throw new InvalidAsn1DataException(0, "Cannot parse tag at position: " + tagStart, e);
+ }
+
+ // Extracts the length.
+ int dataLen;
+ b = mSrc[offset++];
+ if ((b & 0x80) == 0) {
+ // Short-form length
+ dataLen = b;
+ } else {
+ // Long-form length
+ int lenLen = b & 0x7F;
+ if (offset + lenLen > mEnd) {
+ // No enough bytes for the long-form length
+ throw new InvalidAsn1DataException(
+ tag, "Cannot parse length at position: " + offset);
+ }
+ try {
+ dataLen = IccUtils.bytesToInt(mSrc, offset, lenLen);
+ } catch (IllegalArgumentException e) {
+ // Cannot parse the data length as an integer.
+ throw new InvalidAsn1DataException(
+ tag, "Cannot parse length at position: " + offset, e);
+ }
+ offset += lenLen;
+ }
+ if (offset + dataLen > mEnd) {
+ // No enough data left.
+ throw new InvalidAsn1DataException(
+ tag,
+ "Incomplete data at position: "
+ + offset
+ + ", expected bytes: "
+ + dataLen
+ + ", actual bytes: "
+ + (mEnd - offset));
+ }
+
+ Asn1Node root = new Asn1Node(tag, mSrc, offset, dataLen);
+ mPosition = offset + dataLen;
+ return root;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Node.java b/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Node.java
new file mode 100644
index 0000000..5eb1d5c
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Node.java
@@ -0,0 +1,598 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.uicc.asn1;
+
+import android.annotation.Nullable;
+
+import com.android.internal.telephony.uicc.IccUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This represents a primitive or constructed data defined by ASN.1. A constructed node can have
+ * child nodes. A non-constructed node can have a value. This class is read-only. To build a node,
+ * you can use the {@link #newBuilder(int)} method to get a {@link Builder} instance. This class is
+ * not thread-safe.
+ */
+public final class Asn1Node {
+ private static final int INT_BYTES = Integer.SIZE / Byte.SIZE;
+ private static final List<Asn1Node> EMPTY_NODE_LIST = Collections.emptyList();
+
+ // Bytes for boolean values.
+ private static final byte[] TRUE_BYTES = new byte[] {-1};
+ private static final byte[] FALSE_BYTES = new byte[] {0};
+
+ /**
+ * This class is used to build an Asn1Node instance of a constructed tag. This class is not
+ * thread-safe.
+ */
+ public static final class Builder {
+ private final int mTag;
+ private final List<Asn1Node> mChildren;
+
+ private Builder(int tag) {
+ if (!isConstructedTag(tag)) {
+ throw new IllegalArgumentException(
+ "Builder should be created for a constructed tag: " + tag);
+ }
+ mTag = tag;
+ mChildren = new ArrayList<>();
+ }
+
+ /**
+ * Adds a child from an existing node.
+ *
+ * @return This builder.
+ * @throws IllegalArgumentException If the child is a non-existing node.
+ */
+ public Builder addChild(Asn1Node child) {
+ mChildren.add(child);
+ return this;
+ }
+
+ /**
+ * Adds a child from another builder. The child will be built with the call to this method,
+ * and any changes to the child builder after the call to this method doesn't have effect.
+ *
+ * @return This builder.
+ */
+ public Builder addChild(Builder child) {
+ mChildren.add(child.build());
+ return this;
+ }
+
+ /**
+ * Adds children from bytes. This method calls {@link Asn1Decoder} to verify the {@code
+ * encodedBytes} and adds all nodes parsed from it as children.
+ *
+ * @return This builder.
+ * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+ */
+ public Builder addChildren(byte[] encodedBytes) throws InvalidAsn1DataException {
+ Asn1Decoder subDecoder = new Asn1Decoder(encodedBytes, 0, encodedBytes.length);
+ while (subDecoder.hasNextNode()) {
+ mChildren.add(subDecoder.nextNode());
+ }
+ return this;
+ }
+
+ /**
+ * Adds a child of non-constructed tag with an integer as the data.
+ *
+ * @return This builder.
+ * @throws IllegalStateException If the {@code tag} is not constructed..
+ */
+ public Builder addChildAsInteger(int tag, int value) {
+ if (isConstructedTag(tag)) {
+ throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
+ }
+ byte[] dataBytes = IccUtils.signedIntToBytes(value);
+ addChild(new Asn1Node(tag, dataBytes, 0, dataBytes.length));
+ return this;
+ }
+
+ /**
+ * Adds a child of non-constructed tag with a string as the data.
+ *
+ * @return This builder.
+ * @throws IllegalStateException If the {@code tag} is not constructed..
+ */
+ public Builder addChildAsString(int tag, String value) {
+ if (isConstructedTag(tag)) {
+ throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
+ }
+ byte[] dataBytes = value.getBytes(StandardCharsets.UTF_8);
+ addChild(new Asn1Node(tag, dataBytes, 0, dataBytes.length));
+ return this;
+ }
+
+ /**
+ * Adds a child of non-constructed tag with a byte array as the data.
+ *
+ * @param value The value will be owned by this node.
+ * @return This builder.
+ * @throws IllegalStateException If the {@code tag} is not constructed..
+ */
+ public Builder addChildAsBytes(int tag, byte[] value) {
+ if (isConstructedTag(tag)) {
+ throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
+ }
+ addChild(new Asn1Node(tag, value, 0, value.length));
+ return this;
+ }
+
+ /**
+ * Adds a child of non-constructed tag with a byte array as the data from a hex string.
+ *
+ * @return This builder.
+ * @throws IllegalStateException If the {@code tag} is not constructed..
+ */
+ public Builder addChildAsBytesFromHex(int tag, String hex) {
+ return addChildAsBytes(tag, IccUtils.hexStringToBytes(hex));
+ }
+
+ /**
+ * Adds a child of non-constructed tag with bits as the data.
+ *
+ * @return This builder.
+ * @throws IllegalStateException If the {@code tag} is not constructed..
+ */
+ public Builder addChildAsBits(int tag, int value) {
+ if (isConstructedTag(tag)) {
+ throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
+ }
+ // Always allocate 5 bytes for simplicity.
+ byte[] dataBytes = new byte[INT_BYTES + 1];
+ // Puts the integer into the byte[1-4].
+ value = Integer.reverse(value);
+ int dataLength = 0;
+ for (int i = 1; i < dataBytes.length; i++) {
+ dataBytes[i] = (byte) (value >> ((INT_BYTES - i) * Byte.SIZE));
+ if (dataBytes[i] != 0) {
+ dataLength = i;
+ }
+ }
+ dataLength++;
+ // The first byte is the number of trailing zeros of the last byte.
+ dataBytes[0] = IccUtils.countTrailingZeros(dataBytes[dataLength - 1]);
+ addChild(new Asn1Node(tag, dataBytes, 0, dataLength));
+ return this;
+ }
+
+ /**
+ * Adds a child of non-constructed tag with a boolean as the data.
+ *
+ * @return This builder.
+ * @throws IllegalStateException If the {@code tag} is not constructed..
+ */
+ public Builder addChildAsBoolean(int tag, boolean value) {
+ if (isConstructedTag(tag)) {
+ throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
+ }
+ addChild(new Asn1Node(tag, value ? TRUE_BYTES : FALSE_BYTES, 0, 1));
+ return this;
+ }
+
+ /** Builds the node. */
+ public Asn1Node build() {
+ return new Asn1Node(mTag, mChildren);
+ }
+ }
+
+ private final int mTag;
+ private final boolean mConstructed;
+ // Do not use this field directly in the methods other than the constructor and encoding
+ // methods (e.g., toBytes()), but always use getChildren() instead.
+ private final List<Asn1Node> mChildren;
+
+ // Byte array that actually holds the data. For a non-constructed node, this stores its actual
+ // value. If the value is not set, this is null. For constructed node, this stores encoded data
+ // of its children, which will be decoded on the first call to getChildren().
+ private @Nullable byte[] mDataBytes;
+ // Offset of the data in above byte array.
+ private int mDataOffset;
+ // Length of the data in above byte array. If it's a constructed node, this is always the total
+ // length of all its children.
+ private int mDataLength;
+ // Length of the total bytes required to encode this node.
+ private int mEncodedLength;
+
+ /**
+ * Creates a new ASN.1 data node builder with the given tag. The tag is an encoded tag including
+ * the tag class, tag number, and constructed mask.
+ */
+ public static Builder newBuilder(int tag) {
+ return new Builder(tag);
+ }
+
+ private static boolean isConstructedTag(int tag) {
+ // Constructed mask is at the 6th bit.
+ byte[] tagBytes = IccUtils.unsignedIntToBytes(tag);
+ return (tagBytes[0] & 0x20) != 0;
+ }
+
+ private static int calculateEncodedBytesNumForLength(int length) {
+ // Constructed mask is at the 6th bit.
+ int len = 1;
+ if (length > 127) {
+ len += IccUtils.byteNumForUnsignedInt(length);
+ }
+ return len;
+ }
+
+ /**
+ * Creates a node with given data bytes. If it is a constructed node, its children will be
+ * parsed when they are visited.
+ */
+ Asn1Node(int tag, @Nullable byte[] src, int offset, int length) {
+ mTag = tag;
+ // Constructed mask is at the 6th bit.
+ mConstructed = isConstructedTag(tag);
+ mDataBytes = src;
+ mDataOffset = offset;
+ mDataLength = length;
+ mChildren = mConstructed ? new ArrayList<Asn1Node>() : EMPTY_NODE_LIST;
+ mEncodedLength =
+ IccUtils.byteNumForUnsignedInt(mTag)
+ + calculateEncodedBytesNumForLength(mDataLength)
+ + mDataLength;
+ }
+
+ /** Creates a constructed node with given children. */
+ private Asn1Node(int tag, List<Asn1Node> children) {
+ mTag = tag;
+ mConstructed = true;
+ mChildren = children;
+
+ mDataLength = 0;
+ int size = children.size();
+ for (int i = 0; i < size; i++) {
+ mDataLength += children.get(i).mEncodedLength;
+ }
+ mEncodedLength =
+ IccUtils.byteNumForUnsignedInt(mTag)
+ + calculateEncodedBytesNumForLength(mDataLength)
+ + mDataLength;
+ }
+
+ public int getTag() {
+ return mTag;
+ }
+
+ public boolean isConstructed() {
+ return mConstructed;
+ }
+
+ /**
+ * Tests if a node has a child.
+ *
+ * @param tag The tag of an immediate child.
+ * @param tags The tags of lineal descendant.
+ */
+ public boolean hasChild(int tag, int... tags) throws InvalidAsn1DataException {
+ try {
+ getChild(tag, tags);
+ } catch (TagNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Gets the first child node having the given {@code tag} and {@code tags}.
+ *
+ * @param tag The tag of an immediate child.
+ * @param tags The tags of lineal descendant.
+ * @throws TagNotFoundException If the child cannot be found.
+ */
+ public Asn1Node getChild(int tag, int... tags)
+ throws TagNotFoundException, InvalidAsn1DataException {
+ if (!mConstructed) {
+ throw new TagNotFoundException(tag);
+ }
+ int index = 0;
+ Asn1Node node = this;
+ while (node != null) {
+ List<Asn1Node> children = node.getChildren();
+ int size = children.size();
+ Asn1Node foundChild = null;
+ for (int i = 0; i < size; i++) {
+ Asn1Node child = children.get(i);
+ if (child.getTag() == tag) {
+ foundChild = child;
+ break;
+ }
+ }
+ node = foundChild;
+ if (index >= tags.length) {
+ break;
+ }
+ tag = tags[index++];
+ }
+ if (node == null) {
+ throw new TagNotFoundException(tag);
+ }
+ return node;
+ }
+
+ /**
+ * Gets all child nodes which have the given {@code tag}.
+ *
+ * @return If this is primitive or no such children are found, an empty list will be returned.
+ */
+ public List<Asn1Node> getChildren(int tag)
+ throws TagNotFoundException, InvalidAsn1DataException {
+ if (!mConstructed) {
+ return EMPTY_NODE_LIST;
+ }
+
+ List<Asn1Node> children = getChildren();
+ if (children.isEmpty()) {
+ return EMPTY_NODE_LIST;
+ }
+ List<Asn1Node> output = new ArrayList<>();
+ int size = children.size();
+ for (int i = 0; i < size; i++) {
+ Asn1Node child = children.get(i);
+ if (child.getTag() == tag) {
+ output.add(child);
+ }
+ }
+ return output.isEmpty() ? EMPTY_NODE_LIST : output;
+ }
+
+ /**
+ * Gets all child nodes of this node. If it's a constructed node having encoded data, it's
+ * children will be decoded here.
+ *
+ * @return If this is primitive, an empty list will be returned. Do not modify the returned list
+ * directly.
+ */
+ public List<Asn1Node> getChildren() throws InvalidAsn1DataException {
+ if (!mConstructed) {
+ return EMPTY_NODE_LIST;
+ }
+
+ if (mDataBytes != null) {
+ Asn1Decoder subDecoder = new Asn1Decoder(mDataBytes, mDataOffset, mDataLength);
+ while (subDecoder.hasNextNode()) {
+ mChildren.add(subDecoder.nextNode());
+ }
+ mDataBytes = null;
+ mDataOffset = 0;
+ }
+ return mChildren;
+ }
+
+ /** @return Whether this node has a value. False will be returned for a constructed node. */
+ public boolean hasValue() {
+ return !mConstructed && mDataBytes != null;
+ }
+
+ /**
+ * @return The data as an integer. If the data length is larger than 4, only the first 4 bytes
+ * will be parsed.
+ * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+ */
+ public int asInteger() throws InvalidAsn1DataException {
+ if (mConstructed) {
+ throw new IllegalStateException("Cannot get value of a constructed node.");
+ }
+ if (mDataBytes == null) {
+ throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+ }
+ try {
+ return IccUtils.bytesToInt(mDataBytes, mDataOffset, mDataLength);
+ } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
+ throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
+ }
+ }
+
+ /**
+ * @return The data as a long variable which can be both positive and negative. If the data
+ * length is larger than 8, only the first 8 bytes will be parsed.
+ * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+ */
+ public long asRawLong() throws InvalidAsn1DataException {
+ if (mConstructed) {
+ throw new IllegalStateException("Cannot get value of a constructed node.");
+ }
+ if (mDataBytes == null) {
+ throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+ }
+ try {
+ return IccUtils.bytesToRawLong(mDataBytes, mDataOffset, mDataLength);
+ } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
+ throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
+ }
+ }
+
+ /**
+ * @return The data as a string in UTF-8 encoding.
+ * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+ */
+ public String asString() throws InvalidAsn1DataException {
+ if (mConstructed) {
+ throw new IllegalStateException("Cannot get value of a constructed node.");
+ }
+ if (mDataBytes == null) {
+ throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+ }
+ try {
+ return new String(mDataBytes, mDataOffset, mDataLength, StandardCharsets.UTF_8);
+ } catch (IndexOutOfBoundsException e) {
+ throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
+ }
+ }
+
+ /**
+ * @return The data as a byte array.
+ * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+ */
+ public byte[] asBytes() throws InvalidAsn1DataException {
+ if (mConstructed) {
+ throw new IllegalStateException("Cannot get value of a constructed node.");
+ }
+ if (mDataBytes == null) {
+ throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+ }
+ byte[] output = new byte[mDataLength];
+ try {
+ System.arraycopy(mDataBytes, mDataOffset, output, 0, mDataLength);
+ } catch (IndexOutOfBoundsException e) {
+ throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
+ }
+ return output;
+ }
+
+ /**
+ * Gets the data as an integer for BIT STRING. DER actually stores the bits in a reversed order.
+ * The returned integer here has the order fixed (first bit is at the lowest position). This
+ * method currently only support at most 32 bits which fit in an integer.
+ *
+ * @return The data as an integer. If this is constructed, a {@code null} will be returned.
+ * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+ */
+ public int asBits() throws InvalidAsn1DataException {
+ if (mConstructed) {
+ throw new IllegalStateException("Cannot get value of a constructed node.");
+ }
+ if (mDataBytes == null) {
+ throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+ }
+ int bits;
+ try {
+ bits = IccUtils.bytesToInt(mDataBytes, mDataOffset + 1, mDataLength - 1);
+ } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
+ throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
+ }
+ for (int i = mDataLength - 1; i < INT_BYTES; i++) {
+ bits <<= Byte.SIZE;
+ }
+ return Integer.reverse(bits);
+ }
+
+ /**
+ * @return The data as a boolean.
+ * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
+ */
+ public boolean asBoolean() throws InvalidAsn1DataException {
+ if (mConstructed) {
+ throw new IllegalStateException("Cannot get value of a constructed node.");
+ }
+ if (mDataBytes == null) {
+ throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
+ }
+ if (mDataLength != 1) {
+ throw new InvalidAsn1DataException(
+ mTag, "Cannot parse data bytes as boolean: length=" + mDataLength);
+ }
+ if (mDataOffset < 0 || mDataOffset >= mDataBytes.length) {
+ throw new InvalidAsn1DataException(
+ mTag,
+ "Cannot parse data bytes.",
+ new ArrayIndexOutOfBoundsException(mDataOffset));
+ }
+ // ASN.1 has "true" as 0xFF.
+ if (mDataBytes[mDataOffset] == -1) {
+ return Boolean.TRUE;
+ } else if (mDataBytes[mDataOffset] == 0) {
+ return Boolean.FALSE;
+ }
+ throw new InvalidAsn1DataException(
+ mTag, "Cannot parse data bytes as boolean: " + mDataBytes[mDataOffset]);
+ }
+
+ /** @return The number of required bytes for encoding this node in DER. */
+ public int getEncodedLength() {
+ return mEncodedLength;
+ }
+
+ /** @return The number of required bytes for encoding this node's data in DER. */
+ public int getDataLength() {
+ return mDataLength;
+ }
+
+ /**
+ * Writes the DER encoded bytes of this node into a byte array. The number of written bytes is
+ * {@link #getEncodedLength()}.
+ *
+ * @throws IndexOutOfBoundsException If the {@code dest} doesn't have enough space to write.
+ */
+ public void writeToBytes(byte[] dest, int offset) {
+ if (offset < 0 || offset + mEncodedLength > dest.length) {
+ throw new IndexOutOfBoundsException(
+ "Not enough space to write. Required bytes: " + mEncodedLength);
+ }
+ write(dest, offset);
+ }
+
+ /** Writes the DER encoded bytes of this node into a new byte array. */
+ public byte[] toBytes() {
+ byte[] dest = new byte[mEncodedLength];
+ write(dest, 0);
+ return dest;
+ }
+
+ /** Gets a hex string representing the DER encoded bytes of this node. */
+ public String toHex() {
+ return IccUtils.bytesToHexString(toBytes());
+ }
+
+ /** Gets header (tag + length) as hex string. */
+ public String getHeadAsHex() {
+ String headHex = IccUtils.bytesToHexString(IccUtils.unsignedIntToBytes(mTag));
+ if (mDataLength <= 127) {
+ headHex += IccUtils.byteToHex((byte) mDataLength);
+ } else {
+ byte[] lenBytes = IccUtils.unsignedIntToBytes(mDataLength);
+ headHex += IccUtils.byteToHex((byte) (lenBytes.length | 0x80));
+ headHex += IccUtils.bytesToHexString(lenBytes);
+ }
+ return headHex;
+ }
+
+ /** Returns the new offset where to write the next node data. */
+ private int write(byte[] dest, int offset) {
+ // Writes the tag.
+ offset += IccUtils.unsignedIntToBytes(mTag, dest, offset);
+ // Writes the length.
+ if (mDataLength <= 127) {
+ dest[offset++] = (byte) mDataLength;
+ } else {
+ // Bytes required for encoding the length
+ int lenLen = IccUtils.unsignedIntToBytes(mDataLength, dest, ++offset);
+ dest[offset - 1] = (byte) (lenLen | 0x80);
+ offset += lenLen;
+ }
+ // Writes the data.
+ if (mConstructed && mDataBytes == null) {
+ int size = mChildren.size();
+ for (int i = 0; i < size; i++) {
+ Asn1Node child = mChildren.get(i);
+ offset = child.write(dest, offset);
+ }
+ } else if (mDataBytes != null) {
+ System.arraycopy(mDataBytes, mDataOffset, dest, offset, mDataLength);
+ offset += mDataLength;
+ }
+ return offset;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/uicc/asn1/InvalidAsn1DataException.java b/telephony/java/com/android/internal/telephony/uicc/asn1/InvalidAsn1DataException.java
new file mode 100644
index 0000000..c151468
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/uicc/asn1/InvalidAsn1DataException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.uicc.asn1;
+
+/**
+ * Exception for invalid ASN.1 data in DER encoding which cannot be parsed as a node or a specific
+ * data type.
+ */
+public class InvalidAsn1DataException extends Exception {
+ private final int mTag;
+
+ public InvalidAsn1DataException(int tag, String message) {
+ super(message);
+ mTag = tag;
+ }
+
+ public InvalidAsn1DataException(int tag, String message, Throwable throwable) {
+ super(message, throwable);
+ mTag = tag;
+ }
+
+ /** @return The tag which has the invalid data. */
+ public int getTag() {
+ return mTag;
+ }
+
+ @Override
+ public String getMessage() {
+ return super.getMessage() + " (tag=" + mTag + ")";
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/uicc/asn1/TagNotFoundException.java b/telephony/java/com/android/internal/telephony/uicc/asn1/TagNotFoundException.java
new file mode 100644
index 0000000..f79021e
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/uicc/asn1/TagNotFoundException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.uicc.asn1;
+
+/**
+ * Exception for getting a child of a {@link Asn1Node} with a non-existing tag.
+ */
+public class TagNotFoundException extends Exception {
+ private final int mTag;
+
+ public TagNotFoundException(int tag) {
+ mTag = tag;
+ }
+
+ /** @return The tag which has the invalid data. */
+ public int getTag() {
+ return mTag;
+ }
+
+ @Override
+ public String getMessage() {
+ return super.getMessage() + " (tag=" + mTag + ")";
+ }
+}
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index 558dbb6..473dc538 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -73,18 +73,18 @@
}
@Test
- public void testToSafeString() {
+ public void testToOuiString() {
String[][] macs = {
- {"07:00:d3:56:8a:c4", "07:00:d3:00:00:00"},
- {"33:33:aa:bb:cc:dd", "33:33:aa:00:00:00"},
- {"06:00:00:00:00:00", "06:00:00:00:00:00"},
- {"07:00:d3:56:8a:c4", "07:00:d3:00:00:00"}
+ {"07:00:d3:56:8a:c4", "07:00:d3"},
+ {"33:33:aa:bb:cc:dd", "33:33:aa"},
+ {"06:00:00:00:00:00", "06:00:00"},
+ {"07:00:d3:56:8a:c4", "07:00:d3"}
};
for (String[] pair : macs) {
String mac = pair[0];
String expected = pair[1];
- assertEquals(expected, MacAddress.fromString(mac).toSafeString());
+ assertEquals(expected, MacAddress.fromString(mac).toOuiString());
}
}
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index f38a9a3..5d1e10e 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -475,4 +475,26 @@
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/testables/Android.mk b/tests/testables/Android.mk
index 7fcfc6e..4c4d2b4 100644
--- a/tests/testables/Android.mk
+++ b/tests/testables/Android.mk
@@ -24,10 +24,9 @@
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
- mockito-target-minus-junit4
+ android-support-test
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.mock
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.mock mockito-target-minus-junit4
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/utils/testutils/Android.mk b/tests/utils/testutils/Android.mk
index 543c652..a76616f 100644
--- a/tests/utils/testutils/Android.mk
+++ b/tests/utils/testutils/Android.mk
@@ -24,9 +24,12 @@
LOCAL_SRC_FILES := $(call all-java-files-under,java)
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
- mockito-target-minus-junit4
+ android-support-test
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+ android.test.base \
+ android.test.mock \
+ mockito-target-minus-junit4 \
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index d8bb999..9c76119 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -377,44 +377,10 @@
}
const std::string& apk_path = flags.GetArgs()[0];
- std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
- if (!apk) {
- return 1;
- }
context.SetVerbose(verbose);
IDiagnostics* diag = context.GetDiagnostics();
- if (target_densities) {
- // Parse the target screen densities.
- for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
- Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
- if (!target_density) {
- return 1;
- }
- options.table_splitter_options.preferred_densities.push_back(target_density.value());
- }
- }
-
- std::unique_ptr<IConfigFilter> filter;
- if (!configs.empty()) {
- filter = ParseConfigFilterParameters(configs, diag);
- if (filter == nullptr) {
- return 1;
- }
- options.table_splitter_options.config_filter = filter.get();
- }
-
- // Parse the split parameters.
- for (const std::string& split_arg : split_args) {
- options.split_paths.emplace_back();
- options.split_constraints.emplace_back();
- if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
- &options.split_constraints.back())) {
- return 1;
- }
- }
-
if (config_path) {
std::string& path = config_path.value();
Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
@@ -456,6 +422,41 @@
return 1;
}
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
+ if (!apk) {
+ return 1;
+ }
+
+ if (target_densities) {
+ // Parse the target screen densities.
+ for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
+ Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
+ if (!target_density) {
+ return 1;
+ }
+ options.table_splitter_options.preferred_densities.push_back(target_density.value());
+ }
+ }
+
+ std::unique_ptr<IConfigFilter> filter;
+ if (!configs.empty()) {
+ filter = ParseConfigFilterParameters(configs, diag);
+ if (filter == nullptr) {
+ return 1;
+ }
+ options.table_splitter_options.config_filter = filter.get();
+ }
+
+ // Parse the split parameters.
+ for (const std::string& split_arg : split_args) {
+ options.split_paths.emplace_back();
+ options.split_constraints.emplace_back();
+ if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
+ &options.split_constraints.back())) {
+ return 1;
+ }
+ }
+
if (options.table_flattener_options.collapse_key_stringpool) {
if (whitelist_path) {
std::string& path = whitelist_path.value();
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index ebc523f..eabeb47 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -49,13 +49,15 @@
using ::aapt::configuration::ConfiguredArtifact;
using ::aapt::configuration::DeviceFeature;
using ::aapt::configuration::Entry;
+using ::aapt::configuration::ExtractConfiguration;
using ::aapt::configuration::GlTexture;
using ::aapt::configuration::Group;
using ::aapt::configuration::Locale;
+using ::aapt::configuration::OrderedEntry;
using ::aapt::configuration::OutputArtifact;
using ::aapt::configuration::PostProcessingConfiguration;
using ::aapt::configuration::handler::AbiGroupTagHandler;
-using ::aapt::configuration::handler::AndroidSdkGroupTagHandler;
+using ::aapt::configuration::handler::AndroidSdkTagHandler;
using ::aapt::configuration::handler::ArtifactFormatTagHandler;
using ::aapt::configuration::handler::ArtifactTagHandler;
using ::aapt::configuration::handler::DeviceFeatureGroupTagHandler;
@@ -130,7 +132,7 @@
return false;
}
- for (const T& item : group->second) {
+ for (const T& item : group->second.entry) {
target->push_back(item);
}
return true;
@@ -188,61 +190,6 @@
};
}
-/** Returns the binary reprasentation of the XML configuration. */
-Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
- IDiagnostics* diag) {
- StringInputStream in(contents);
- std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source("config.xml"));
- if (!doc) {
- return {};
- }
-
- // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
- Element* root = doc->root.get();
- if (root == nullptr) {
- diag->Error(DiagMessage() << "Could not find the root element in the XML document");
- return {};
- }
-
- std::string& xml_ns = root->namespace_uri;
- if (!xml_ns.empty()) {
- if (xml_ns != kAaptXmlNs) {
- diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
- return {};
- }
-
- xml_ns.clear();
- NamespaceVisitor visitor;
- root->Accept(&visitor);
- }
-
- XmlActionExecutor executor;
- XmlNodeAction& root_action = executor["post-process"];
- XmlNodeAction& artifacts_action = root_action["artifacts"];
- XmlNodeAction& groups_action = root_action["groups"];
-
- PostProcessingConfiguration config;
-
- // Parse the artifact elements.
- artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
- artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
-
- // Parse the different configuration groups.
- groups_action["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
- groups_action["screen-density-group"].Action(Bind(&config, ScreenDensityGroupTagHandler));
- groups_action["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
- groups_action["android-sdk-group"].Action(Bind(&config, AndroidSdkGroupTagHandler));
- groups_action["gl-texture-group"].Action(Bind(&config, GlTextureGroupTagHandler));
- groups_action["device-feature-group"].Action(Bind(&config, DeviceFeatureGroupTagHandler));
-
- if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
- diag->Error(DiagMessage() << "Could not process XML document");
- return {};
- }
-
- return {config};
-}
-
/** Converts a ConfiguredArtifact into an OutputArtifact. */
Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact,
const std::string& apk_name,
@@ -302,11 +249,11 @@
has_errors = true;
}
- if (artifact.android_sdk_group) {
- auto entry = config.android_sdk_groups.find(artifact.android_sdk_group.value());
- if (entry == config.android_sdk_groups.end()) {
+ if (artifact.android_sdk) {
+ auto entry = config.android_sdks.find(artifact.android_sdk.value());
+ if (entry == config.android_sdks.end()) {
src_diag.Error(DiagMessage() << "Could not lookup required Android SDK version: "
- << artifact.android_sdk_group.value());
+ << artifact.android_sdk.value());
has_errors = true;
} else {
output_artifact.android_sdk = {entry->second};
@@ -323,6 +270,64 @@
namespace configuration {
+/** Returns the binary reprasentation of the XML configuration. */
+Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
+ const std::string& config_path,
+ IDiagnostics* diag) {
+ StringInputStream in(contents);
+ std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source(config_path));
+ if (!doc) {
+ return {};
+ }
+
+ // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
+ Element* root = doc->root.get();
+ if (root == nullptr) {
+ diag->Error(DiagMessage() << "Could not find the root element in the XML document");
+ return {};
+ }
+
+ std::string& xml_ns = root->namespace_uri;
+ if (!xml_ns.empty()) {
+ if (xml_ns != kAaptXmlNs) {
+ diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
+ return {};
+ }
+
+ xml_ns.clear();
+ NamespaceVisitor visitor;
+ root->Accept(&visitor);
+ }
+
+ XmlActionExecutor executor;
+ XmlNodeAction& root_action = executor["post-process"];
+ XmlNodeAction& artifacts_action = root_action["artifacts"];
+
+ PostProcessingConfiguration config;
+
+ // Parse the artifact elements.
+ artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
+ artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
+
+ // Parse the different configuration groups.
+ root_action["abi-groups"]["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
+ root_action["screen-density-groups"]["screen-density-group"].Action(
+ Bind(&config, ScreenDensityGroupTagHandler));
+ root_action["locale-groups"]["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
+ root_action["android-sdks"]["android-sdk"].Action(Bind(&config, AndroidSdkTagHandler));
+ root_action["gl-texture-groups"]["gl-texture-group"].Action(
+ Bind(&config, GlTextureGroupTagHandler));
+ root_action["device-feature-groups"]["device-feature-group"].Action(
+ Bind(&config, DeviceFeatureGroupTagHandler));
+
+ if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
+ diag->Error(DiagMessage() << "Could not process XML document");
+ return {};
+ }
+
+ return {config};
+}
+
const StringPiece& AbiToString(Abi abi) {
return kAbiToStringMap.at(static_cast<size_t>(abi));
}
@@ -383,7 +388,7 @@
return {};
}
- if (!ReplacePlaceholder("${sdk}", android_sdk_group, &result, diag)) {
+ if (!ReplacePlaceholder("${sdk}", android_sdk, &result, diag)) {
return {};
}
@@ -414,47 +419,37 @@
if (!ReadFileToString(path, &contents, true)) {
return {};
}
- return ConfigurationParser(contents);
+ return ConfigurationParser(contents, path);
}
-ConfigurationParser::ConfigurationParser(std::string contents)
- : contents_(std::move(contents)),
- diag_(&noop_) {
+ConfigurationParser::ConfigurationParser(std::string contents, const std::string& config_path)
+ : contents_(std::move(contents)), config_path_(config_path), diag_(&noop_) {
}
Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse(
const android::StringPiece& apk_path) {
- Maybe<PostProcessingConfiguration> maybe_config = ExtractConfiguration(contents_, diag_);
+ Maybe<PostProcessingConfiguration> maybe_config =
+ ExtractConfiguration(contents_, config_path_, diag_);
if (!maybe_config) {
return {};
}
- const PostProcessingConfiguration& config = maybe_config.value();
-
- // TODO: Automatically arrange artifacts so that they match Play Store multi-APK requirements.
- // see: https://developer.android.com/google/play/publishing/multiple-apks.html
- //
- // For now, make sure the version codes are unique.
- std::vector<ConfiguredArtifact> artifacts = config.artifacts;
- std::sort(artifacts.begin(), artifacts.end());
- if (std::adjacent_find(artifacts.begin(), artifacts.end()) != artifacts.end()) {
- diag_->Error(DiagMessage() << "Configuration has duplicate versions");
- return {};
- }
-
- const std::string& apk_name = file::GetFilename(apk_path).to_string();
- const StringPiece ext = file::GetExtension(apk_name);
- const std::string base_name = apk_name.substr(0, apk_name.size() - ext.size());
// Convert from a parsed configuration to a list of artifacts for processing.
+ const std::string& apk_name = file::GetFilename(apk_path).to_string();
std::vector<OutputArtifact> output_artifacts;
bool has_errors = false;
- for (const ConfiguredArtifact& artifact : artifacts) {
+ PostProcessingConfiguration& config = maybe_config.value();
+ config.SortArtifacts();
+
+ int version = 1;
+ for (const ConfiguredArtifact& artifact : config.artifacts) {
Maybe<OutputArtifact> output_artifact = ToOutputArtifact(artifact, apk_name, config, diag_);
if (!output_artifact) {
// Defer return an error condition so that all errors are reported.
has_errors = true;
} else {
+ output_artifact.value().version = version++;
output_artifacts.push_back(std::move(output_artifact.value()));
}
}
@@ -470,24 +465,18 @@
bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_element,
IDiagnostics* diag) {
- // This will be incremented later so the first version will always be different to the base APK.
- int current_version = (config->artifacts.empty()) ? 0 : config->artifacts.back().version;
-
ConfiguredArtifact artifact{};
- Maybe<int> version;
for (const auto& attr : root_element->attributes) {
if (attr.name == "name") {
artifact.name = attr.value;
- } else if (attr.name == "version") {
- version = std::stoi(attr.value);
} else if (attr.name == "abi-group") {
artifact.abi_group = {attr.value};
} else if (attr.name == "screen-density-group") {
artifact.screen_density_group = {attr.value};
} else if (attr.name == "locale-group") {
artifact.locale_group = {attr.value};
- } else if (attr.name == "android-sdk-group") {
- artifact.android_sdk_group = {attr.value};
+ } else if (attr.name == "android-sdk") {
+ artifact.android_sdk = {attr.value};
} else if (attr.name == "gl-texture-group") {
artifact.gl_texture_group = {attr.value};
} else if (attr.name == "device-feature-group") {
@@ -497,9 +486,6 @@
<< attr.value);
}
}
-
- artifact.version = (version) ? version.value() : current_version + 1;
-
config->artifacts.push_back(artifact);
return true;
};
@@ -523,9 +509,19 @@
return false;
}
- auto& group = config->abi_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->abi_groups);
bool valid = true;
+ // Special case for empty abi-group tag. Label will be used as the ABI.
+ if (root_element->GetChildElements().empty()) {
+ auto abi = kStringToAbiMap.find(label);
+ if (abi == kStringToAbiMap.end()) {
+ return false;
+ }
+ group.push_back(abi->second);
+ return true;
+ }
+
for (auto* child : root_element->GetChildElements()) {
if (child->name != "abi") {
diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name);
@@ -534,7 +530,13 @@
for (auto& node : child->children) {
xml::Text* t;
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
- group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
+ auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string());
+ if (abi != kStringToAbiMap.end()) {
+ group.push_back(abi->second);
+ } else {
+ diag->Error(DiagMessage() << "Could not parse ABI value: " << t->text);
+ valid = false;
+ }
break;
}
}
@@ -551,9 +553,28 @@
return false;
}
- auto& group = config->screen_density_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->screen_density_groups);
bool valid = true;
+ // Special case for empty screen-density-group tag. Label will be used as the screen density.
+ if (root_element->GetChildElements().empty()) {
+ ConfigDescription config_descriptor;
+ bool parsed = ConfigDescription::Parse(label, &config_descriptor);
+ if (parsed &&
+ (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+ android::ResTable_config::CONFIG_DENSITY)) {
+ // Copy the density with the minimum SDK version stripped out.
+ group.push_back(config_descriptor.CopyWithoutSdkVersion());
+ } else {
+ diag->Error(DiagMessage()
+ << "Could not parse config descriptor for empty screen-density-group: "
+ << label);
+ valid = false;
+ }
+
+ return valid;
+ }
+
for (auto* child : root_element->GetChildElements()) {
if (child->name != "screen-density") {
diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
@@ -592,9 +613,28 @@
return false;
}
- auto& group = config->locale_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->locale_groups);
bool valid = true;
+ // Special case to auto insert a locale for an empty group. Label will be used for locale.
+ if (root_element->GetChildElements().empty()) {
+ ConfigDescription config_descriptor;
+ bool parsed = ConfigDescription::Parse(label, &config_descriptor);
+ if (parsed &&
+ (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+ android::ResTable_config::CONFIG_LOCALE)) {
+ // Copy the locale with the minimum SDK version stripped out.
+ group.push_back(config_descriptor.CopyWithoutSdkVersion());
+ } else {
+ diag->Error(DiagMessage()
+ << "Could not parse config descriptor for empty screen-density-group: "
+ << label);
+ valid = false;
+ }
+
+ return valid;
+ }
+
for (auto* child : root_element->GetChildElements()) {
if (child->name != "locale") {
diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
@@ -626,61 +666,58 @@
return valid;
};
-bool AndroidSdkGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
- IDiagnostics* diag) {
- std::string label = GetLabel(root_element, diag);
- if (label.empty()) {
- return false;
- }
-
+bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_element,
+ IDiagnostics* diag) {
+ AndroidSdk entry = AndroidSdk::ForMinSdk(-1);
bool valid = true;
- bool found = false;
+ for (const auto& attr : root_element->attributes) {
+ bool valid_attr = false;
+ if (attr.name == "label") {
+ entry.label = attr.value;
+ valid_attr = true;
+ } else if (attr.name == "minSdkVersion") {
+ Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+ if (version) {
+ valid_attr = true;
+ entry.min_sdk_version = version.value();
+ }
+ } else if (attr.name == "targetSdkVersion") {
+ Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+ if (version) {
+ valid_attr = true;
+ entry.target_sdk_version = version;
+ }
+ } else if (attr.name == "maxSdkVersion") {
+ Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+ if (version) {
+ valid_attr = true;
+ entry.max_sdk_version = version;
+ }
+ }
- for (auto* child : root_element->GetChildElements()) {
- if (child->name != "android-sdk") {
- diag->Error(DiagMessage() << "Unexpected root_element in ABI group: " << child->name);
+ if (!valid_attr) {
+ diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
valid = false;
- } else {
- AndroidSdk entry;
- for (const auto& attr : child->attributes) {
- Maybe<int>* target = nullptr;
- if (attr.name == "minSdkVersion") {
- target = &entry.min_sdk_version;
- } else if (attr.name == "targetSdkVersion") {
- target = &entry.target_sdk_version;
- } else if (attr.name == "maxSdkVersion") {
- target = &entry.max_sdk_version;
- } else {
- diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
- continue;
- }
-
- *target = ResourceUtils::ParseSdkVersion(attr.value);
- if (!*target) {
- diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
- valid = false;
- }
- }
-
- // TODO: Fill in the manifest details when they are finalised.
- for (auto node : child->GetChildElements()) {
- if (node->name == "manifest") {
- if (entry.manifest) {
- diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
- continue;
- }
- entry.manifest = {AndroidManifest()};
- }
- }
-
- config->android_sdk_groups[label] = entry;
- if (found) {
- valid = false;
- }
- found = true;
}
}
+ if (entry.min_sdk_version == -1) {
+ diag->Error(DiagMessage() << "android-sdk is missing minSdkVersion attribute");
+ valid = false;
+ }
+
+ // TODO: Fill in the manifest details when they are finalised.
+ for (auto node : root_element->GetChildElements()) {
+ if (node->name == "manifest") {
+ if (entry.manifest) {
+ diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
+ continue;
+ }
+ entry.manifest = {AndroidManifest()};
+ }
+ }
+
+ config->android_sdks[entry.label] = entry;
return valid;
};
@@ -691,7 +728,7 @@
return false;
}
- auto& group = config->gl_texture_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->gl_texture_groups);
bool valid = true;
GlTexture result;
@@ -734,7 +771,7 @@
return false;
}
- auto& group = config->device_feature_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->device_feature_groups);
bool valid = true;
for (auto* child : root_element->GetChildElements()) {
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index ca58910..7f1d445 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -71,7 +71,8 @@
};
struct AndroidSdk {
- Maybe<int> min_sdk_version;
+ std::string label;
+ int min_sdk_version; // min_sdk_version is mandatory if splitting by SDK.
Maybe<int> target_sdk_version;
Maybe<int> max_sdk_version;
Maybe<AndroidManifest> manifest;
@@ -113,15 +114,19 @@
Maybe<AndroidSdk> android_sdk;
std::vector<DeviceFeature> features;
std::vector<GlTexture> textures;
+
+ inline int GetMinSdk(int default_value = -1) const {
+ if (!android_sdk) {
+ return default_value;
+ }
+ return android_sdk.value().min_sdk_version;
+ }
};
} // namespace configuration
// Forward declaration of classes used in the API.
struct IDiagnostics;
-namespace xml {
-class Element;
-}
/**
* XML configuration file parser for the split and optimize commands.
@@ -133,8 +138,8 @@
static Maybe<ConfigurationParser> ForPath(const std::string& path);
/** Returns a ConfigurationParser for the configuration in the provided file contents. */
- static ConfigurationParser ForContents(const std::string& contents) {
- ConfigurationParser parser{contents};
+ static ConfigurationParser ForContents(const std::string& contents, const std::string& path) {
+ ConfigurationParser parser{contents, path};
return parser;
}
@@ -156,7 +161,7 @@
* diagnostics context. The default diagnostics context can be overridden with a call to
* WithDiagnostics(IDiagnostics *).
*/
- explicit ConfigurationParser(std::string contents);
+ ConfigurationParser(std::string contents, const std::string& config_path);
/** Returns the current diagnostics context to any subclasses. */
IDiagnostics* diagnostics() {
@@ -166,6 +171,8 @@
private:
/** The contents of the configuration file to parse. */
const std::string contents_;
+ /** Path to the input configuration. */
+ const std::string config_path_;
/** The diagnostics context to send messages to. */
IDiagnostics* diag_;
};
diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h
index 7657ebd..a583057 100644
--- a/tools/aapt2/configuration/ConfigurationParser.internal.h
+++ b/tools/aapt2/configuration/ConfigurationParser.internal.h
@@ -17,35 +17,105 @@
#ifndef AAPT2_CONFIGURATIONPARSER_INTERNAL_H
#define AAPT2_CONFIGURATIONPARSER_INTERNAL_H
+#include "configuration/ConfigurationParser.h"
+
+#include <algorithm>
+#include <limits>
+
namespace aapt {
+
+// Forward declaration of classes used in the API.
+namespace xml {
+class Element;
+}
+
namespace configuration {
+template <typename T>
+struct OrderedEntry {
+ size_t order;
+ std::vector<T> entry;
+};
+
/** A mapping of group labels to group of configuration items. */
template <class T>
-using Group = std::unordered_map<std::string, std::vector<T>>;
+using Group = std::unordered_map<std::string, OrderedEntry<T>>;
/** A mapping of group label to a single configuration item. */
template <class T>
using Entry = std::unordered_map<std::string, T>;
+/** Retrieves an entry from the provided Group, creating a new instance if one does not exist. */
+template <typename T>
+std::vector<T>& GetOrCreateGroup(std::string label, Group<T>* group) {
+ OrderedEntry<T>& entry = (*group)[label];
+ // If this is a new entry, set the order.
+ if (entry.order == 0) {
+ entry.order = group->size();
+ }
+ return entry.entry;
+}
+
+/**
+ * A ComparisonChain is a grouping of comparisons to perform when sorting groups that have a well
+ * defined order of precedence. Comparisons are only made if none of the previous comparisons had a
+ * definite result. A comparison has a result if at least one of the items has an entry for that
+ * value and that they are not equal.
+ */
+class ComparisonChain {
+ public:
+ /**
+ * Adds a new comparison of items in a group to the chain. The new comparison is only used if we
+ * have not been able to determine the sort order with the previous comparisons.
+ */
+ template <typename T>
+ ComparisonChain& Add(const Group<T>& groups, const Maybe<std::string>& lhs,
+ const Maybe<std::string>& rhs) {
+ return Add(GetGroupOrder(groups, lhs), GetGroupOrder(groups, rhs));
+ }
+
+ /**
+ * Adds a new comparison to the chain. The new comparison is only used if we have not been able to
+ * determine the sort order with the previous comparisons.
+ */
+ ComparisonChain& Add(int lhs, int rhs) {
+ if (!has_result_) {
+ has_result_ = (lhs != rhs);
+ result_ = (lhs < rhs);
+ }
+ return *this;
+ }
+
+ /** Returns true if the left hand side should come before the right hand side. */
+ bool Compare() {
+ return result_;
+ }
+
+ private:
+ template <typename T>
+ inline size_t GetGroupOrder(const Group<T>& groups, const Maybe<std::string>& label) {
+ if (!label) {
+ return std::numeric_limits<size_t>::max();
+ }
+ return groups.at(label.value()).order;
+ }
+
+ bool has_result_ = false;
+ bool result_ = false;
+};
+
/** Output artifact configuration options. */
struct ConfiguredArtifact {
/** Name to use for output of processing foo.apk -> foo.<name>.apk. */
Maybe<std::string> name;
- /**
- * Value to add to the base Android manifest versionCode. If it is not present in the
- * configuration file, it is set to the previous artifact + 1. If the first artifact does not have
- * a value, artifacts are a 1 based index.
- */
- int version;
/** If present, uses the ABI group with this name. */
Maybe<std::string> abi_group;
/** If present, uses the screen density group with this name. */
Maybe<std::string> screen_density_group;
/** If present, uses the locale group with this name. */
Maybe<std::string> locale_group;
- /** If present, uses the Android SDK group with this name. */
- Maybe<std::string> android_sdk_group;
+ /** If present, uses the Android SDK with this name. */
+ Maybe<std::string> android_sdk;
/** If present, uses the device feature group with this name. */
Maybe<std::string> device_feature_group;
/** If present, uses the OpenGL texture group with this name. */
@@ -57,31 +127,71 @@
/** Convert an artifact name template into a name string based on configuration contents. */
Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const;
-
- bool operator<(const ConfiguredArtifact& rhs) const {
- // TODO(safarmer): Order by play store multi-APK requirements.
- return version < rhs.version;
- }
-
- bool operator==(const ConfiguredArtifact& rhs) const {
- return version == rhs.version;
- }
};
/** AAPT2 XML configuration file binary representation. */
struct PostProcessingConfiguration {
- // TODO: Support named artifacts?
std::vector<ConfiguredArtifact> artifacts;
Maybe<std::string> artifact_format;
Group<Abi> abi_groups;
Group<ConfigDescription> screen_density_groups;
Group<ConfigDescription> locale_groups;
- Entry<AndroidSdk> android_sdk_groups;
Group<DeviceFeature> device_feature_groups;
Group<GlTexture> gl_texture_groups;
+ Entry<AndroidSdk> android_sdks;
+
+ /**
+ * Sorts the configured artifacts based on the ordering of the groups in the configuration file.
+ * The only exception to this rule is Android SDK versions. Larger SDK versions will have a larger
+ * versionCode to ensure users get the correct APK when they upgrade their OS.
+ */
+ void SortArtifacts() {
+ std::sort(artifacts.begin(), artifacts.end(), *this);
+ }
+
+ /** Comparator that ensures artifacts are in the preferred order for versionCode rewriting. */
+ bool operator()(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) {
+ // Split dimensions are added in the order of precedence. Items higher in the list result in
+ // higher version codes.
+ return ComparisonChain()
+ // All splits with a minSdkVersion specified must be last to ensure the application will be
+ // updated if a user upgrades the version of Android on their device.
+ .Add(GetMinSdk(lhs), GetMinSdk(rhs))
+ // ABI version is important, especially on x86 phones where they may begin to run in ARM
+ // emulation mode on newer Android versions. This allows us to ensure that the x86 version
+ // is installed on these devices rather than ARM.
+ .Add(abi_groups, lhs.abi_group, rhs.abi_group)
+ // The rest are in arbitrary order based on estimated usage.
+ .Add(screen_density_groups, lhs.screen_density_group, rhs.screen_density_group)
+ .Add(locale_groups, lhs.locale_group, rhs.locale_group)
+ .Add(gl_texture_groups, lhs.gl_texture_group, rhs.gl_texture_group)
+ .Add(device_feature_groups, lhs.device_feature_group, rhs.device_feature_group)
+ .Compare();
+ }
+
+ private:
+ /**
+ * Returns the min_sdk_version from the provided artifact or 0 if none is present. This allows
+ * artifacts that have an Android SDK version to have a higher versionCode than those that do not.
+ */
+ inline int GetMinSdk(const ConfiguredArtifact& artifact) {
+ if (!artifact.android_sdk) {
+ return 0;
+ }
+ const auto& entry = android_sdks.find(artifact.android_sdk.value());
+ if (entry == android_sdks.end()) {
+ return 0;
+ }
+ return entry->second.min_sdk_version;
+ }
};
+/** Parses the provided XML document returning the post processing configuration. */
+Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
+ const std::string& config_path,
+ IDiagnostics* diag);
+
namespace handler {
/** Handler for <artifact> tags. */
@@ -104,9 +214,9 @@
bool LocaleGroupTagHandler(configuration::PostProcessingConfiguration* config,
xml::Element* element, IDiagnostics* diag);
-/** Handler for <android-sdk-group> tags. */
-bool AndroidSdkGroupTagHandler(configuration::PostProcessingConfiguration* config,
- xml::Element* element, IDiagnostics* diag);
+/** Handler for <android-sdk> tags. */
+bool AndroidSdkTagHandler(configuration::PostProcessingConfiguration* config, xml::Element* element,
+ IDiagnostics* diag);
/** Handler for <gl-texture-group> tags. */
bool GlTextureGroupTagHandler(configuration::PostProcessingConfiguration* config,
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index 3f356d7..0329846 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -30,11 +30,33 @@
namespace configuration {
void PrintTo(const AndroidSdk& sdk, std::ostream* os) {
- *os << "SDK: min=" << sdk.min_sdk_version.value_or_default(-1)
+ *os << "SDK: min=" << sdk.min_sdk_version
<< ", target=" << sdk.target_sdk_version.value_or_default(-1)
<< ", max=" << sdk.max_sdk_version.value_or_default(-1);
}
+bool operator==(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) {
+ return lhs.name == rhs.name && lhs.abi_group == rhs.abi_group &&
+ lhs.screen_density_group == rhs.screen_density_group &&
+ lhs.locale_group == rhs.locale_group && lhs.android_sdk == rhs.android_sdk &&
+ lhs.device_feature_group == rhs.device_feature_group &&
+ lhs.gl_texture_group == rhs.gl_texture_group;
+}
+
+std::ostream& operator<<(std::ostream& out, const Maybe<std::string>& value) {
+ PrintTo(value, &out);
+ return out;
+}
+
+void PrintTo(const ConfiguredArtifact& artifact, std::ostream* os) {
+ *os << "\n{"
+ << "\n name: " << artifact.name << "\n sdk: " << artifact.android_sdk
+ << "\n abi: " << artifact.abi_group << "\n density: " << artifact.screen_density_group
+ << "\n locale: " << artifact.locale_group
+ << "\n features: " << artifact.device_feature_group
+ << "\n textures: " << artifact.gl_texture_group << "\n}\n";
+}
+
namespace handler {
namespace {
@@ -44,6 +66,7 @@
using ::aapt::configuration::AndroidSdk;
using ::aapt::configuration::ConfiguredArtifact;
using ::aapt::configuration::DeviceFeature;
+using ::aapt::configuration::ExtractConfiguration;
using ::aapt::configuration::GlTexture;
using ::aapt::configuration::Locale;
using ::aapt::configuration::PostProcessingConfiguration;
@@ -52,11 +75,13 @@
using ::android::ResTable_config;
using ::android::base::StringPrintf;
using ::testing::ElementsAre;
+using ::testing::Eq;
using ::testing::SizeIs;
+using ::testing::StrEq;
constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?>
<post-process xmlns="http://schemas.android.com/tools/aapt">
- <groups>
+ <abi-groups>
<abi-group label="arm">
<abi>armeabi-v7a</abi>
<abi>arm64-v8a</abi>
@@ -65,6 +90,8 @@
<abi>x86</abi>
<abi>mips</abi>
</abi-group>
+ </abi-groups>
+ <screen-density-groups>
<screen-density-group label="large">
<screen-density>xhdpi</screen-density>
<screen-density>xxhdpi</screen-density>
@@ -78,6 +105,8 @@
<screen-density>xxhdpi</screen-density>
<screen-density>xxxhdpi</screen-density>
</screen-density-group>
+ </screen-density-groups>
+ <locale-groups>
<locale-group label="europe">
<locale>en</locale>
<locale>es</locale>
@@ -89,25 +118,30 @@
<locale>es-rMX</locale>
<locale>fr-rCA</locale>
</locale-group>
- <android-sdk-group label="v19">
- <android-sdk
- minSdkVersion="19"
- targetSdkVersion="24"
- maxSdkVersion="25">
- <manifest>
- <!--- manifest additions here XSLT? TODO -->
- </manifest>
- </android-sdk>
- </android-sdk-group>
+ </locale-groups>
+ <android-sdks>
+ <android-sdk
+ label="v19"
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>
+ </android-sdks>
+ <gl-texture-groups>
<gl-texture-group label="dxt1">
<gl-texture name="GL_EXT_texture_compression_dxt1">
<texture-path>assets/dxt1/*</texture-path>
</gl-texture>
</gl-texture-group>
+ </gl-texture-groups>
+ <device-feature-groups>
<device-feature-group label="low-latency">
<supports-feature>android.hardware.audio.low_latency</supports-feature>
</device-feature-group>
- </groups>
+ </device-feature-groups>
<artifacts>
<artifact-format>
${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
@@ -117,7 +151,7 @@
abi-group="arm"
screen-density-group="large"
locale-group="europe"
- android-sdk-group="v19"
+ android-sdk="v19"
gl-texture-group="dxt1"
device-feature-group="low-latency"/>
<artifact
@@ -125,7 +159,7 @@
abi-group="other"
screen-density-group="alldpi"
locale-group="north-america"
- android-sdk-group="v19"
+ android-sdk="v19"
gl-texture-group="dxt1"
device-feature-group="low-latency"/>
</artifacts>
@@ -134,7 +168,8 @@
class ConfigurationParserTest : public ConfigurationParser, public ::testing::Test {
public:
- ConfigurationParserTest() : ConfigurationParser("") {}
+ ConfigurationParserTest() : ConfigurationParser("", "config.xml") {
+ }
protected:
StdErrDiagnostics diag_;
@@ -145,8 +180,31 @@
EXPECT_FALSE(result);
}
+TEST_F(ConfigurationParserTest, ExtractConfiguration) {
+ Maybe<PostProcessingConfiguration> maybe_config =
+ ExtractConfiguration(kValidConfig, "dummy.xml", &diag_);
+
+ PostProcessingConfiguration config = maybe_config.value();
+
+ auto& arm = config.abi_groups["arm"];
+ auto& other = config.abi_groups["other"];
+ EXPECT_EQ(arm.order, 1ul);
+ EXPECT_EQ(other.order, 2ul);
+
+ auto& large = config.screen_density_groups["large"];
+ auto& alldpi = config.screen_density_groups["alldpi"];
+ EXPECT_EQ(large.order, 1ul);
+ EXPECT_EQ(alldpi.order, 2ul);
+
+ auto& north_america = config.locale_groups["north-america"];
+ auto& europe = config.locale_groups["europe"];
+ // Checked in reverse to make sure access order does not matter.
+ EXPECT_EQ(north_america.order, 2ul);
+ EXPECT_EQ(europe.order, 1ul);
+}
+
TEST_F(ConfigurationParserTest, ValidateFile) {
- auto parser = ConfigurationParser::ForContents(kValidConfig).WithDiagnostics(&diag_);
+ auto parser = ConfigurationParser::ForContents(kValidConfig, "conf.xml").WithDiagnostics(&diag_);
auto result = parser.Parse("test.apk");
ASSERT_TRUE(result);
const std::vector<OutputArtifact>& value = result.value();
@@ -154,6 +212,7 @@
const OutputArtifact& a1 = value[0];
EXPECT_EQ(a1.name, "art1.apk");
+ EXPECT_EQ(a1.version, 1);
EXPECT_THAT(a1.abis, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
EXPECT_THAT(a1.screen_densities,
ElementsAre(test::ParseConfigOrDie("xhdpi").CopyWithoutSdkVersion(),
@@ -161,12 +220,15 @@
test::ParseConfigOrDie("xxxhdpi").CopyWithoutSdkVersion()));
EXPECT_THAT(a1.locales, ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es"),
test::ParseConfigOrDie("fr"), test::ParseConfigOrDie("de")));
- EXPECT_EQ(a1.android_sdk.value().min_sdk_version.value(), 19l);
+ ASSERT_TRUE(a1.android_sdk);
+ ASSERT_TRUE(a1.android_sdk.value().min_sdk_version);
+ EXPECT_EQ(a1.android_sdk.value().min_sdk_version, 19l);
EXPECT_THAT(a1.textures, SizeIs(1ul));
EXPECT_THAT(a1.features, SizeIs(1ul));
const OutputArtifact& a2 = value[1];
EXPECT_EQ(a2.name, "art2.apk");
+ EXPECT_EQ(a2.version, 2);
EXPECT_THAT(a2.abis, ElementsAre(Abi::kX86, Abi::kMips));
EXPECT_THAT(a2.screen_densities,
ElementsAre(test::ParseConfigOrDie("ldpi").CopyWithoutSdkVersion(),
@@ -178,124 +240,138 @@
EXPECT_THAT(a2.locales,
ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es-rMX"),
test::ParseConfigOrDie("fr-rCA")));
- EXPECT_EQ(a2.android_sdk.value().min_sdk_version.value(), 19l);
+ ASSERT_TRUE(a2.android_sdk);
+ ASSERT_TRUE(a2.android_sdk.value().min_sdk_version);
+ EXPECT_EQ(a2.android_sdk.value().min_sdk_version, 19l);
EXPECT_THAT(a2.textures, SizeIs(1ul));
EXPECT_THAT(a2.features, SizeIs(1ul));
}
+TEST_F(ConfigurationParserTest, ConfiguredArtifactOrdering) {
+ // Create a base builder with the configuration groups but no artifacts to allow it to be copied.
+ test::PostProcessingConfigurationBuilder base_builder = test::PostProcessingConfigurationBuilder()
+ .AddAbiGroup("arm")
+ .AddAbiGroup("arm64")
+ .AddAndroidSdk("v23", 23)
+ .AddAndroidSdk("v19", 19);
+
+ {
+ // Test version ordering.
+ ConfiguredArtifact v23;
+ v23.android_sdk = {"v23"};
+ ConfiguredArtifact v19;
+ v19.android_sdk = {"v19"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(v23).AddArtifact(v19).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(v19));
+ EXPECT_THAT(config.artifacts[1], Eq(v23));
+ }
+
+ {
+ // Test ABI ordering.
+ ConfiguredArtifact arm;
+ arm.android_sdk = {"v19"};
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact arm64;
+ arm64.android_sdk = {"v19"};
+ arm64.abi_group = {"arm64"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(arm64).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm));
+ EXPECT_THAT(config.artifacts[1], Eq(arm64));
+ }
+
+ {
+ // Test Android SDK has precedence over ABI.
+ ConfiguredArtifact arm;
+ arm.android_sdk = {"v23"};
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact arm64;
+ arm64.android_sdk = {"v19"};
+ arm64.abi_group = {"arm64"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(arm64).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm64));
+ EXPECT_THAT(config.artifacts[1], Eq(arm));
+ }
+
+ {
+ // Test version is better than ABI.
+ ConfiguredArtifact arm;
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact v19;
+ v19.android_sdk = {"v19"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(v19).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm));
+ EXPECT_THAT(config.artifacts[1], Eq(v19));
+ }
+
+ {
+ // Test version is sorted higher than no version.
+ ConfiguredArtifact arm;
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact v19;
+ v19.abi_group = {"arm"};
+ v19.android_sdk = {"v19"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(v19).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm));
+ EXPECT_THAT(config.artifacts[1], Eq(v19));
+ }
+}
+
TEST_F(ConfigurationParserTest, InvalidNamespace) {
constexpr const char* invalid_ns = R"(<?xml version="1.0" encoding="utf-8" ?>
- <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
+ <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
- auto result = ConfigurationParser::ForContents(invalid_ns).Parse("test.apk");
+ auto result = ConfigurationParser::ForContents(invalid_ns, "config.xml").Parse("test.apk");
ASSERT_FALSE(result);
}
TEST_F(ConfigurationParserTest, ArtifactAction) {
PostProcessingConfiguration config;
- {
- const auto doc = test::BuildXmlDom(R"xml(
+ const auto doc = test::BuildXmlDom(R"xml(
<artifact
abi-group="arm"
screen-density-group="large"
locale-group="europe"
- android-sdk-group="v19"
+ android-sdk="v19"
gl-texture-group="dxt1"
device-feature-group="low-latency"/>)xml");
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc->root.get()), &diag_));
+ ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(1ul));
+ EXPECT_THAT(config.artifacts, SizeIs(1ul));
- auto& artifact = config.artifacts.back();
- EXPECT_FALSE(artifact.name); // TODO: make this fail.
- EXPECT_EQ(1, artifact.version);
- EXPECT_EQ("arm", artifact.abi_group.value());
- EXPECT_EQ("large", artifact.screen_density_group.value());
- EXPECT_EQ("europe", artifact.locale_group.value());
- EXPECT_EQ("v19", artifact.android_sdk_group.value());
- EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
- EXPECT_EQ("low-latency", artifact.device_feature_group.value());
- }
-
- {
- // Perform a second action to ensure we get 2 artifacts.
- const auto doc = test::BuildXmlDom(R"xml(
- <artifact
- abi-group="other"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>)xml");
-
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(2ul));
- EXPECT_EQ(2, config.artifacts.back().version);
- }
-
- {
- // Perform a third action with a set version code.
- const auto doc = test::BuildXmlDom(R"xml(
- <artifact
- version="5"
- abi-group="other"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>)xml");
-
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(3ul));
- EXPECT_EQ(5, config.artifacts.back().version);
- }
-
- {
- // Perform a fourth action to ensure the version code still increments.
- const auto doc = test::BuildXmlDom(R"xml(
- <artifact
- abi-group="other"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>)xml");
-
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(4ul));
- EXPECT_EQ(6, config.artifacts.back().version);
- }
-}
-
-TEST_F(ConfigurationParserTest, DuplicateArtifactVersion) {
- static constexpr const char* configuration = R"xml(<?xml version="1.0" encoding="utf-8" ?>
- <pst-process xmlns="http://schemas.android.com/tools/aapt">>
- <artifacts>
- <artifact-format>
- ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
- </artifact-format>
- <artifact
- name="art1"
- abi-group="arm"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>
- <artifact
- name="art2"
- version = "1"
- abi-group="other"
- screen-density-group="alldpi"
- locale-group="north-america"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>
- </artifacts>
- </post-process>)xml";
- auto result = ConfigurationParser::ForContents(configuration).Parse("test.apk");
- ASSERT_FALSE(result);
+ auto& artifact = config.artifacts.back();
+ EXPECT_FALSE(artifact.name); // TODO: make this fail.
+ EXPECT_EQ("arm", artifact.abi_group.value());
+ EXPECT_EQ("large", artifact.screen_density_group.value());
+ EXPECT_EQ("europe", artifact.locale_group.value());
+ EXPECT_EQ("v19", artifact.android_sdk.value());
+ EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
+ EXPECT_EQ("low-latency", artifact.device_feature_group.value());
}
TEST_F(ConfigurationParserTest, ArtifactFormatAction) {
@@ -334,10 +410,36 @@
EXPECT_THAT(config.abi_groups, SizeIs(1ul));
ASSERT_EQ(1u, config.abi_groups.count("arm"));
- auto& out = config.abi_groups["arm"];
+ auto& out = config.abi_groups["arm"].entry;
ASSERT_THAT(out, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
}
+TEST_F(ConfigurationParserTest, AbiGroupAction_EmptyGroup) {
+ static constexpr const char* xml = R"xml(<abi-group label="arm64-v8a"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ EXPECT_THAT(config.abi_groups, SizeIs(1ul));
+ ASSERT_EQ(1u, config.abi_groups.count("arm64-v8a"));
+
+ auto& out = config.abi_groups["arm64-v8a"].entry;
+ ASSERT_THAT(out, ElementsAre(Abi::kArm64V8a));
+}
+
+TEST_F(ConfigurationParserTest, AbiGroupAction_InvalidEmptyGroup) {
+ static constexpr const char* xml = R"xml(<abi-group label="arm"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_FALSE(ok);
+}
+
TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) {
static constexpr const char* xml = R"xml(
<screen-density-group label="large">
@@ -364,10 +466,39 @@
ConfigDescription xxxhdpi;
xxxhdpi.density = ResTable_config::DENSITY_XXXHIGH;
- auto& out = config.screen_density_groups["large"];
+ auto& out = config.screen_density_groups["large"].entry;
ASSERT_THAT(out, ElementsAre(xhdpi, xxhdpi, xxxhdpi));
}
+TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_EmtpyGroup) {
+ static constexpr const char* xml = R"xml(<screen-density-group label="xhdpi"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ EXPECT_THAT(config.screen_density_groups, SizeIs(1ul));
+ ASSERT_EQ(1u, config.screen_density_groups.count("xhdpi"));
+
+ ConfigDescription xhdpi;
+ xhdpi.density = ResTable_config::DENSITY_XHIGH;
+
+ auto& out = config.screen_density_groups["xhdpi"].entry;
+ ASSERT_THAT(out, ElementsAre(xhdpi));
+}
+
+TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_InvalidEmtpyGroup) {
+ static constexpr const char* xml = R"xml(<screen-density-group label="really-big-screen"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_FALSE(ok);
+}
+
TEST_F(ConfigurationParserTest, LocaleGroupAction) {
static constexpr const char* xml = R"xml(
<locale-group label="europe">
@@ -386,7 +517,7 @@
ASSERT_EQ(1ul, config.locale_groups.size());
ASSERT_EQ(1u, config.locale_groups.count("europe"));
- const auto& out = config.locale_groups["europe"];
+ const auto& out = config.locale_groups["europe"].entry;
ConfigDescription en = test::ParseConfigOrDie("en");
ConfigDescription es = test::ParseConfigOrDie("es");
@@ -396,29 +527,56 @@
ASSERT_THAT(out, ElementsAre(en, es, fr, de));
}
+TEST_F(ConfigurationParserTest, LocaleGroupAction_EmtpyGroup) {
+ static constexpr const char* xml = R"xml(<locale-group label="en"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ ASSERT_EQ(1ul, config.locale_groups.size());
+ ASSERT_EQ(1u, config.locale_groups.count("en"));
+
+ const auto& out = config.locale_groups["en"].entry;
+
+ ConfigDescription en = test::ParseConfigOrDie("en");
+
+ ASSERT_THAT(out, ElementsAre(en));
+}
+
+TEST_F(ConfigurationParserTest, LocaleGroupAction_InvalidEmtpyGroup) {
+ static constexpr const char* xml = R"xml(<locale-group label="arm64"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_FALSE(ok);
+}
+
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) {
static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk
+ <android-sdk label="v19"
minSdkVersion="19"
targetSdkVersion="24"
maxSdkVersion="25">
<manifest>
<!--- manifest additions here XSLT? TODO -->
</manifest>
- </android-sdk>
- </android-sdk-group>)xml";
+ </android-sdk>)xml";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
+ auto& out = config.android_sdks["v19"];
AndroidSdk sdk;
sdk.min_sdk_version = 19;
@@ -431,98 +589,86 @@
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_SingleVersion) {
{
- static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk minSdkVersion="19"></android-sdk>
- </android-sdk-group>)xml";
-
+ const char* xml = "<android-sdk label='v19' minSdkVersion='19'></android-sdk>";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
- EXPECT_EQ(19, out.min_sdk_version.value());
+ auto& out = config.android_sdks["v19"];
+ EXPECT_EQ(19, out.min_sdk_version);
EXPECT_FALSE(out.max_sdk_version);
EXPECT_FALSE(out.target_sdk_version);
}
{
- static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk maxSdkVersion="19"></android-sdk>
- </android-sdk-group>)xml";
-
+ const char* xml =
+ "<android-sdk label='v19' minSdkVersion='19' maxSdkVersion='19'></android-sdk>";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
+ auto& out = config.android_sdks["v19"];
EXPECT_EQ(19, out.max_sdk_version.value());
- EXPECT_FALSE(out.min_sdk_version);
+ EXPECT_EQ(19, out.min_sdk_version);
EXPECT_FALSE(out.target_sdk_version);
}
{
- static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk targetSdkVersion="19"></android-sdk>
- </android-sdk-group>)xml";
-
+ const char* xml =
+ "<android-sdk label='v19' minSdkVersion='19' targetSdkVersion='19'></android-sdk>";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
+ auto& out = config.android_sdks["v19"];
EXPECT_EQ(19, out.target_sdk_version.value());
- EXPECT_FALSE(out.min_sdk_version);
+ EXPECT_EQ(19, out.min_sdk_version);
EXPECT_FALSE(out.max_sdk_version);
}
}
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_InvalidVersion) {
static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk
- minSdkVersion="v19"
- targetSdkVersion="v24"
- maxSdkVersion="v25">
- <manifest>
- <!--- manifest additions here XSLT? TODO -->
- </manifest>
- </android-sdk>
- </android-sdk-group>)xml";
+ <android-sdk
+ label="v19"
+ minSdkVersion="v19"
+ targetSdkVersion="v24"
+ maxSdkVersion="v25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>)xml";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_FALSE(ok);
}
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) {
static constexpr const char* xml = R"xml(
- <android-sdk-group label="P">
<android-sdk
+ label="P"
minSdkVersion="25"
targetSdkVersion="%s"
maxSdkVersion="%s">
- </android-sdk>
- </android-sdk-group>)xml";
+ </android-sdk>)xml";
const auto& dev_sdk = GetDevelopmentSdkCodeNameAndVersion();
const char* codename = dev_sdk.first.data();
@@ -531,13 +677,13 @@
auto doc = test::BuildXmlDom(StringPrintf(xml, codename, codename));
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("P"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("P"));
- auto& out = config.android_sdk_groups["P"];
+ auto& out = config.android_sdks["P"];
AndroidSdk sdk;
sdk.min_sdk_version = 25;
@@ -567,7 +713,7 @@
EXPECT_THAT(config.gl_texture_groups, SizeIs(1ul));
ASSERT_EQ(1u, config.gl_texture_groups.count("dxt1"));
- auto& out = config.gl_texture_groups["dxt1"];
+ auto& out = config.gl_texture_groups["dxt1"].entry;
GlTexture texture{
std::string("GL_EXT_texture_compression_dxt1"),
@@ -596,7 +742,7 @@
EXPECT_THAT(config.device_feature_groups, SizeIs(1ul));
ASSERT_EQ(1u, config.device_feature_groups.count("low-latency"));
- auto& out = config.device_feature_groups["low-latency"];
+ auto& out = config.device_feature_groups["low-latency"].entry;
DeviceFeature low_latency = "android.hardware.audio.low_latency";
DeviceFeature pro = "android.hardware.audio.pro";
@@ -650,7 +796,7 @@
artifact.device_feature_group = {"df1"};
artifact.gl_texture_group = {"glx1"};
artifact.locale_group = {"en-AU"};
- artifact.android_sdk_group = {"v26"};
+ artifact.android_sdk = {"v26"};
{
auto result = artifact.ToArtifactName(
diff --git a/tools/aapt2/configuration/aapt2.xsd b/tools/aapt2/configuration/aapt2.xsd
index 134153a..fb2f49b 100644
--- a/tools/aapt2/configuration/aapt2.xsd
+++ b/tools/aapt2/configuration/aapt2.xsd
@@ -8,22 +8,52 @@
<xsd:element name="post-process">
<xsd:complexType>
<xsd:sequence>
- <xsd:element name="groups" type="groups"/>
<xsd:element name="artifacts" type="artifacts"/>
+ <xsd:element name="android-sdks" type="android-sdks"/>
+ <xsd:element name="abi-groups" type="abi-groups"/>
+ <xsd:element name="screen-density-groups" type="screen-density-groups"/>
+ <xsd:element name="locale-groups" type="locale-groups"/>
+ <xsd:element name="gl-texture-groups" type="gl-texture-groups"/>
+ <xsd:element name="device-feature-groups" type="device-feature-groups"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
- <xsd:complexType name="groups">
+ <xsd:complexType name="android-sdks">
+ <xsd:sequence>
+ <xsd:element name="android-sdk" type="android-sdk" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="abi-groups">
<xsd:sequence>
<xsd:element name="abi-group" type="abi-group" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="screen-density-groups">
+ <xsd:sequence>
<xsd:element name="screen-density-group" type="screen-density-group" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="locale-groups">
+ <xsd:sequence>
<xsd:element name="locale-group" type="locale-group" maxOccurs="unbounded"/>
- <xsd:element name="android-sdk-group" type="android-sdk-group" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="gl-texture-groups">
+ <xsd:sequence>
<xsd:element
name="gl-texture-group"
type="gl-texture-group"
maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="device-feature-groups">
+ <xsd:sequence>
<xsd:element name="device-feature-group" type="device-feature-group" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
@@ -38,8 +68,6 @@
<!-- Groups output artifacts together by dimension labels. -->
<xsd:complexType name="artifact">
- <xsd:attribute name="name" type="xsd:string"/>
- <xsd:attribute name="version" type="xsd:integer"/>
<xsd:attribute name="abi-group" type="xsd:string"/>
<xsd:attribute name="android-sdk-group" type="xsd:string"/>
<xsd:attribute name="device-feature-group" type="xsd:string"/>
@@ -52,7 +80,7 @@
<xsd:sequence>
<xsd:element name="gl-texture" type="gl-texture" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="gl-texture">
@@ -66,14 +94,14 @@
<xsd:sequence>
<xsd:element name="supports-feature" type="xsd:string" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="abi-group">
<xsd:sequence>
<xsd:element name="abi" type="abi-name" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:simpleType name="abi-name">
@@ -93,7 +121,7 @@
<xsd:sequence>
<xsd:element name="screen-density" type="screen-density" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:simpleType name="screen-density">
@@ -108,20 +136,14 @@
</xsd:restriction>
</xsd:simpleType>
- <xsd:complexType name="android-sdk-group">
- <xsd:sequence>
- <xsd:element name="android-sdk" type="android-sdk" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
- </xsd:complexType>
-
<xsd:complexType name="android-sdk">
<!-- TODO(safarmer): Add permissions to add/remove. -->
<!-- TODO(safarmer): Add option for uncompressed native libs. -->
<xsd:sequence>
<xsd:element name="manifest" type="manifest"/>
</xsd:sequence>
- <xsd:attribute name="minSdkVersion" type="xsd:integer"/>
+ <xsd:attribute name="label" type="xsd:string" use="required"/>
+ <xsd:attribute name="minSdkVersion" type="xsd:integer" use="required"/>
<xsd:attribute name="targetSdkVersion" type="xsd:integer"/>
<xsd:attribute name="maxSdkVersion" type="xsd:integer"/>
</xsd:complexType>
@@ -135,7 +157,7 @@
<xsd:sequence>
<xsd:element name="locale" type="locale" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="locale">
diff --git a/tools/aapt2/configuration/example/config.xml b/tools/aapt2/configuration/example/config.xml
index ce31e61..d8aba09 100644
--- a/tools/aapt2/configuration/example/config.xml
+++ b/tools/aapt2/configuration/example/config.xml
@@ -1,70 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<post-process xmlns="http://schemas.android.com/tools/aapt">
- <groups>
- <abi-group label="arm">
- <abi>armeabi-v7a</abi>
- <abi>arm64-v8a</abi>
- </abi-group>
-
- <abi-group label="other">
- <abi>x86</abi>
- <abi>mips</abi>
- </abi-group>
-
- <screen-density-group label="large">
- <screen-density>xhdpi</screen-density>
- <screen-density>xxhdpi</screen-density>
- <screen-density>xxxhdpi</screen-density>
- </screen-density-group>
-
- <screen-density-group label="alldpi">
- <screen-density>ldpi</screen-density>
- <screen-density>mdpi</screen-density>
- <screen-density>hdpi</screen-density>
- <screen-density>xhdpi</screen-density>
- <screen-density>xxhdpi</screen-density>
- <screen-density>xxxhdpi</screen-density>
- </screen-density-group>
-
- <locale-group label="europe">
- <locale lang="en"/>
- <locale lang="es"/>
- <locale lang="fr"/>
- <locale lang="de" compressed="true"/>
- </locale-group>
-
- <locale-group label="north-america">
- <locale lang="en"/>
- <locale lang="es" region="MX"/>
- <locale lang="fr" region="CA" compressed="true"/>
- </locale-group>
-
- <locale-group label="all">
- <locale compressed="true"/>
- </locale-group>
-
- <android-sdk-group label="19">
- <android-sdk
- minSdkVersion="19"
- targetSdkVersion="24"
- maxSdkVersion="25">
- <manifest>
- <!--- manifest additions here XSLT? TODO -->
- </manifest>
- </android-sdk>
- </android-sdk-group>
-
- <gl-texture-group label="dxt1">
- <gl-texture name="GL_EXT_texture_compression_dxt1">
- <texture-path>assets/dxt1/*</texture-path>
- </gl-texture>
- </gl-texture-group>
-
- <device-feature-group label="low-latency">
- <supports-feature>android.hardware.audio.low_latency</supports-feature>
- </device-feature-group>
- </groups>
-
<artifacts>
<artifact-format>
${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
@@ -87,4 +22,79 @@
device-feature-group="low-latency"/>
</artifacts>
+
+ <android-sdks>
+ <android-sdk
+ label="19"
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>
+ </android-sdks>
+
+ <abi-groups>
+ <abi-group label="arm">
+ <abi>armeabi-v7a</abi>
+ <abi>arm64-v8a</abi>
+ </abi-group>
+
+ <abi-group label="other">
+ <abi>x86</abi>
+ <abi>mips</abi>
+ </abi-group>
+ </abi-groups>
+
+ <screen-density-groups>
+ <screen-density-group label="large">
+ <screen-density>xhdpi</screen-density>
+ <screen-density>xxhdpi</screen-density>
+ <screen-density>xxxhdpi</screen-density>
+ </screen-density-group>
+
+ <screen-density-group label="alldpi">
+ <screen-density>ldpi</screen-density>
+ <screen-density>mdpi</screen-density>
+ <screen-density>hdpi</screen-density>
+ <screen-density>xhdpi</screen-density>
+ <screen-density>xxhdpi</screen-density>
+ <screen-density>xxxhdpi</screen-density>
+ </screen-density-group>
+ </screen-density-groups>
+
+ <locale-groups>
+ <locale-group label="europe">
+ <locale lang="en"/>
+ <locale lang="es"/>
+ <locale lang="fr"/>
+ <locale lang="de" compressed="true"/>
+ </locale-group>
+
+ <locale-group label="north-america">
+ <locale lang="en"/>
+ <locale lang="es" region="MX"/>
+ <locale lang="fr" region="CA" compressed="true"/>
+ </locale-group>
+
+ <locale-group label="all">
+ <locale compressed="true"/>
+ </locale-group>
+ </locale-groups>
+
+ <gl-texture-groups>
+ <gl-texture-group label="dxt1">
+ <gl-texture name="GL_EXT_texture_compression_dxt1">
+ <texture-path>assets/dxt1/*</texture-path>
+ </gl-texture>
+ </gl-texture-group>
+ </gl-texture-groups>
+
+ <device-feature-groups>
+ <device-feature-group label="low-latency">
+ <supports-feature>android.hardware.audio.low_latency</supports-feature>
+ </device-feature-group>
+ </device-feature-groups>
+
</post-process>
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 16898d6..991faad 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -120,8 +120,6 @@
}
bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) {
- // TODO(safarmer): Handle APK version codes for the generated APKs.
-
std::unordered_set<std::string> artifacts_to_keep = options.kept_artifacts;
std::unordered_set<std::string> filtered_artifacts;
std::unordered_set<std::string> kept_artifacts;
@@ -237,8 +235,8 @@
splits.config_filter = &axis_filter;
}
- if (artifact.android_sdk && artifact.android_sdk.value().min_sdk_version) {
- wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version.value());
+ if (artifact.android_sdk) {
+ wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version);
}
std::unique_ptr<ResourceTable> table = old_table.Clone();
@@ -301,7 +299,7 @@
if (xml::Attribute* min_sdk_attr =
uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
// Populate with a pre-compiles attribute to we don't need to relink etc.
- const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version.value());
+ const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version);
min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
} else {
// There was no minSdkVersion. This is strange since at this point we should have been
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index a3e2f80..495a48a 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -26,6 +26,7 @@
using ::aapt::configuration::Abi;
using ::aapt::configuration::AndroidSdk;
using ::aapt::configuration::ConfiguredArtifact;
+using ::aapt::configuration::GetOrCreateGroup;
using ::aapt::io::StringInputStream;
using ::android::StringPiece;
@@ -227,6 +228,11 @@
return *this;
}
+ArtifactBuilder& ArtifactBuilder::SetVersion(int version) {
+ artifact_.version = version;
+ return *this;
+}
+
ArtifactBuilder& ArtifactBuilder::AddAbi(configuration::Abi abi) {
artifact_.abis.push_back(abi);
return *this;
@@ -251,5 +257,54 @@
return artifact_;
}
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddAbiGroup(
+ const std::string& label, std::vector<configuration::Abi> abis) {
+ return AddGroup(label, &config_.abi_groups, std::move(abis));
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddDensityGroup(
+ const std::string& label, std::vector<std::string> densities) {
+ std::vector<ConfigDescription> configs;
+ for (const auto& density : densities) {
+ configs.push_back(test::ParseConfigOrDie(density));
+ }
+ return AddGroup(label, &config_.screen_density_groups, configs);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddLocaleGroup(
+ const std::string& label, std::vector<std::string> locales) {
+ std::vector<ConfigDescription> configs;
+ for (const auto& locale : locales) {
+ configs.push_back(test::ParseConfigOrDie(locale));
+ }
+ return AddGroup(label, &config_.locale_groups, configs);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddDeviceFeatureGroup(
+ const std::string& label) {
+ return AddGroup(label, &config_.device_feature_groups);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddGlTextureGroup(
+ const std::string& label) {
+ return AddGroup(label, &config_.gl_texture_groups);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddAndroidSdk(
+ std::string label, int min_sdk) {
+ config_.android_sdks[label] = AndroidSdk::ForMinSdk(min_sdk);
+ return *this;
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddArtifact(
+ configuration::ConfiguredArtifact artifact) {
+ config_.artifacts.push_back(std::move(artifact));
+ return *this;
+}
+
+configuration::PostProcessingConfiguration PostProcessingConfigurationBuilder::Build() {
+ return config_;
+}
+
} // namespace test
} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index b456908..0d7451b 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -160,6 +160,7 @@
ArtifactBuilder() = default;
ArtifactBuilder& SetName(const std::string& name);
+ ArtifactBuilder& SetVersion(int version);
ArtifactBuilder& AddAbi(configuration::Abi abi);
ArtifactBuilder& AddDensity(const ConfigDescription& density);
ArtifactBuilder& AddLocale(const ConfigDescription& locale);
@@ -167,9 +168,41 @@
configuration::OutputArtifact Build();
private:
+ DISALLOW_COPY_AND_ASSIGN(ArtifactBuilder);
+
configuration::OutputArtifact artifact_;
};
+class PostProcessingConfigurationBuilder {
+ public:
+ PostProcessingConfigurationBuilder() = default;
+
+ PostProcessingConfigurationBuilder& AddAbiGroup(const std::string& label,
+ std::vector<configuration::Abi> abis = {});
+ PostProcessingConfigurationBuilder& AddDensityGroup(const std::string& label,
+ std::vector<std::string> densities = {});
+ PostProcessingConfigurationBuilder& AddLocaleGroup(const std::string& label,
+ std::vector<std::string> locales = {});
+ PostProcessingConfigurationBuilder& AddDeviceFeatureGroup(const std::string& label);
+ PostProcessingConfigurationBuilder& AddGlTextureGroup(const std::string& label);
+ PostProcessingConfigurationBuilder& AddAndroidSdk(std::string label, int min_sdk);
+ PostProcessingConfigurationBuilder& AddArtifact(configuration::ConfiguredArtifact artrifact);
+
+ configuration::PostProcessingConfiguration Build();
+
+ private:
+ template <typename T>
+ inline PostProcessingConfigurationBuilder& AddGroup(const std::string& label,
+ configuration::Group<T>* group,
+ std::vector<T> to_add = {}) {
+ auto& values = GetOrCreateGroup(label, group);
+ values.insert(std::begin(values), std::begin(to_add), std::end(to_add));
+ return *this;
+ }
+
+ configuration::PostProcessingConfiguration config_;
+};
+
} // namespace test
} // namespace aapt
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index a158d94..ea9be29 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2846,8 +2846,7 @@
* gets added to the list of configured networks for the foreground user.
*
* For a new network, this function is used instead of a
- * sequence of addNetwork(), enableNetwork(), saveConfiguration() and
- * reconnect()
+ * sequence of addNetwork(), enableNetwork(), and reconnect()
*
* @param config the set of variables that describe the configuration,
* contained in a {@link WifiConfiguration} object.
@@ -2869,8 +2868,7 @@
/**
* Connect to a network with the given networkId.
*
- * This function is used instead of a enableNetwork(), saveConfiguration() and
- * reconnect()
+ * This function is used instead of a enableNetwork() and reconnect()
*
* @param networkId the ID of the network as returned by {@link #addNetwork} or {@link
* getConfiguredNetworks}.
@@ -2890,10 +2888,12 @@
* is updated. Any new network is enabled by default.
*
* For a new network, this function is used instead of a
- * sequence of addNetwork(), enableNetwork() and saveConfiguration().
+ * sequence of addNetwork() and enableNetwork().
*
* For an existing network, it accomplishes the task of updateNetwork()
- * and saveConfiguration()
+ *
+ * This API will cause reconnect if the crecdentials of the current active
+ * connection has been changed.
*
* @param config the set of variables that describe the configuration,
* contained in a {@link WifiConfiguration} object.
@@ -2912,7 +2912,6 @@
* foreground user.
*
* This function is used instead of a sequence of removeNetwork()
- * and saveConfiguration().
*
* @param config the set of variables that describe the configuration,
* contained in a {@link WifiConfiguration} object.
diff --git a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
index bad5ce2..c4b24cf 100644
--- a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
+++ b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
@@ -42,9 +42,9 @@
in ConfigRequest configRequest, boolean notifyOnIdentityChanged);
void disconnect(int clientId, in IBinder binder);
- void publish(int clientId, in PublishConfig publishConfig,
+ void publish(in String callingPackage, int clientId, in PublishConfig publishConfig,
in IWifiAwareDiscoverySessionCallback callback);
- void subscribe(int clientId, in SubscribeConfig subscribeConfig,
+ void subscribe(in String callingPackage, int clientId, in SubscribeConfig subscribeConfig,
in IWifiAwareDiscoverySessionCallback callback);
// session API
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 166da48..d57d152 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -301,7 +301,7 @@
if (VDBG) Log.v(TAG, "publish(): clientId=" + clientId + ", config=" + publishConfig);
try {
- mService.publish(clientId, publishConfig,
+ mService.publish(mContext.getOpPackageName(), clientId, publishConfig,
new WifiAwareDiscoverySessionCallbackProxy(this, looper, true, callback,
clientId));
} catch (RemoteException e) {
@@ -334,7 +334,7 @@
}
try {
- mService.subscribe(clientId, subscribeConfig,
+ mService.subscribe(mContext.getOpPackageName(), clientId, subscribeConfig,
new WifiAwareDiscoverySessionCallbackProxy(this, looper, false, callback,
clientId));
} catch (RemoteException e) {
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 653fcff..9cab66a 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -145,7 +145,7 @@
// (2) publish - should succeed
PublishConfig publishConfig = new PublishConfig.Builder().build();
session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig), any());
+ inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig), any());
// (3) disconnect
session.close();
@@ -197,7 +197,7 @@
// (4) subscribe: should succeed
SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig), any());
+ inOrder.verify(mockAwareService).subscribe(any(), eq(clientId), eq(subscribeConfig), any());
verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService);
}
@@ -280,7 +280,7 @@
// (1) publish
session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+ inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig),
sessionProxyCallback.capture());
// (2) publish session created
@@ -372,7 +372,7 @@
// (2) publish: successfully - then terminated
session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+ inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig),
sessionProxyCallback.capture());
sessionProxyCallback.getValue().onSessionStarted(sessionId);
sessionProxyCallback.getValue().onSessionTerminated(0);
@@ -429,7 +429,7 @@
// (1) subscribe
session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
+ inOrder.verify(mockAwareService).subscribe(any(), eq(clientId), eq(subscribeConfig),
sessionProxyCallback.capture());
// (2) subscribe session created
@@ -514,7 +514,7 @@
// (2) subscribe: successfully - then terminated
session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
+ inOrder.verify(mockAwareService).subscribe(any(), eq(clientId), eq(subscribeConfig),
sessionProxyCallback.capture());
sessionProxyCallback.getValue().onSessionStarted(sessionId);
sessionProxyCallback.getValue().onSessionTerminated(0);
@@ -912,7 +912,7 @@
// (2) publish successfully
session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+ inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig),
sessionProxyCallback.capture());
sessionProxyCallback.getValue().onSessionStarted(sessionId);
mMockLooper.dispatchAll();
@@ -1089,7 +1089,7 @@
// (2) publish successfully
session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+ inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig),
sessionProxyCallback.capture());
sessionProxyCallback.getValue().onSessionStarted(sessionId);
mMockLooper.dispatchAll();