summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp7
-rw-r--r--api/current.txt11
-rw-r--r--api/system-current.txt231
-rw-r--r--api/test-current.txt58
-rw-r--r--cmds/idmap2/idmap2/Scan.cpp30
-rwxr-xr-xcmds/idmap2/static-checks.sh10
-rw-r--r--core/java/android/app/ActivityThread.java63
-rw-r--r--core/java/android/app/AppOpsManager.aidl4
-rw-r--r--core/java/android/app/AppOpsManager.java1539
-rw-r--r--core/java/android/app/ClientTransactionHandler.java4
-rw-r--r--core/java/android/app/SystemServiceRegistry.java26
-rw-r--r--core/java/android/app/contentsuggestions/ClassificationsRequest.aidl19
-rw-r--r--core/java/android/app/contentsuggestions/ClassificationsRequest.java113
-rw-r--r--core/java/android/app/contentsuggestions/ContentClassification.aidl19
-rw-r--r--core/java/android/app/contentsuggestions/ContentClassification.java79
-rw-r--r--core/java/android/app/contentsuggestions/ContentSelection.aidl19
-rw-r--r--core/java/android/app/contentsuggestions/ContentSelection.java79
-rw-r--r--core/java/android/app/contentsuggestions/ContentSuggestionsManager.java230
-rw-r--r--core/java/android/app/contentsuggestions/IClassificationsCallback.aidl25
-rw-r--r--core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl37
-rw-r--r--core/java/android/app/contentsuggestions/ISelectionsCallback.aidl25
-rw-r--r--core/java/android/app/contentsuggestions/SelectionsRequest.aidl19
-rw-r--r--core/java/android/app/contentsuggestions/SelectionsRequest.java129
-rw-r--r--core/java/android/app/prediction/AppPredictionContext.aidl19
-rw-r--r--core/java/android/app/prediction/AppPredictionContext.java158
-rw-r--r--core/java/android/app/prediction/AppPredictionManager.java47
-rw-r--r--core/java/android/app/prediction/AppPredictionSessionId.aidl19
-rw-r--r--core/java/android/app/prediction/AppPredictionSessionId.java86
-rw-r--r--core/java/android/app/prediction/AppPredictor.java257
-rw-r--r--core/java/android/app/prediction/AppTarget.aidl19
-rw-r--r--core/java/android/app/prediction/AppTarget.java166
-rw-r--r--core/java/android/app/prediction/AppTargetEvent.aidl19
-rw-r--r--core/java/android/app/prediction/AppTargetEvent.java154
-rw-r--r--core/java/android/app/prediction/AppTargetId.aidl19
-rw-r--r--core/java/android/app/prediction/AppTargetId.java90
-rw-r--r--core/java/android/app/prediction/IPredictionCallback.aidl27
-rw-r--r--core/java/android/app/prediction/IPredictionManager.aidl51
-rw-r--r--core/java/android/app/role/IRoleManager.aidl2
-rw-r--r--core/java/android/app/role/RoleManager.java18
-rw-r--r--core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java5
-rw-r--r--core/java/android/content/Context.java27
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java10
-rw-r--r--core/java/android/content/pm/PackageUserState.java23
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java8
-rw-r--r--core/java/android/hardware/display/ColorDisplayManager.java31
-rw-r--r--core/java/android/hardware/display/DisplayManager.java7
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java11
-rw-r--r--core/java/android/hardware/display/IColorDisplayManager.aidl2
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl3
-rw-r--r--core/java/android/os/Parcel.java115
-rw-r--r--core/java/android/os/PowerManager.java2
-rw-r--r--core/java/android/os/PowerSaveState.java2
-rw-r--r--core/java/android/provider/Settings.java62
-rw-r--r--core/java/android/service/appprediction/AppPredictionService.java322
-rw-r--r--core/java/android/service/appprediction/IPredictionService.aidl53
-rw-r--r--core/java/android/service/contentsuggestions/ContentSuggestionsService.java154
-rw-r--r--core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl43
-rw-r--r--core/java/android/util/DebugUtils.java3
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl15
-rw-r--r--core/java/com/android/internal/os/AtomicDirectory.java294
-rw-r--r--core/java/com/android/internal/util/CollectionUtils.java14
-rw-r--r--core/jni/Android.bp3
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/com_android_internal_os_AtomicDirectory.cpp66
-rw-r--r--core/proto/android/providers/settings/global.proto4
-rw-r--r--core/res/AndroidManifest.xml15
-rw-r--r--core/res/res/values/config.xml17
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java230
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java8
-rw-r--r--libs/input/PointerController.cpp2
-rw-r--r--libs/protoutil/include/android/util/EncodedBuffer.h6
-rw-r--r--packages/AppPredictionLib/Android.bp24
-rw-r--r--packages/AppPredictionLib/AndroidManifest.xml20
-rw-r--r--packages/AppPredictionLib/src/com/android/app/prediction/Constants.java71
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java4
-rw-r--r--packages/Shell/AndroidManifest.xml4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/BatteryMeterView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/DependencyBinder.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java115
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java177
-rw-r--r--services/Android.bp2
-rw-r--r--services/appprediction/Android.bp5
-rw-r--r--services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java156
-rw-r--r--services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java83
-rw-r--r--services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java218
-rw-r--r--services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java142
-rw-r--r--services/contentsuggestions/Android.bp5
-rw-r--r--services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java205
-rw-r--r--services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java84
-rw-r--r--services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java162
-rw-r--r--services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java97
-rw-r--r--services/core/java/com/android/server/IntentResolver.java30
-rw-r--r--services/core/java/com/android/server/PackageWatchdog.java106
-rw-r--r--services/core/java/com/android/server/TEST_MAPPING15
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java4
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java19
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java (renamed from services/core/java/com/android/server/AppOpsService.java)253
-rw-r--r--services/core/java/com/android/server/appop/HistoricalRegistry.java1495
-rw-r--r--services/core/java/com/android/server/appop/TEST_MAPPING7
-rw-r--r--services/core/java/com/android/server/display/ColorDisplayService.java97
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java34
-rw-r--r--services/core/java/com/android/server/hdmi/Constants.java67
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java20
-rwxr-xr-xservices/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java26
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java160
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java6
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java139
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java8
-rw-r--r--services/core/java/com/android/server/hdmi/OneTouchPlayAction.java6
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java38
-rw-r--r--services/core/java/com/android/server/pm/ComponentResolver.java46
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java3
-rw-r--r--services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java97
-rw-r--r--services/core/java/com/android/server/power/OWNERS4
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java1
-rw-r--r--services/core/java/com/android/server/power/batterysaver/BatterySaverController.java20
-rw-r--r--services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java (renamed from services/core/java/com/android/server/power/BatterySaverPolicy.java)14
-rw-r--r--services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java5
-rw-r--r--services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java1
-rw-r--r--services/core/java/com/android/server/role/RoleManagerService.java48
-rw-r--r--services/core/java/com/android/server/role/RoleUserState.java30
-rw-r--r--services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java44
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowTraceBuffer.java181
-rw-r--r--services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java88
-rw-r--r--services/core/java/com/android/server/wm/WindowTracing.java102
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp23
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java4
-rw-r--r--services/java/com/android/server/SystemServer.java18
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java (renamed from services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java)2
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java (renamed from services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java)0
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java (renamed from services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java)3
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java (renamed from services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java)2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java64
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java (renamed from services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java)21
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java41
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java115
-rw-r--r--telephony/java/android/telephony/ServiceState.java3
-rw-r--r--telephony/java/com/android/internal/telephony/SmsApplication.java117
-rw-r--r--tests/PackageWatchdog/Android.mk34
-rw-r--r--tests/PackageWatchdog/AndroidManifest.xml28
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java286
-rw-r--r--tools/bit/command.h2
-rw-r--r--tools/stats_log_api_gen/main.cpp65
-rw-r--r--tools/streaming_proto/Errors.h2
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java6
163 files changed, 10180 insertions, 1043 deletions
diff --git a/Android.bp b/Android.bp
index c5f415d500be..ea6a6319718f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -104,6 +104,11 @@ java_defaults {
"core/java/android/app/backup/IRestoreObserver.aidl",
"core/java/android/app/backup/IRestoreSession.aidl",
"core/java/android/app/backup/ISelectBackupTransportCallback.aidl",
+ "core/java/android/app/contentsuggestions/IClassificationsCallback.aidl",
+ "core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl",
+ "core/java/android/app/contentsuggestions/ISelectionsCallback.aidl",
+ "core/java/android/app/prediction/IPredictionCallback.aidl",
+ "core/java/android/app/prediction/IPredictionManager.aidl",
"core/java/android/app/role/IOnRoleHoldersChangedListener.aidl",
"core/java/android/app/role/IRoleManager.aidl",
"core/java/android/app/role/IRoleManagerCallback.aidl",
@@ -273,6 +278,7 @@ java_defaults {
"core/java/android/rolecontrollerservice/IRoleControllerService.aidl",
":keystore_aidl",
"core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl",
+ "core/java/android/service/appprediction/IPredictionService.aidl",
"core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl",
"core/java/android/service/autofill/augmented/IFillCallback.aidl",
"core/java/android/service/autofill/IAutoFillService.aidl",
@@ -282,6 +288,7 @@ java_defaults {
"core/java/android/service/carrier/ICarrierService.aidl",
"core/java/android/service/carrier/ICarrierMessagingCallback.aidl",
"core/java/android/service/carrier/ICarrierMessagingService.aidl",
+ "core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl",
"core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl",
"core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl",
"core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl",
diff --git a/api/current.txt b/api/current.txt
index 8dad532b1e71..0ff1743c2a39 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -29748,9 +29748,6 @@ package android.net.wifi {
method public void setReferenceCounted(boolean);
}
- public static abstract class WifiManager.NetworkSuggestionsStatusCode implements java.lang.annotation.Annotation {
- }
-
public class WifiManager.WifiLock {
method public void acquire();
method public boolean isHeld();
@@ -34440,6 +34437,8 @@ package android.os {
method public java.util.ArrayList<java.lang.String> createStringArrayList();
method public <T> T[] createTypedArray(android.os.Parcelable.Creator<T>);
method public <T> java.util.ArrayList<T> createTypedArrayList(android.os.Parcelable.Creator<T>);
+ method public <T extends android.os.Parcelable> android.util.ArrayMap<java.lang.String, T> createTypedArrayMap(android.os.Parcelable.Creator<T>);
+ method public <T extends android.os.Parcelable> android.util.SparseArray<T> createTypedSparseArray(android.os.Parcelable.Creator<T>);
method public int dataAvail();
method public int dataCapacity();
method public int dataPosition();
@@ -34481,7 +34480,7 @@ package android.os {
method public java.io.Serializable readSerializable();
method public android.util.Size readSize();
method public android.util.SizeF readSizeF();
- method public android.util.SparseArray readSparseArray(java.lang.ClassLoader);
+ method public <T> android.util.SparseArray<T> readSparseArray(java.lang.ClassLoader);
method public android.util.SparseBooleanArray readSparseBooleanArray();
method public java.lang.String readString();
method public void readStringArray(java.lang.String[]);
@@ -34527,7 +34526,7 @@ package android.os {
method public void writeSerializable(java.io.Serializable);
method public void writeSize(android.util.Size);
method public void writeSizeF(android.util.SizeF);
- method public void writeSparseArray(android.util.SparseArray<java.lang.Object>);
+ method public <T> void writeSparseArray(android.util.SparseArray<T>);
method public void writeSparseBooleanArray(android.util.SparseBooleanArray);
method public void writeString(java.lang.String);
method public void writeStringArray(java.lang.String[]);
@@ -34535,8 +34534,10 @@ package android.os {
method public void writeStrongBinder(android.os.IBinder);
method public void writeStrongInterface(android.os.IInterface);
method public <T extends android.os.Parcelable> void writeTypedArray(T[], int);
+ method public <T extends android.os.Parcelable> void writeTypedArrayMap(android.util.ArrayMap<java.lang.String, T>, int);
method public <T extends android.os.Parcelable> void writeTypedList(java.util.List<T>);
method public <T extends android.os.Parcelable> void writeTypedObject(T, int);
+ method public <T extends android.os.Parcelable> void writeTypedSparseArray(android.util.SparseArray<T>, int);
method public void writeValue(java.lang.Object);
field public static final android.os.Parcelable.Creator<java.lang.String> STRING_CREATOR;
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 320a794cffaf..b190e9f5971f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -86,11 +86,13 @@ package android {
field public static final java.lang.String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final java.lang.String MANAGE_APP_OPS_RESTRICTIONS = "android.permission.MANAGE_APP_OPS_RESTRICTIONS";
+ field public static final java.lang.String MANAGE_APP_PREDICTIONS = "android.permission.MANAGE_APP_PREDICTIONS";
field public static final java.lang.String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
field public static final java.lang.String MANAGE_AUTO_FILL = "android.permission.MANAGE_AUTO_FILL";
field public static final java.lang.String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE";
field public static final java.lang.String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
field public static final java.lang.String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
+ field public static final java.lang.String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS";
field public static final java.lang.String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
@@ -284,10 +286,11 @@ package android.app {
}
public class AppOpsManager {
- method public java.util.List<android.app.AppOpsManager.HistoricalPackageOps> getAllHistoricPackagesOps(java.lang.String[], long, long);
- method public android.app.AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int, java.lang.String, java.lang.String[], long, long);
+ method public void getHistoricalOps(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static java.lang.String[] getOpStrs();
- method public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, int[]);
+ method public deprecated java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, int[]);
+ method public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, java.lang.String...);
+ method public java.util.List<android.app.AppOpsManager.PackageOps> getPackagesForOps(java.lang.String[]);
method public static int opToDefaultMode(java.lang.String);
method public static java.lang.String opToPermission(java.lang.String);
method public void setMode(java.lang.String, int, java.lang.String, int);
@@ -343,7 +346,7 @@ package android.app {
field public static final int UID_STATE_TOP = 1; // 0x1
}
- public static final class AppOpsManager.HistoricalOpEntry implements android.os.Parcelable {
+ public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
method public int describeContents();
method public long getAccessCount(int);
method public long getAccessDuration(int);
@@ -353,23 +356,43 @@ package android.app {
method public long getForegroundAccessCount();
method public long getForegroundAccessDuration();
method public long getForegroundRejectCount();
- method public java.lang.String getOp();
+ method public java.lang.String getOpName();
method public long getRejectCount(int);
method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOpEntry> CREATOR;
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOp> CREATOR;
+ }
+
+ public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getBeginTimeMillis();
+ method public long getEndTimeMillis();
+ method public int getUidCount();
+ method public android.app.AppOpsManager.HistoricalUidOps getUidOps(int);
+ method public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR;
}
public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
method public int describeContents();
- method public android.app.AppOpsManager.HistoricalOpEntry getEntry(java.lang.String);
- method public android.app.AppOpsManager.HistoricalOpEntry getEntryAt(int);
- method public int getEntryCount();
+ method public android.app.AppOpsManager.HistoricalOp getOp(java.lang.String);
+ method public android.app.AppOpsManager.HistoricalOp getOpAt(int);
+ method public int getOpCount();
method public java.lang.String getPackageName();
- method public int getUid();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalPackageOps> CREATOR;
}
+ public static final class AppOpsManager.HistoricalUidOps implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getPackageCount();
+ method public android.app.AppOpsManager.HistoricalPackageOps getPackageOps(java.lang.String);
+ method public android.app.AppOpsManager.HistoricalPackageOps getPackageOpsAt(int);
+ method public int getUid();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalUidOps> CREATOR;
+ }
+
public static final class AppOpsManager.OpEntry implements android.os.Parcelable {
method public int describeContents();
method public int getDuration();
@@ -848,6 +871,73 @@ package android.app.backup {
}
+package android.app.contentsuggestions {
+
+ public final class ClassificationsRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ClassificationsRequest> CREATOR;
+ }
+
+ public static final class ClassificationsRequest.Builder {
+ ctor public ClassificationsRequest.Builder(java.util.List<android.app.contentsuggestions.ContentSelection>);
+ method public android.app.contentsuggestions.ClassificationsRequest build();
+ method public android.app.contentsuggestions.ClassificationsRequest.Builder setExtras(android.os.Bundle);
+ }
+
+ public final class ContentClassification implements android.os.Parcelable {
+ ctor public ContentClassification(java.lang.String, android.os.Bundle);
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public java.lang.String getId();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentClassification> CREATOR;
+ }
+
+ public final class ContentSelection implements android.os.Parcelable {
+ ctor public ContentSelection(java.lang.String, android.os.Bundle);
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public java.lang.String getId();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentSelection> CREATOR;
+ }
+
+ public final class ContentSuggestionsManager {
+ method public void classifyContentSelections(android.app.contentsuggestions.ClassificationsRequest, java.util.concurrent.Executor, android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
+ method public void notifyInteraction(java.lang.String, android.os.Bundle);
+ method public void provideContextImage(int, android.os.Bundle);
+ method public void suggestContentSelections(android.app.contentsuggestions.SelectionsRequest, java.util.concurrent.Executor, android.app.contentsuggestions.ContentSuggestionsManager.SelectionsCallback);
+ }
+
+ public static abstract interface ContentSuggestionsManager.ClassificationsCallback {
+ method public abstract void onContentClassificationsAvailable(int, java.util.List<android.app.contentsuggestions.ContentClassification>);
+ }
+
+ public static abstract interface ContentSuggestionsManager.SelectionsCallback {
+ method public abstract void onContentSelectionsAvailable(int, java.util.List<android.app.contentsuggestions.ContentSelection>);
+ }
+
+ public final class SelectionsRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public android.graphics.Point getInterestPoint();
+ method public int getTaskId();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.SelectionsRequest> CREATOR;
+ }
+
+ public static final class SelectionsRequest.Builder {
+ ctor public SelectionsRequest.Builder(int);
+ method public android.app.contentsuggestions.SelectionsRequest build();
+ method public android.app.contentsuggestions.SelectionsRequest.Builder setExtras(android.os.Bundle);
+ method public android.app.contentsuggestions.SelectionsRequest.Builder setInterestPoint(android.graphics.Point);
+ }
+
+}
+
package android.app.job {
public abstract class JobScheduler {
@@ -856,6 +946,87 @@ package android.app.job {
}
+package android.app.prediction {
+
+ public final class AppPredictionContext implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public java.lang.String getPackageName();
+ method public int getPredictedTargetCount();
+ method public java.lang.String getUiSurface();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.prediction.AppPredictionContext> CREATOR;
+ }
+
+ public static final class AppPredictionContext.Builder {
+ method public android.app.prediction.AppPredictionContext build();
+ method public android.app.prediction.AppPredictionContext.Builder setExtras(android.os.Bundle);
+ method public android.app.prediction.AppPredictionContext.Builder setPredictedTargetCount(int);
+ method public android.app.prediction.AppPredictionContext.Builder setUiSurface(java.lang.String);
+ }
+
+ public final class AppPredictionManager {
+ method public android.app.prediction.AppPredictor createAppPredictionSession(android.app.prediction.AppPredictionContext);
+ }
+
+ public final class AppPredictionSessionId implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.prediction.AppPredictionSessionId> CREATOR;
+ }
+
+ public final class AppPredictor {
+ method public void destroy();
+ method public void notifyAppTargetEvent(android.app.prediction.AppTargetEvent);
+ method public void notifyLocationShown(java.lang.String, java.util.List<android.app.prediction.AppTargetId>);
+ method public void registerPredictionUpdates(java.util.concurrent.Executor, android.app.prediction.AppPredictor.Callback);
+ method public void requestPredictionUpdate();
+ method public void sortTargets(java.util.List<android.app.prediction.AppTarget>, java.util.concurrent.Executor, java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>);
+ method public void unregisterPredictionUpdates(android.app.prediction.AppPredictor.Callback);
+ }
+
+ public static abstract interface AppPredictor.Callback {
+ method public abstract void onTargetsAvailable(java.util.List<android.app.prediction.AppTarget>);
+ }
+
+ public final class AppTarget implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.lang.String getClassName();
+ method public android.app.prediction.AppTargetId getId();
+ method public java.lang.String getPackageName();
+ method public int getRank();
+ method public android.content.pm.ShortcutInfo getShortcutInfo();
+ method public android.os.UserHandle getUser();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.prediction.AppTarget> CREATOR;
+ }
+
+ public final class AppTargetEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAction();
+ method public java.lang.String getLaunchLocation();
+ method public android.app.prediction.AppTarget getTarget();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int ACTION_DISMISS = 2; // 0x2
+ field public static final int ACTION_LAUNCH = 1; // 0x1
+ field public static final int ACTION_PIN = 3; // 0x3
+ field public static final android.os.Parcelable.Creator<android.app.prediction.AppTargetEvent> CREATOR;
+ }
+
+ public static final class AppTargetEvent.Builder {
+ ctor public AppTargetEvent.Builder(android.app.prediction.AppTarget, int);
+ method public android.app.prediction.AppTargetEvent build();
+ method public android.app.prediction.AppTargetEvent.Builder setLaunchLocation(java.lang.String);
+ }
+
+ public final class AppTargetId implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.prediction.AppTargetId> CREATOR;
+ }
+
+}
+
package android.app.role {
public abstract interface OnRoleHoldersChangedListener {
@@ -1042,7 +1213,9 @@ package android.content {
method public abstract void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void startActivityAsUser(android.content.Intent, android.os.UserHandle);
+ field public static final java.lang.String APP_PREDICTION_SERVICE = "app_prediction";
field public static final java.lang.String BACKUP_SERVICE = "backup";
+ field public static final java.lang.String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
field public static final java.lang.String CONTEXTHUB_SERVICE = "contexthub";
field public static final java.lang.String EUICC_CARD_SERVICE = "euicc_card";
field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control";
@@ -1128,6 +1301,7 @@ package android.content {
package android.content.pm {
public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+ method public boolean isEncryptionAware();
method public boolean isInstantApp();
field public java.lang.String credentialProtectedDataDir;
field public int targetSandboxVersion;
@@ -1548,6 +1722,10 @@ package android.hardware.display {
field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessCorrection> CREATOR;
}
+ public final class ColorDisplayManager {
+ method public boolean setSaturationLevel(int);
+ }
+
public final class DisplayManager {
method public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats();
method public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration();
@@ -1556,7 +1734,7 @@ package android.hardware.display {
method public android.util.Pair<float[], float[]> getMinimumBrightnessCurve();
method public android.graphics.Point getStableDisplaySize();
method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
- method public void setSaturationLevel(float);
+ method public deprecated void setSaturationLevel(float);
}
}
@@ -5162,6 +5340,24 @@ package android.security.keystore.recovery {
}
+package android.service.appprediction {
+
+ public abstract class AppPredictionService extends android.app.Service {
+ ctor public AppPredictionService();
+ method public abstract void onAppTargetEvent(android.app.prediction.AppPredictionSessionId, android.app.prediction.AppTargetEvent);
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public void onCreatePredictionSession(android.app.prediction.AppPredictionContext, android.app.prediction.AppPredictionSessionId);
+ method public void onDestroyPredictionSession(android.app.prediction.AppPredictionSessionId);
+ method public abstract void onLocationShown(android.app.prediction.AppPredictionSessionId, java.lang.String, java.util.List<android.app.prediction.AppTargetId>);
+ method public abstract void onRequestPredictionUpdate(android.app.prediction.AppPredictionSessionId);
+ method public abstract void onSortAppTargets(android.app.prediction.AppPredictionSessionId, java.util.List<android.app.prediction.AppTarget>, android.os.CancellationSignal, java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>);
+ method public void onStartPredictionUpdates();
+ method public void onStopPredictionUpdates();
+ method public final void updatePredictions(android.app.prediction.AppPredictionSessionId, java.util.List<android.app.prediction.AppTarget>);
+ }
+
+}
+
package android.service.autofill {
public abstract class AutofillFieldClassificationService extends android.app.Service {
@@ -5290,6 +5486,19 @@ package android.service.contentcapture {
}
+package android.service.contentsuggestions {
+
+ public abstract class ContentSuggestionsService extends android.app.Service {
+ ctor public ContentSuggestionsService();
+ method public abstract void classifyContentSelections(android.app.contentsuggestions.ClassificationsRequest, android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
+ method public abstract void notifyInteraction(java.lang.String, android.os.Bundle);
+ method public abstract void processContextImage(int, android.graphics.Bitmap, android.os.Bundle);
+ method public abstract void suggestContentSelections(android.app.contentsuggestions.SelectionsRequest, android.app.contentsuggestions.ContentSuggestionsManager.SelectionsCallback);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.contentsuggestions.ContentSuggestionsService";
+ }
+
+}
+
package android.service.euicc {
public final class DownloadSubscriptionResult implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index ae3c1e027f51..575875dddad7 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -89,18 +89,26 @@ package android.app {
}
public class AppOpsManager {
- method public java.util.List<android.app.AppOpsManager.HistoricalPackageOps> getAllHistoricPackagesOps(java.lang.String[], long, long);
- method public android.app.AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int, java.lang.String, java.lang.String[], long, long);
+ method public void addHistoricalOps(android.app.AppOpsManager.HistoricalOps);
+ method public void clearHistory();
+ method public void getHistoricalOps(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
+ method public void getHistoricalOpsFromDiskRaw(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static int getNumOps();
method public static java.lang.String[] getOpStrs();
method public boolean isOperationActive(int, int, java.lang.String);
+ method public void offsetHistory(long);
method public static java.lang.String opToPermission(int);
method public static int permissionToOpCode(java.lang.String);
+ method public void resetHistoryParameters();
+ method public void setHistoryParameters(int, long, int);
method public void setMode(int, int, java.lang.String, int);
method public void setUidMode(java.lang.String, int, int);
method public void startWatchingActive(int[], android.app.AppOpsManager.OnOpActiveChangedListener);
method public void stopWatchingActive(android.app.AppOpsManager.OnOpActiveChangedListener);
method public static int strOpToOp(java.lang.String);
+ field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0
+ field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1
+ field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2
field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
@@ -144,11 +152,18 @@ package android.app {
field public static final java.lang.String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms";
field public static final java.lang.String OPSTR_WRITE_SMS = "android:write_sms";
field public static final java.lang.String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper";
+ field public static final int OP_COARSE_LOCATION = 0; // 0x0
field public static final int OP_RECORD_AUDIO = 27; // 0x1b
field public static final int OP_SYSTEM_ALERT_WINDOW = 24; // 0x18
+ field public static final int UID_STATE_BACKGROUND = 4; // 0x4
+ field public static final int UID_STATE_CACHED = 5; // 0x5
+ field public static final int UID_STATE_FOREGROUND = 3; // 0x3
+ field public static final int UID_STATE_FOREGROUND_SERVICE = 2; // 0x2
+ field public static final int UID_STATE_PERSISTENT = 0; // 0x0
+ field public static final int UID_STATE_TOP = 1; // 0x1
}
- public static final class AppOpsManager.HistoricalOpEntry implements android.os.Parcelable {
+ public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
method public int describeContents();
method public long getAccessCount(int);
method public long getAccessDuration(int);
@@ -158,23 +173,48 @@ package android.app {
method public long getForegroundAccessCount();
method public long getForegroundAccessDuration();
method public long getForegroundRejectCount();
- method public java.lang.String getOp();
+ method public java.lang.String getOpName();
method public long getRejectCount(int);
method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOpEntry> CREATOR;
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOp> CREATOR;
+ }
+
+ public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable {
+ ctor public AppOpsManager.HistoricalOps(long, long);
+ method public int describeContents();
+ method public long getBeginTimeMillis();
+ method public long getEndTimeMillis();
+ method public int getUidCount();
+ method public android.app.AppOpsManager.HistoricalUidOps getUidOps(int);
+ method public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(int);
+ method public void increaseAccessCount(int, int, java.lang.String, int, long);
+ method public void increaseAccessDuration(int, int, java.lang.String, int, long);
+ method public void increaseRejectCount(int, int, java.lang.String, int, long);
+ method public void offsetBeginAndEndTime(long);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR;
}
public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
method public int describeContents();
- method public android.app.AppOpsManager.HistoricalOpEntry getEntry(java.lang.String);
- method public android.app.AppOpsManager.HistoricalOpEntry getEntryAt(int);
- method public int getEntryCount();
+ method public android.app.AppOpsManager.HistoricalOp getOp(java.lang.String);
+ method public android.app.AppOpsManager.HistoricalOp getOpAt(int);
+ method public int getOpCount();
method public java.lang.String getPackageName();
- method public int getUid();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalPackageOps> CREATOR;
}
+ public static final class AppOpsManager.HistoricalUidOps implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getPackageCount();
+ method public android.app.AppOpsManager.HistoricalPackageOps getPackageOps(java.lang.String);
+ method public android.app.AppOpsManager.HistoricalPackageOps getPackageOpsAt(int);
+ method public int getUid();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalUidOps> CREATOR;
+ }
+
public static abstract interface AppOpsManager.OnOpActiveChangedListener {
method public abstract void onOpActiveChanged(int, int, java.lang.String, boolean);
}
diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp
index 49187475ccdb..4f88127f8af1 100644
--- a/cmds/idmap2/idmap2/Scan.cpp
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -39,6 +39,17 @@ using android::idmap2::ZipFile;
using android::idmap2::utils::FindFiles;
namespace {
+
+struct InputOverlay {
+ bool operator<(InputOverlay const& rhs) const {
+ return priority < rhs.priority || (priority == rhs.priority && apk_path < rhs.apk_path);
+ }
+
+ std::string apk_path; // NOLINT(misc-non-private-member-variables-in-classes)
+ std::string idmap_path; // NOLINT(misc-non-private-member-variables-in-classes)
+ int priority; // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
std::unique_ptr<std::vector<std::string>> FindApkFiles(const std::vector<std::string>& dirs,
bool recursive, std::ostream& out_error) {
const auto predicate = [](unsigned char type, const std::string& path) -> bool {
@@ -58,6 +69,7 @@ std::unique_ptr<std::vector<std::string>> FindApkFiles(const std::vector<std::st
}
return std::make_unique<std::vector<std::string>>(paths.cbegin(), paths.cend());
}
+
} // namespace
bool Scan(const std::vector<std::string>& args, std::ostream& out_error) {
@@ -87,7 +99,7 @@ bool Scan(const std::vector<std::string>& args, std::ostream& out_error) {
return false;
}
- std::vector<std::string> interesting_apks;
+ std::vector<InputOverlay> interesting_apks;
for (const std::string& path : *apk_paths) {
std::unique_ptr<const ZipFile> zip = ZipFile::Open(path);
if (!zip) {
@@ -132,27 +144,29 @@ bool Scan(const std::vector<std::string>& args, std::ostream& out_error) {
continue;
}
+ // Sort the static overlays in ascending priority order
+ std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, path);
+ InputOverlay input{path, idmap_path, priority};
interesting_apks.insert(
- std::lower_bound(interesting_apks.begin(), interesting_apks.end(), path), path);
+ std::lower_bound(interesting_apks.begin(), interesting_apks.end(), input), input);
}
std::stringstream stream;
- for (const auto& apk : interesting_apks) {
- const std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, apk);
+ for (const auto& overlay : interesting_apks) {
std::stringstream dev_null;
- if (!Verify(std::vector<std::string>({"--idmap-path", idmap_path}), dev_null) &&
+ if (!Verify(std::vector<std::string>({"--idmap-path", overlay.idmap_path}), dev_null) &&
!Create(std::vector<std::string>({
"--target-apk-path",
target_apk_path,
"--overlay-apk-path",
- apk,
+ overlay.apk_path,
"--idmap-path",
- idmap_path,
+ overlay.idmap_path,
}),
out_error)) {
return false;
}
- stream << idmap_path << std::endl;
+ stream << overlay.idmap_path << std::endl;
}
std::cout << stream.str();
diff --git a/cmds/idmap2/static-checks.sh b/cmds/idmap2/static-checks.sh
index 560ccb692bb1..ad9830b8a096 100755
--- a/cmds/idmap2/static-checks.sh
+++ b/cmds/idmap2/static-checks.sh
@@ -70,7 +70,15 @@ function _bpfmt()
function _cpplint()
{
local cpplint="${ANDROID_BUILD_TOP}/tools/repohooks/tools/cpplint.py"
- $cpplint --quiet $cpp_files
+ local output="$($cpplint --quiet $cpp_files 2>&1 >/dev/null | grep -v \
+ -e 'Found C system header after C++ system header.' \
+ -e 'Unknown NOLINT error category: misc-non-private-member-variables-in-classes' \
+ )"
+ if [[ "$output" ]]; then
+ echo "$output"
+ return 1
+ fi
+ return 0
}
function _parse_args()
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8faf08a77355..7767f0491a16 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -271,6 +271,13 @@ public final class ActivityThread extends ClientTransactionHandler {
@UnsupportedAppUsage
final H mH = new H();
final Executor mExecutor = new HandlerExecutor(mH);
+ /**
+ * Maps from activity token to local record of running activities in this process.
+ *
+ * This variable is readable if the code is running in activity thread or holding {@link
+ * #mResourcesManager}. It's only writable if the code is running in activity thread and holding
+ * {@link #mResourcesManager}.
+ */
@UnsupportedAppUsage
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
/** The activities to be truly destroyed (not include relaunch). */
@@ -434,6 +441,10 @@ public final class ActivityThread extends ClientTransactionHandler {
Configuration newConfig;
Configuration createdConfig;
Configuration overrideConfig;
+ // Used to save the last reported configuration from server side so that activity
+ // configuration transactions can always use the latest configuration.
+ @GuardedBy("this")
+ private Configuration mPendingOverrideConfig;
// Used for consolidating configs before sending on to Activity.
private Configuration tmpConfig = new Configuration();
// Callback used for updating activity override config.
@@ -3064,7 +3075,12 @@ public final class ActivityThread extends ClientTransactionHandler {
}
r.setState(ON_CREATE);
- mActivities.put(r.token, r);
+ // updatePendingActivityConfiguration() reads from mActivities to update
+ // ActivityClientRecord which runs in a different thread. Protect modifications to
+ // mActivities to avoid race.
+ synchronized (mResourcesManager) {
+ mActivities.put(r.token, r);
+ }
} catch (SuperNotCalledException e) {
throw e;
@@ -4639,7 +4655,12 @@ public final class ActivityThread extends ClientTransactionHandler {
r.setState(ON_DESTROY);
}
schedulePurgeIdler();
- mActivities.remove(token);
+ // updatePendingActivityConfiguration() reads from mActivities to update
+ // ActivityClientRecord which runs in a different thread. Protect modifications to
+ // mActivities to avoid race.
+ synchronized (mResourcesManager) {
+ mActivities.remove(token);
+ }
StrictMode.decrementExpectedActivityCount(activityClass);
return r;
}
@@ -5382,6 +5403,26 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
+ @Override
+ public void updatePendingActivityConfiguration(IBinder activityToken,
+ Configuration overrideConfig) {
+ final ActivityClientRecord r;
+ synchronized (mResourcesManager) {
+ r = mActivities.get(activityToken);
+ }
+
+ if (r == null) {
+ if (DEBUG_CONFIGURATION) {
+ Slog.w(TAG, "Not found target activity to update its pending config.");
+ }
+ return;
+ }
+
+ synchronized (r) {
+ r.mPendingOverrideConfig = overrideConfig;
+ }
+ }
+
/**
* Handle new activity configuration and/or move to a different display.
* @param activityToken Target activity token.
@@ -5401,6 +5442,24 @@ public final class ActivityThread extends ClientTransactionHandler {
final boolean movedToDifferentDisplay = displayId != INVALID_DISPLAY
&& displayId != r.activity.getDisplayId();
+ synchronized (r) {
+ if (r.mPendingOverrideConfig != null
+ && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
+ overrideConfig = r.mPendingOverrideConfig;
+ }
+ r.mPendingOverrideConfig = null;
+ }
+
+ if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)
+ && !movedToDifferentDisplay) {
+ if (DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Activity already handled newer configuration so drop this"
+ + " transaction. overrideConfig=" + overrideConfig + " r.overrideConfig="
+ + r.overrideConfig);
+ }
+ return;
+ }
+
// Perform updates.
r.overrideConfig = overrideConfig;
final ViewRootImpl viewRoot = r.activity.mDecor != null
diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl
index 9329fbc83376..224030202e08 100644
--- a/core/java/android/app/AppOpsManager.aidl
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -19,5 +19,7 @@ package android.app;
parcelable AppOpsManager.PackageOps;
parcelable AppOpsManager.OpEntry;
+parcelable AppOpsManager.HistoricalOp;
+parcelable AppOpsManager.HistoricalOps;
parcelable AppOpsManager.HistoricalPackageOps;
-parcelable AppOpsManager.HistoricalOpEntry;
+parcelable AppOpsManager.HistoricalUidOps;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 78fe0024b0b0..e155fe201f98 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -36,26 +36,33 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserManager;
import android.provider.Settings;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
+import android.util.SparseArray;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* API for interacting with "application operation" tracking.
@@ -106,6 +113,51 @@ public class AppOpsManager {
static IBinder sToken;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "HISTORICAL_MODE_" }, value = {
+ HISTORICAL_MODE_DISABLED,
+ HISTORICAL_MODE_ENABLED_ACTIVE,
+ HISTORICAL_MODE_ENABLED_PASSIVE
+ })
+ public @interface HistoricalMode {}
+
+ /**
+ * Mode in which app op history is completely disabled.
+ * @hide
+ */
+ @TestApi
+ public static final int HISTORICAL_MODE_DISABLED = 0;
+
+ /**
+ * Mode in which app op history is enabled and app ops performed by apps would
+ * be tracked. This is the mode in which the feature is completely enabled.
+ * @hide
+ */
+ @TestApi
+ public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1;
+
+ /**
+ * Mode in which app op history is enabled but app ops performed by apps would
+ * not be tracked and the only way to add ops to the history is via explicit calls
+ * to dedicated APIs. This mode is useful for testing to allow full control of
+ * the historical content.
+ * @hide
+ */
+ @TestApi
+ public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "MODE_" }, value = {
+ MODE_ALLOWED,
+ MODE_IGNORED,
+ MODE_ERRORED,
+ MODE_DEFAULT,
+ MODE_FOREGROUND
+ })
+ public @interface Mode {}
+
/**
* Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is
* allowed to perform the given operation.
@@ -159,7 +211,6 @@ public class AppOpsManager {
"foreground", // MODE_FOREGROUND
};
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "UID_STATE_" }, value = {
@@ -173,9 +224,16 @@ public class AppOpsManager {
public @interface UidState {}
/**
+ * Invalid UID state.
+ * @hide
+ */
+ public static final int UID_STATE_INVALID = -1;
+
+ /**
* Metrics about an op when its uid is persistent.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_PERSISTENT = 0;
@@ -183,6 +241,7 @@ public class AppOpsManager {
* Metrics about an op when its uid is at the top.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_TOP = 1;
@@ -190,6 +249,7 @@ public class AppOpsManager {
* Metrics about an op when its uid is running a foreground service.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_FOREGROUND_SERVICE = 2;
@@ -203,6 +263,7 @@ public class AppOpsManager {
* Metrics about an op when its uid is in the foreground for any other reasons.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_FOREGROUND = 3;
@@ -210,6 +271,7 @@ public class AppOpsManager {
* Metrics about an op when its uid is in the background for any reason.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_BACKGROUND = 4;
@@ -217,6 +279,7 @@ public class AppOpsManager {
* Metrics about an op when its uid is cached.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_CACHED = 5;
@@ -237,7 +300,7 @@ public class AppOpsManager {
@UnsupportedAppUsage
public static final int OP_NONE = -1;
/** @hide Access to coarse location information. */
- @UnsupportedAppUsage
+ @TestApi
public static final int OP_COARSE_LOCATION = 0;
/** @hide Access to fine location information. */
@UnsupportedAppUsage
@@ -1645,6 +1708,9 @@ public class AppOpsManager {
}
}
+ /** @hide */
+ public static final String KEY_HISTORICAL_OPS = "historical_ops";
+
/**
* Retrieve the op switch that controls the given operation.
* @hide
@@ -1731,7 +1797,7 @@ public class AppOpsManager {
* Retrieve the default mode for the operation.
* @hide
*/
- public static int opToDefaultMode(int op) {
+ public static @Mode int opToDefaultMode(int op) {
// STOPSHIP b/118520006: Hardcode the default values once the feature is stable.
switch (op) {
// SMS permissions
@@ -1795,7 +1861,7 @@ public class AppOpsManager {
* Retrieve the human readable mode.
* @hide
*/
- public static String modeToName(int mode) {
+ public static String modeToName(@Mode int mode) {
if (mode >= 0 && mode < MODE_NAMES.length) {
return MODE_NAMES[mode];
}
@@ -1885,7 +1951,7 @@ public class AppOpsManager {
@SystemApi
public static final class OpEntry implements Parcelable {
private final int mOp;
- private final int mMode;
+ private final @Mode int mMode;
private final long[] mTimes;
private final long[] mRejectTimes;
private final int mDuration;
@@ -1896,7 +1962,7 @@ public class AppOpsManager {
/**
* @hide
*/
- public OpEntry(int op, int mode, long time, long rejectTime, int duration,
+ public OpEntry(int op, @Mode int mode, long time, long rejectTime, int duration,
int proxyUid, String proxyPackage) {
mOp = op;
mMode = mode;
@@ -1913,7 +1979,7 @@ public class AppOpsManager {
/**
* @hide
*/
- public OpEntry(int op, int mode, long[] times, long[] rejectTimes, int duration,
+ public OpEntry(int op, @Mode int mode, long[] times, long[] rejectTimes, int duration,
boolean running, int proxyUid, String proxyPackage) {
mOp = op;
mMode = mode;
@@ -1930,7 +1996,7 @@ public class AppOpsManager {
/**
* @hide
*/
- public OpEntry(int op, int mode, long[] times, long[] rejectTimes, int duration,
+ public OpEntry(int op, @Mode int mode, long[] times, long[] rejectTimes, int duration,
int proxyUid, String proxyPackage) {
this(op, mode, times, rejectTimes, duration, duration == -1, proxyUid, proxyPackage);
}
@@ -1953,7 +2019,7 @@ public class AppOpsManager {
/**
* Return this entry's current mode, such as {@link #MODE_ALLOWED}.
*/
- public int getMode() {
+ public @Mode int getMode() {
return mMode;
}
@@ -2086,83 +2152,813 @@ public class AppOpsManager {
};
}
+ /** @hide */
+ public interface HistoricalOpsVisitor {
+ void visitHistoricalOps(@NonNull HistoricalOps ops);
+ void visitHistoricalUidOps(@NonNull HistoricalUidOps ops);
+ void visitHistoricalPackageOps(@NonNull HistoricalPackageOps ops);
+ void visitHistoricalOp(@NonNull HistoricalOp ops);
+ }
+
/**
- * This class represents historical app op information about a package. The history
- * is aggregated information about ops for a certain amount of time such
- * as the times the op was accessed, the times the op was rejected, the total
- * duration the app op has been accessed.
+ * This class represents historical app op state of all UIDs for a given time interval.
*
* @hide
*/
@TestApi
@SystemApi
- public static final class HistoricalPackageOps implements Parcelable {
- private final int mUid;
- private final @NonNull String mPackageName;
- private final @NonNull List<HistoricalOpEntry> mEntries;
+ public static final class HistoricalOps implements Parcelable {
+ private long mBeginTimeMillis;
+ private long mEndTimeMillis;
+ private @Nullable SparseArray<HistoricalUidOps> mHistoricalUidOps;
+
+ /** @hide */
+ @TestApi
+ public HistoricalOps(long beginTimeMillis, long endTimeMillis) {
+ Preconditions.checkState(beginTimeMillis <= endTimeMillis);
+ mBeginTimeMillis = beginTimeMillis;
+ mEndTimeMillis = endTimeMillis;
+ }
+
+ /** @hide */
+ public HistoricalOps(@NonNull HistoricalOps other) {
+ mBeginTimeMillis = other.mBeginTimeMillis;
+ mEndTimeMillis = other.mEndTimeMillis;
+ Preconditions.checkState(mBeginTimeMillis <= mEndTimeMillis);
+ if (other.mHistoricalUidOps != null) {
+ final int opCount = other.getUidCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalUidOps origOps = other.getUidOpsAt(i);
+ final HistoricalUidOps clonedOps = new HistoricalUidOps(origOps);
+ if (mHistoricalUidOps == null) {
+ mHistoricalUidOps = new SparseArray<>(opCount);
+ }
+ mHistoricalUidOps.put(clonedOps.getUid(), clonedOps);
+ }
+ }
+ }
+
+ private HistoricalOps(Parcel parcel) {
+ mBeginTimeMillis = parcel.readLong();
+ mEndTimeMillis = parcel.readLong();
+ final int[] uids = parcel.createIntArray();
+ if (!ArrayUtils.isEmpty(uids)) {
+ final ParceledListSlice<HistoricalUidOps> listSlice = parcel.readParcelable(
+ HistoricalOps.class.getClassLoader());
+ final List<HistoricalUidOps> uidOps = (listSlice != null)
+ ? listSlice.getList() : null;
+ if (uidOps == null) {
+ return;
+ }
+ for (int i = 0; i < uids.length; i++) {
+ if (mHistoricalUidOps == null) {
+ mHistoricalUidOps = new SparseArray<>();
+ }
+ mHistoricalUidOps.put(uids[i], uidOps.get(i));
+ }
+ }
+ }
/**
+ * Splice a piece from the beginning of these ops.
+ *
+ * @param splicePoint The fraction of the data to be spliced off.
+ *
* @hide
*/
- public HistoricalPackageOps(int uid, @NonNull String packageName) {
- mUid = uid;
- mPackageName = packageName;
- mEntries = new ArrayList<>();
+ public @NonNull HistoricalOps spliceFromBeginning(double splicePoint) {
+ return splice(splicePoint, true);
}
- HistoricalPackageOps(@NonNull Parcel parcel) {
- mUid = parcel.readInt();
- mPackageName = parcel.readString();
- mEntries = parcel.createTypedArrayList(HistoricalOpEntry.CREATOR);
+ /**
+ * Splice a piece from the end of these ops.
+ *
+ * @param fractionToRemove The fraction of the data to be spliced off.
+ *
+ * @hide
+ */
+ public @NonNull HistoricalOps spliceFromEnd(double fractionToRemove) {
+ return splice(fractionToRemove, false);
}
/**
+ * Splice a piece from the beginning or end of these ops.
+ *
+ * @param fractionToRemove The fraction of the data to be spliced off.
+ * @param beginning Whether to splice off the beginning or the end.
+ *
+ * @return The spliced off part.
+ *
* @hide
*/
- public void addEntry(@NonNull HistoricalOpEntry entry) {
- mEntries.add(entry);
+ private @Nullable HistoricalOps splice(double fractionToRemove, boolean beginning) {
+ final long spliceBeginTimeMills;
+ final long spliceEndTimeMills;
+ if (beginning) {
+ spliceBeginTimeMills = mBeginTimeMillis;
+ spliceEndTimeMills = (long) (mBeginTimeMillis
+ + getDurationMillis() * fractionToRemove);
+ mBeginTimeMillis = spliceEndTimeMills;
+ } else {
+ spliceBeginTimeMills = (long) (mEndTimeMillis
+ - getDurationMillis() * fractionToRemove);
+ spliceEndTimeMills = mEndTimeMillis;
+ mEndTimeMillis = spliceBeginTimeMills;
+ }
+
+ HistoricalOps splice = null;
+ final int uidCount = getUidCount();
+ for (int i = 0; i < uidCount; i++) {
+ final HistoricalUidOps origOps = getUidOpsAt(i);
+ final HistoricalUidOps spliceOps = origOps.splice(fractionToRemove);
+ if (spliceOps != null) {
+ if (splice == null) {
+ splice = new HistoricalOps(spliceBeginTimeMills, spliceEndTimeMills);
+ }
+ if (splice.mHistoricalUidOps == null) {
+ splice.mHistoricalUidOps = new SparseArray<>();
+ }
+ splice.mHistoricalUidOps.put(spliceOps.getUid(), spliceOps);
+ }
+ }
+ return splice;
}
/**
- * Gets the package name which the data represents.
+ * Merge the passed ops into the current ones. The time interval is a
+ * union of the current and passed in one and the passed in data is
+ * folded into the data of this instance.
*
- * @return The package name which the data represents.
+ * @hide
*/
- public @NonNull String getPackageName() {
- return mPackageName;
+ public void merge(@NonNull HistoricalOps other) {
+ mBeginTimeMillis = Math.min(mBeginTimeMillis, other.mBeginTimeMillis);
+ mEndTimeMillis = Math.max(mEndTimeMillis, other.mEndTimeMillis);
+ final int uidCount = other.getUidCount();
+ for (int i = 0; i < uidCount; i++) {
+ final HistoricalUidOps otherUidOps = other.getUidOpsAt(i);
+ final HistoricalUidOps thisUidOps = getUidOps(otherUidOps.getUid());
+ if (thisUidOps != null) {
+ thisUidOps.merge(otherUidOps);
+ } else {
+ if (mHistoricalUidOps == null) {
+ mHistoricalUidOps = new SparseArray<>();
+ }
+ mHistoricalUidOps.put(otherUidOps.getUid(), otherUidOps);
+ }
+ }
+ }
+
+ /**
+ * AppPermissionUsage the ops to leave only the data we filter for.
+ *
+ * @param uid Uid to filter for or {@link android.os.Process#INCIDENTD_UID} for all.
+ * @param packageName Package to filter for or null for all.
+ * @param opNames Ops to filter for or null for all.
+ * @param beginTimeMillis The begin time to filter for or {@link Long#MIN_VALUE} for all.
+ * @param endTimeMillis The end time to filter for or {@link Long#MAX_VALUE} for all.
+ *
+ * @hide
+ */
+ public void filter(int uid, @Nullable String packageName, @Nullable String[] opNames,
+ long beginTimeMillis, long endTimeMillis) {
+ final long durationMillis = getDurationMillis();
+ mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis);
+ mEndTimeMillis = Math.min(mEndTimeMillis, endTimeMillis);
+ final double scaleFactor = Math.min((double) (endTimeMillis - beginTimeMillis)
+ / (double) durationMillis, 1);
+ final int uidCount = getUidCount();
+ for (int i = uidCount - 1; i >= 0; i--) {
+ final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i);
+ if (uid != Process.INVALID_UID && uid != uidOp.getUid()) {
+ mHistoricalUidOps.removeAt(i);
+ } else {
+ uidOp.filter(packageName, opNames, scaleFactor);
+ }
+ }
+ }
+
+ /** @hide */
+ public boolean isEmpty() {
+ if (getBeginTimeMillis() >= getEndTimeMillis()) {
+ return true;
+ }
+ final int uidCount = getUidCount();
+ for (int i = uidCount - 1; i >= 0; i--) {
+ final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i);
+ if (!uidOp.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** @hide */
+ public long getDurationMillis() {
+ return mEndTimeMillis - mBeginTimeMillis;
+ }
+
+ /** @hide */
+ @TestApi
+ public void increaseAccessCount(int opCode, int uid, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalUidOps(uid).increaseAccessCount(opCode,
+ packageName, uidState, increment);
+ }
+
+ /** @hide */
+ @TestApi
+ public void increaseRejectCount(int opCode, int uid, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalUidOps(uid).increaseRejectCount(opCode,
+ packageName, uidState, increment);
+ }
+
+ /** @hide */
+ @TestApi
+ public void increaseAccessDuration(int opCode, int uid, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalUidOps(uid).increaseAccessDuration(opCode,
+ packageName, uidState, increment);
+ }
+
+ /** @hide */
+ @TestApi
+ public void offsetBeginAndEndTime(long offsetMillis) {
+ mBeginTimeMillis += offsetMillis;
+ mEndTimeMillis += offsetMillis;
+ }
+
+ /** @hide */
+ public void setBeginAndEndTime(long beginTimeMillis, long endTimeMillis) {
+ mBeginTimeMillis = beginTimeMillis;
+ mEndTimeMillis = endTimeMillis;
+ }
+
+ /** @hide */
+ public void setBeginTime(long beginTimeMillis) {
+ mBeginTimeMillis = beginTimeMillis;
+ }
+
+ /** @hide */
+ public void setEndTime(long endTimeMillis) {
+ mEndTimeMillis = endTimeMillis;
+ }
+
+ /**
+ * @return The beginning of the interval in milliseconds since
+ * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ */
+ public long getBeginTimeMillis() {
+ return mBeginTimeMillis;
+ }
+
+ /**
+ * @return The end of the interval in milliseconds since
+ * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ */
+ public long getEndTimeMillis() {
+ return mEndTimeMillis;
+ }
+
+ /**
+ * Gets number of UIDs with historical ops.
+ *
+ * @return The number of UIDs with historical ops.
+ *
+ * @see #getUidOpsAt(int)
+ */
+ public int getUidCount() {
+ if (mHistoricalUidOps == null) {
+ return 0;
+ }
+ return mHistoricalUidOps.size();
+ }
+
+ /**
+ * Gets the historical UID ops at a given index.
+ *
+ * @param index The index.
+ *
+ * @return The historical UID ops at the given index.
+ *
+ * @see #getUidCount()
+ */
+ public @NonNull HistoricalUidOps getUidOpsAt(int index) {
+ if (mHistoricalUidOps == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mHistoricalUidOps.valueAt(index);
+ }
+
+ /**
+ * Gets the historical UID ops for a given UID.
+ *
+ * @param uid The UID.
+ *
+ * @return The historical ops for the UID.
+ */
+ public @Nullable HistoricalUidOps getUidOps(int uid) {
+ if (mHistoricalUidOps == null) {
+ return null;
+ }
+ return mHistoricalUidOps.get(uid);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeLong(mBeginTimeMillis);
+ parcel.writeLong(mEndTimeMillis);
+ if (mHistoricalUidOps != null) {
+ final int uidCount = mHistoricalUidOps.size();
+ parcel.writeInt(uidCount);
+ for (int i = 0; i < uidCount; i++) {
+ parcel.writeInt(mHistoricalUidOps.keyAt(i));
+ }
+ final List<HistoricalUidOps> opsList = new ArrayList<>(uidCount);
+ for (int i = 0; i < uidCount; i++) {
+ opsList.add(mHistoricalUidOps.valueAt(i));
+ }
+ parcel.writeParcelable(new ParceledListSlice<>(opsList), flags);
+ } else {
+ parcel.writeInt(-1);
+ }
+ }
+
+ /**
+ * Accepts a visitor to traverse the ops tree.
+ *
+ * @param visitor The visitor.
+ *
+ * @hide
+ */
+ public void accept(@NonNull HistoricalOpsVisitor visitor) {
+ visitor.visitHistoricalOps(this);
+ final int uidCount = getUidCount();
+ for (int i = 0; i < uidCount; i++) {
+ getUidOpsAt(i).accept(visitor);
+ }
+ }
+
+ private @NonNull HistoricalUidOps getOrCreateHistoricalUidOps(int uid) {
+ if (mHistoricalUidOps == null) {
+ mHistoricalUidOps = new SparseArray<>();
+ }
+ HistoricalUidOps historicalUidOp = mHistoricalUidOps.get(uid);
+ if (historicalUidOp == null) {
+ historicalUidOp = new HistoricalUidOps(uid);
+ mHistoricalUidOps.put(uid, historicalUidOp);
+ }
+ return historicalUidOp;
}
/**
- * Gets the UID which the data represents.
+ * @return Rounded value up at the 0.5 boundary.
*
- * @return The UID which the data represents.
+ * @hide
+ */
+ public static double round(double value) {
+ final BigDecimal decimalScale = new BigDecimal(value);
+ return decimalScale.setScale(0, RoundingMode.HALF_UP).doubleValue();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final HistoricalOps other = (HistoricalOps) obj;
+ if (mBeginTimeMillis != other.mBeginTimeMillis) {
+ return false;
+ }
+ if (mEndTimeMillis != other.mEndTimeMillis) {
+ return false;
+ }
+ if (mHistoricalUidOps == null) {
+ if (other.mHistoricalUidOps != null) {
+ return false;
+ }
+ } else if (!mHistoricalUidOps.equals(other.mHistoricalUidOps)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) (mBeginTimeMillis ^ (mBeginTimeMillis >>> 32));
+ result = 31 * result + mHistoricalUidOps.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[from:"
+ + mBeginTimeMillis + " to:" + mEndTimeMillis + "]";
+ }
+
+ public static final Creator<HistoricalOps> CREATOR = new Creator<HistoricalOps>() {
+ @Override
+ public @NonNull HistoricalOps createFromParcel(@NonNull Parcel parcel) {
+ return new HistoricalOps(parcel);
+ }
+
+ @Override
+ public @NonNull HistoricalOps[] newArray(int size) {
+ return new HistoricalOps[size];
+ }
+ };
+ }
+
+ /**
+ * This class represents historical app op state for a UID.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public static final class HistoricalUidOps implements Parcelable {
+ private final int mUid;
+ private @Nullable ArrayMap<String, HistoricalPackageOps> mHistoricalPackageOps;
+
+ /** @hide */
+ public HistoricalUidOps(int uid) {
+ mUid = uid;
+ }
+
+ private HistoricalUidOps(@NonNull HistoricalUidOps other) {
+ mUid = other.mUid;
+ final int opCount = other.getPackageCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalPackageOps origOps = other.getPackageOpsAt(i);
+ final HistoricalPackageOps cloneOps = new HistoricalPackageOps(origOps);
+ if (mHistoricalPackageOps == null) {
+ mHistoricalPackageOps = new ArrayMap<>(opCount);
+ }
+ mHistoricalPackageOps.put(cloneOps.getPackageName(), cloneOps);
+ }
+ }
+
+ private HistoricalUidOps(@NonNull Parcel parcel) {
+ // No arg check since we always read from a trusted source.
+ mUid = parcel.readInt();
+ mHistoricalPackageOps = parcel.createTypedArrayMap(HistoricalPackageOps.CREATOR);
+ }
+
+ private @Nullable HistoricalUidOps splice(double fractionToRemove) {
+ HistoricalUidOps splice = null;
+ final int packageCount = getPackageCount();
+ for (int i = 0; i < packageCount; i++) {
+ final HistoricalPackageOps origOps = getPackageOpsAt(i);
+ final HistoricalPackageOps spliceOps = origOps.splice(fractionToRemove);
+ if (spliceOps != null) {
+ if (splice == null) {
+ splice = new HistoricalUidOps(mUid);
+ }
+ if (splice.mHistoricalPackageOps == null) {
+ splice.mHistoricalPackageOps = new ArrayMap<>();
+ }
+ splice.mHistoricalPackageOps.put(spliceOps.getPackageName(), spliceOps);
+ }
+ }
+ return splice;
+ }
+
+ private void merge(@NonNull HistoricalUidOps other) {
+ final int packageCount = other.getPackageCount();
+ for (int i = 0; i < packageCount; i++) {
+ final HistoricalPackageOps otherPackageOps = other.getPackageOpsAt(i);
+ final HistoricalPackageOps thisPackageOps = getPackageOps(
+ otherPackageOps.getPackageName());
+ if (thisPackageOps != null) {
+ thisPackageOps.merge(otherPackageOps);
+ } else {
+ if (mHistoricalPackageOps == null) {
+ mHistoricalPackageOps = new ArrayMap<>();
+ }
+ mHistoricalPackageOps.put(otherPackageOps.getPackageName(), otherPackageOps);
+ }
+ }
+ }
+
+ private void filter(@Nullable String packageName, @Nullable String[] opNames,
+ double fractionToRemove) {
+ final int packageCount = getPackageCount();
+ for (int i = packageCount - 1; i >= 0; i--) {
+ final HistoricalPackageOps packageOps = getPackageOpsAt(i);
+ if (packageName != null && !packageName.equals(packageOps.getPackageName())) {
+ mHistoricalPackageOps.removeAt(i);
+ } else {
+ packageOps.filter(opNames, fractionToRemove);
+ }
+ }
+ }
+
+ private boolean isEmpty() {
+ final int packageCount = getPackageCount();
+ for (int i = packageCount - 1; i >= 0; i--) {
+ final HistoricalPackageOps packageOps = mHistoricalPackageOps.valueAt(i);
+ if (!packageOps.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void increaseAccessCount(int opCode, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalPackageOps(packageName).increaseAccessCount(
+ opCode, uidState, increment);
+ }
+
+ private void increaseRejectCount(int opCode, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalPackageOps(packageName).increaseRejectCount(
+ opCode, uidState, increment);
+ }
+
+ private void increaseAccessDuration(int opCode, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalPackageOps(packageName).increaseAccessDuration(
+ opCode, uidState, increment);
+ }
+
+ /**
+ * @return The UID for which the data is related.
*/
public int getUid() {
return mUid;
}
/**
- * Gets number historical app op entries.
+ * Gets number of packages with historical ops.
+ *
+ * @return The number of packages with historical ops.
+ *
+ * @see #getPackageOpsAt(int)
+ */
+ public int getPackageCount() {
+ if (mHistoricalPackageOps == null) {
+ return 0;
+ }
+ return mHistoricalPackageOps.size();
+ }
+
+ /**
+ * Gets the historical package ops at a given index.
+ *
+ * @param index The index.
*
- * @return The number historical app op entries.
+ * @return The historical package ops at the given index.
*
- * @see #getEntryAt(int)
+ * @see #getPackageCount()
*/
- public int getEntryCount() {
- return mEntries.size();
+ public @NonNull HistoricalPackageOps getPackageOpsAt(int index) {
+ if (mHistoricalPackageOps == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mHistoricalPackageOps.valueAt(index);
}
/**
- * Gets the historical at a given index.
+ * Gets the historical package ops for a given package.
+ *
+ * @param packageName The package.
+ *
+ * @return The historical ops for the package.
+ */
+ public @Nullable HistoricalPackageOps getPackageOps(@NonNull String packageName) {
+ if (mHistoricalPackageOps == null) {
+ return null;
+ }
+ return mHistoricalPackageOps.get(packageName);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mUid);
+ parcel.writeTypedArrayMap(mHistoricalPackageOps, flags);
+ }
+
+ private void accept(@NonNull HistoricalOpsVisitor visitor) {
+ visitor.visitHistoricalUidOps(this);
+ final int packageCount = getPackageCount();
+ for (int i = 0; i < packageCount; i++) {
+ getPackageOpsAt(i).accept(visitor);
+ }
+ }
+
+ private @NonNull HistoricalPackageOps getOrCreateHistoricalPackageOps(
+ @NonNull String packageName) {
+ if (mHistoricalPackageOps == null) {
+ mHistoricalPackageOps = new ArrayMap<>();
+ }
+ HistoricalPackageOps historicalPackageOp = mHistoricalPackageOps.get(packageName);
+ if (historicalPackageOp == null) {
+ historicalPackageOp = new HistoricalPackageOps(packageName);
+ mHistoricalPackageOps.put(packageName, historicalPackageOp);
+ }
+ return historicalPackageOp;
+ }
+
+
+ public static final Creator<HistoricalUidOps> CREATOR = new Creator<HistoricalUidOps>() {
+ @Override
+ public @NonNull HistoricalUidOps createFromParcel(@NonNull Parcel parcel) {
+ return new HistoricalUidOps(parcel);
+ }
+
+ @Override
+ public @NonNull HistoricalUidOps[] newArray(int size) {
+ return new HistoricalUidOps[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final HistoricalUidOps other = (HistoricalUidOps) obj;
+ if (mUid != other.mUid) {
+ return false;
+ }
+ if (mHistoricalPackageOps == null) {
+ if (other.mHistoricalPackageOps != null) {
+ return false;
+ }
+ } else if (!mHistoricalPackageOps.equals(other.mHistoricalPackageOps)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mUid;
+ result = 31 * result + (mHistoricalPackageOps != null
+ ? mHistoricalPackageOps.hashCode() : 0);
+ return result;
+ }
+ }
+
+ /**
+ * This class represents historical app op information about a package.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public static final class HistoricalPackageOps implements Parcelable {
+ private final @NonNull String mPackageName;
+ private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps;
+
+ /** @hide */
+ public HistoricalPackageOps(@NonNull String packageName) {
+ mPackageName = packageName;
+ }
+
+ private HistoricalPackageOps(@NonNull HistoricalPackageOps other) {
+ mPackageName = other.mPackageName;
+ final int opCount = other.getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOp origOp = other.getOpAt(i);
+ final HistoricalOp cloneOp = new HistoricalOp(origOp);
+ if (mHistoricalOps == null) {
+ mHistoricalOps = new ArrayMap<>(opCount);
+ }
+ mHistoricalOps.put(cloneOp.getOpName(), cloneOp);
+ }
+ }
+
+ private HistoricalPackageOps(@NonNull Parcel parcel) {
+ mPackageName = parcel.readString();
+ mHistoricalOps = parcel.createTypedArrayMap(HistoricalOp.CREATOR);
+ }
+
+ private @Nullable HistoricalPackageOps splice(double fractionToRemove) {
+ HistoricalPackageOps splice = null;
+ final int opCount = getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOp origOps = getOpAt(i);
+ final HistoricalOp spliceOps = origOps.splice(fractionToRemove);
+ if (spliceOps != null) {
+ if (splice == null) {
+ splice = new HistoricalPackageOps(mPackageName);
+ }
+ if (splice.mHistoricalOps == null) {
+ splice.mHistoricalOps = new ArrayMap<>();
+ }
+ splice.mHistoricalOps.put(spliceOps.getOpName(), spliceOps);
+ }
+ }
+ return splice;
+ }
+
+ private void merge(@NonNull HistoricalPackageOps other) {
+ final int opCount = other.getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOp otherOp = other.getOpAt(i);
+ final HistoricalOp thisOp = getOp(otherOp.getOpName());
+ if (thisOp != null) {
+ thisOp.merge(otherOp);
+ } else {
+ if (mHistoricalOps == null) {
+ mHistoricalOps = new ArrayMap<>();
+ }
+ mHistoricalOps.put(otherOp.getOpName(), otherOp);
+ }
+ }
+ }
+
+ private void filter(@Nullable String[] opNames, double scaleFactor) {
+ final int opCount = getOpCount();
+ for (int i = opCount - 1; i >= 0; i--) {
+ final HistoricalOp op = mHistoricalOps.valueAt(i);
+ if (opNames != null && !ArrayUtils.contains(opNames, op.getOpName())) {
+ mHistoricalOps.removeAt(i);
+ } else {
+ op.filter(scaleFactor);
+ }
+ }
+ }
+
+ private boolean isEmpty() {
+ final int opCount = getOpCount();
+ for (int i = opCount - 1; i >= 0; i--) {
+ final HistoricalOp op = mHistoricalOps.valueAt(i);
+ if (!op.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void increaseAccessCount(int opCode, @UidState int uidState, long increment) {
+ getOrCreateHistoricalOp(opCode).increaseAccessCount(uidState, increment);
+ }
+
+ private void increaseRejectCount(int opCode, @UidState int uidState, long increment) {
+ getOrCreateHistoricalOp(opCode).increaseRejectCount(uidState, increment);
+ }
+
+ private void increaseAccessDuration(int opCode, @UidState int uidState, long increment) {
+ getOrCreateHistoricalOp(opCode).increaseAccessDuration(uidState, increment);
+ }
+
+ /**
+ * Gets the package name which the data represents.
+ *
+ * @return The package name which the data represents.
+ */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Gets number historical app ops.
+ *
+ * @return The number historical app ops.
+ *
+ * @see #getOpAt(int)
+ */
+ public int getOpCount() {
+ if (mHistoricalOps == null) {
+ return 0;
+ }
+ return mHistoricalOps.size();
+ }
+
+ /**
+ * Gets the historical op at a given index.
*
* @param index The index to lookup.
*
- * @return The entry at the given index.
+ * @return The op at the given index.
*
- * @see #getEntryCount()
+ * @see #getOpCount()
*/
- public @NonNull HistoricalOpEntry getEntryAt(int index) {
- return mEntries.get(index);
+ public @NonNull HistoricalOp getOpAt(int index) {
+ if (mHistoricalOps == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mHistoricalOps.valueAt(index);
}
/**
@@ -2172,15 +2968,11 @@ public class AppOpsManager {
*
* @return The historical entry for that op name.
*/
- public @Nullable HistoricalOpEntry getEntry(@NonNull String opName) {
- final int entryCount = mEntries.size();
- for (int i = 0; i < entryCount; i++) {
- final HistoricalOpEntry entry = mEntries.get(i);
- if (entry.getOp().equals(opName)) {
- return entry;
- }
+ public @Nullable HistoricalOp getOp(@NonNull String opName) {
+ if (mHistoricalOps == null) {
+ return null;
}
- return null;
+ return mHistoricalOps.get(opName);
}
@Override
@@ -2190,9 +2982,29 @@ public class AppOpsManager {
@Override
public void writeToParcel(@NonNull Parcel parcel, int flags) {
- parcel.writeInt(mUid);
parcel.writeString(mPackageName);
- parcel.writeTypedList(mEntries, flags);
+ parcel.writeTypedArrayMap(mHistoricalOps, flags);
+ }
+
+ private void accept(@NonNull HistoricalOpsVisitor visitor) {
+ visitor.visitHistoricalPackageOps(this);
+ final int opCount = getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ getOpAt(i).accept(visitor);
+ }
+ }
+
+ private @NonNull HistoricalOp getOrCreateHistoricalOp(int opCode) {
+ if (mHistoricalOps == null) {
+ mHistoricalOps = new ArrayMap<>();
+ }
+ final String opStr = sOpToString[opCode];
+ HistoricalOp op = mHistoricalOps.get(opStr);
+ if (op == null) {
+ op = new HistoricalOp(opCode);
+ mHistoricalOps.put(opStr, op);
+ }
+ return op;
}
public static final Creator<HistoricalPackageOps> CREATOR =
@@ -2207,49 +3019,177 @@ public class AppOpsManager {
return new HistoricalPackageOps[size];
}
};
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final HistoricalPackageOps other = (HistoricalPackageOps) obj;
+ if (!mPackageName.equals(other.mPackageName)) {
+ return false;
+ }
+ if (mHistoricalOps == null) {
+ if (other.mHistoricalOps != null) {
+ return false;
+ }
+ } else if (!mHistoricalOps.equals(other.mHistoricalOps)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mPackageName != null ? mPackageName.hashCode() : 0;
+ result = 31 * result + (mHistoricalOps != null ? mHistoricalOps.hashCode() : 0);
+ return result;
+ }
}
/**
- * This class represents historical information about an app op. The history
- * is aggregated information about the op for a certain amount of time such
- * as the times the op was accessed, the times the op was rejected, the total
- * duration the app op has been accessed.
+ * This class represents historical information about an app op.
*
* @hide
*/
@TestApi
@SystemApi
- public static final class HistoricalOpEntry implements Parcelable {
+ public static final class HistoricalOp implements Parcelable {
private final int mOp;
- private final long[] mAccessCount;
- private final long[] mRejectCount;
- private final long[] mAccessDuration;
+ private @Nullable long[] mAccessCount;
+ private @Nullable long[] mRejectCount;
+ private @Nullable long[] mAccessDuration;
- /**
- * @hide
- */
- public HistoricalOpEntry(int op) {
+ /** @hide */
+ public HistoricalOp(int op) {
mOp = op;
mAccessCount = new long[_NUM_UID_STATE];
mRejectCount = new long[_NUM_UID_STATE];
mAccessDuration = new long[_NUM_UID_STATE];
}
- HistoricalOpEntry(@NonNull Parcel parcel) {
+ private HistoricalOp(@NonNull HistoricalOp other) {
+ mOp = other.mOp;
+ if (other.mAccessCount != null) {
+ System.arraycopy(other.mAccessCount, 0, getOrCreateAccessCount(),
+ 0, other.mAccessCount.length);
+ }
+ if (other.mRejectCount != null) {
+ System.arraycopy(other.mRejectCount, 0, getOrCreateRejectCount(),
+ 0, other.mRejectCount.length);
+ }
+ if (other.mAccessDuration != null) {
+ System.arraycopy(other.mAccessDuration, 0, getOrCreateAccessDuration(),
+ 0, other.mAccessDuration.length);
+ }
+ }
+
+ private HistoricalOp(@NonNull Parcel parcel) {
mOp = parcel.readInt();
mAccessCount = parcel.createLongArray();
mRejectCount = parcel.createLongArray();
mAccessDuration = parcel.createLongArray();
}
- /**
- * @hide
- */
- public void addEntry(@UidState int uidState, long accessCount,
- long rejectCount, long accessDuration) {
- mAccessCount[uidState] = accessCount;
- mRejectCount[uidState] = rejectCount;
- mAccessDuration[uidState] = accessDuration;
+ private void filter(double scaleFactor) {
+ scale(mAccessCount, scaleFactor);
+ scale(mRejectCount, scaleFactor);
+ scale(mAccessDuration, scaleFactor);
+ }
+
+ private boolean isEmpty() {
+ return !hasData(mAccessCount)
+ && !hasData(mRejectCount)
+ && !hasData(mAccessDuration);
+ }
+
+ private boolean hasData(@NonNull long[] array) {
+ for (long value : array) {
+ if (value != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private @Nullable HistoricalOp splice(double fractionToRemove) {
+ HistoricalOp splice = null;
+ if (mAccessCount != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ final long spliceAccessCount = Math.round(
+ mAccessCount[i] * fractionToRemove);
+ if (spliceAccessCount > 0) {
+ if (splice == null) {
+ splice = new HistoricalOp(mOp);
+ }
+ splice.getOrCreateAccessCount()[i] = spliceAccessCount;
+ mAccessCount[i] -= spliceAccessCount;
+ }
+ }
+ }
+
+ if (mRejectCount != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ final long spliceRejectCount = Math.round(
+ mRejectCount[i] * fractionToRemove);
+
+ if (spliceRejectCount > 0) {
+ if (splice == null) {
+ splice = new HistoricalOp(mOp);
+ }
+ splice.getOrCreateRejectCount()[i] = spliceRejectCount;
+ mRejectCount[i] -= spliceRejectCount;
+ }
+ }
+ }
+
+ if (mAccessDuration != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ final long spliceAccessDuration = Math.round(
+ mAccessDuration[i] * fractionToRemove);
+ if (spliceAccessDuration > 0) {
+ if (splice == null) {
+ splice = new HistoricalOp(mOp);
+ }
+ splice.getOrCreateAccessDuration()[i] = spliceAccessDuration;
+ mAccessDuration[i] -= spliceAccessDuration;
+ }
+ }
+ }
+ return splice;
+ }
+
+ private void merge(@NonNull HistoricalOp other) {
+ if (other.mAccessCount != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ getOrCreateAccessCount()[i] += other.mAccessCount[i];
+ }
+ }
+ if (other.mRejectCount != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ getOrCreateRejectCount()[i] += other.mRejectCount[i];
+ }
+ }
+ if (other.mAccessDuration != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ getOrCreateAccessDuration()[i] += other.mAccessDuration[i];
+ }
+ }
+ }
+
+ private void increaseAccessCount(@UidState int uidState, long increment) {
+ getOrCreateAccessCount()[uidState] += increment;
+ }
+
+ private void increaseRejectCount(@UidState int uidState, long increment) {
+ getOrCreateRejectCount()[uidState] += increment;
+ }
+
+ private void increaseAccessDuration(@UidState int uidState, long increment) {
+ getOrCreateAccessDuration()[uidState] += increment;
}
/**
@@ -2257,10 +3197,15 @@ public class AppOpsManager {
*
* @return The op name.
*/
- public @NonNull String getOp() {
+ public @NonNull String getOpName() {
return sOpToString[mOp];
}
+ /** @hide */
+ public int getOpCode() {
+ return mOp;
+ }
+
/**
* Gets the number times the op was accessed (performed) in the foreground.
*
@@ -2270,6 +3215,9 @@ public class AppOpsManager {
* @see #getAccessCount(int)
*/
public long getForegroundAccessCount() {
+ if (mAccessCount == null) {
+ return 0;
+ }
return sum(mAccessCount, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1);
}
@@ -2282,6 +3230,9 @@ public class AppOpsManager {
* @see #getAccessCount(int)
*/
public long getBackgroundAccessCount() {
+ if (mAccessCount == null) {
+ return 0;
+ }
return sum(mAccessCount, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE);
}
@@ -2299,6 +3250,9 @@ public class AppOpsManager {
* @see #getBackgroundAccessCount()
*/
public long getAccessCount(@UidState int uidState) {
+ if (mAccessCount == null) {
+ return 0;
+ }
return mAccessCount[uidState];
}
@@ -2311,6 +3265,9 @@ public class AppOpsManager {
* @see #getRejectCount(int)
*/
public long getForegroundRejectCount() {
+ if (mRejectCount == null) {
+ return 0;
+ }
return sum(mRejectCount, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1);
}
@@ -2323,6 +3280,9 @@ public class AppOpsManager {
* @see #getRejectCount(int)
*/
public long getBackgroundRejectCount() {
+ if (mRejectCount == null) {
+ return 0;
+ }
return sum(mRejectCount, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE);
}
@@ -2340,6 +3300,9 @@ public class AppOpsManager {
* @see #getBackgroundRejectCount()
*/
public long getRejectCount(@UidState int uidState) {
+ if (mRejectCount == null) {
+ return 0;
+ }
return mRejectCount[uidState];
}
@@ -2352,6 +3315,9 @@ public class AppOpsManager {
* @see #getAccessDuration(int)
*/
public long getForegroundAccessDuration() {
+ if (mAccessDuration == null) {
+ return 0;
+ }
return sum(mAccessDuration, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1);
}
@@ -2364,6 +3330,9 @@ public class AppOpsManager {
* @see #getAccessDuration(int)
*/
public long getBackgroundAccessDuration() {
+ if (mAccessDuration == null) {
+ return 0;
+ }
return sum(mAccessDuration, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE);
}
@@ -2381,6 +3350,9 @@ public class AppOpsManager {
* @see #getBackgroundAccessDuration()
*/
public long getAccessDuration(@UidState int uidState) {
+ if (mAccessDuration == null) {
+ return 0;
+ }
return mAccessDuration[uidState];
}
@@ -2397,34 +3369,29 @@ public class AppOpsManager {
parcel.writeLongArray(mAccessDuration);
}
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- if (other == null || getClass() != other.getClass()) {
- return false;
- }
- final HistoricalOpEntry otherInstance = (HistoricalOpEntry) other;
- if (mOp != otherInstance.mOp) {
- return false;
- }
- if (!Arrays.equals(mAccessCount, otherInstance.mAccessCount)) {
- return false;
+ private void accept(@NonNull HistoricalOpsVisitor visitor) {
+ visitor.visitHistoricalOp(this);
+ }
+
+ private @NonNull long[] getOrCreateAccessCount() {
+ if (mAccessCount == null) {
+ mAccessCount = new long[_NUM_UID_STATE];
}
- if (!Arrays.equals(mRejectCount, otherInstance.mRejectCount)) {
- return false;
+ return mAccessCount;
+ }
+
+ private @NonNull long[] getOrCreateRejectCount() {
+ if (mRejectCount == null) {
+ mRejectCount = new long[_NUM_UID_STATE];
}
- return Arrays.equals(mAccessDuration, otherInstance.mAccessDuration);
+ return mRejectCount;
}
- @Override
- public int hashCode() {
- int result = mOp;
- result = 31 * result + Arrays.hashCode(mAccessCount);
- result = 31 * result + Arrays.hashCode(mRejectCount);
- result = 31 * result + Arrays.hashCode(mAccessDuration);
- return result;
+ private @NonNull long[] getOrCreateAccessDuration() {
+ if (mAccessDuration == null) {
+ mAccessDuration = new long[_NUM_UID_STATE];
+ }
+ return mAccessDuration;
}
/**
@@ -2444,17 +3411,62 @@ public class AppOpsManager {
return totalCount;
}
- public static final Creator<HistoricalOpEntry> CREATOR = new Creator<HistoricalOpEntry>() {
+ /**
+ * Multiplies the entries in the array with the passed in scale factor and
+ * rounds the result at up 0.5 boundary.
+ *
+ * @param data The data to scale.
+ * @param scaleFactor The scale factor.
+ */
+ private static void scale(@NonNull long[] data, double scaleFactor) {
+ if (data != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ data[i] = (long) HistoricalOps.round((double) data[i] * scaleFactor);
+ }
+ }
+ }
+
+ public static final Creator<HistoricalOp> CREATOR = new Creator<HistoricalOp>() {
@Override
- public @NonNull HistoricalOpEntry createFromParcel(@NonNull Parcel source) {
- return new HistoricalOpEntry(source);
+ public @NonNull HistoricalOp createFromParcel(@NonNull Parcel source) {
+ return new HistoricalOp(source);
}
@Override
- public @NonNull HistoricalOpEntry[] newArray(int size) {
- return new HistoricalOpEntry[size];
+ public @NonNull HistoricalOp[] newArray(int size) {
+ return new HistoricalOp[size];
}
};
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final HistoricalOp other = (HistoricalOp) obj;
+ if (mOp != other.mOp) {
+ return false;
+ }
+ if (!Arrays.equals(mAccessCount, other.mAccessCount)) {
+ return false;
+ }
+ if (!Arrays.equals(mRejectCount, other.mRejectCount)) {
+ return false;
+ }
+ return Arrays.equals(mAccessDuration, other.mAccessDuration);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mOp;
+ result = 31 * result + Arrays.hashCode(mAccessCount);
+ result = 31 * result + Arrays.hashCode(mRejectCount);
+ result = 31 * result + Arrays.hashCode(mAccessDuration);
+ return result;
+ }
}
/**
@@ -2520,6 +3532,24 @@ public class AppOpsManager {
* @param ops The set of operations you are interested in, or null if you want all of them.
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
+ public @NonNull List<AppOpsManager.PackageOps> getPackagesForOps(@Nullable String[] ops) {
+ final int opCount = ops.length;
+ final int[] opCodes = new int[opCount];
+ for (int i = 0; i < opCount; i++) {
+ opCodes[i] = sOpStrToOp.get(ops[i]);
+ }
+ final List<AppOpsManager.PackageOps> result = getPackagesForOps(opCodes);
+ return (result != null) ? result : Collections.emptyList();
+ }
+
+ /**
+ * Retrieve current operation state for all applications.
+ *
+ * @param ops The set of operations you are interested in, or null if you want all of them.
+ * @hide
+ */
@RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
@UnsupportedAppUsage
public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
@@ -2536,11 +3566,17 @@ public class AppOpsManager {
* @param uid The uid of the application of interest.
* @param packageName The name of the application of interest.
* @param ops The set of operations you are interested in, or null if you want all of them.
+ *
+ * @deprecated The int op codes are not stable and you should use the string based op
+ * names which are stable and namespaced. Use
+ * {@link #getOpsForPackage(int, String, String...)})}.
+ *
* @hide
*/
+ @Deprecated
@SystemApi
@RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
- public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) {
+ public List<PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) {
try {
return mService.getOpsForPackage(uid, packageName, ops);
} catch (RemoteException e) {
@@ -2549,7 +3585,49 @@ public class AppOpsManager {
}
/**
- * Retrieve historical app op stats for a package.
+ * Retrieve current operation state for one application. The UID and the
+ * package must match.
+ *
+ * @param uid The uid of the application of interest.
+ * @param packageName The name of the application of interest.
+ * @param ops The set of operations you are interested in, or null if you want all of them.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
+ public @NonNull List<AppOpsManager.PackageOps> getOpsForPackage(int uid,
+ @NonNull String packageName, @Nullable String... ops) {
+ int[] opCodes = null;
+ if (ops != null) {
+ opCodes = new int[ops.length];
+ for (int i = 0; i < ops.length; i++) {
+ opCodes[i] = strOpToOp(ops[i]);
+ }
+ }
+ try {
+ final List<PackageOps> result = mService.getOpsForPackage(uid, packageName, opCodes);
+ if (result == null) {
+ return Collections.emptyList();
+ }
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve historical app op stats for a period.
+ *
+ * <p>Historical data can be obtained
+ * for a specific package by specifying the <code>packageName</code> argument,
+ * for a specific UID if specifying the <code>uid</code> argument, for a
+ * specific package in a UID by specifying the <code>packageName</code>
+ * and the <code>uid</code> arguments, for all packages by passing
+ * {@link android.os.Process#INVALID_UID} and <code>null</code> for the
+ * <code>uid</code> and <code>packageName</code> arguments, respectively.
+ * Similarly, you can specify the <code>opNames</code> argument to get
+ * data only for these ops or <code>null</code> for all ops.
*
* @param uid The UID to query for.
* @param packageName The package to query for.
@@ -2558,10 +3636,12 @@ public class AppOpsManager {
* negative.
* @param endTimeMillis The end of the interval in milliseconds since
* epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after
- * {@code beginTimeMillis}.
+ * {@code beginTimeMillis}. Pass {@link Long#MAX_VALUE} to get the most recent
+ * history including ops that happen while this call is in flight.
* @param opNames The ops to query for. Pass {@code null} for all ops.
- *
- * @return The historical ops or {@code null} if there are no ops for this package.
+ * @param executor Executor on which to run the callback. If <code>null</code>
+ * the callback is executed on the default executor running on the main thread.
+ * @param callback Callback on which to deliver the result.
*
* @throws IllegalArgumentException If any of the argument contracts is violated.
*
@@ -2570,47 +3650,78 @@ public class AppOpsManager {
@TestApi
@SystemApi
@RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
- public @Nullable HistoricalPackageOps getHistoricalPackagesOps(
- int uid, @NonNull String packageName, @Nullable String[] opNames,
- long beginTimeMillis, long endTimeMillis) {
+ public void getHistoricalOps(int uid, @Nullable String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull Executor executor, @NonNull Consumer<HistoricalOps> callback) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
try {
- return mService.getHistoricalPackagesOps(uid, packageName, opNames,
- beginTimeMillis, endTimeMillis);
+ mService.getHistoricalOps(uid, packageName, opNames, beginTimeMillis, endTimeMillis,
+ new RemoteCallback((result) -> {
+ final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.accept(ops));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Retrieve historical app op stats for all packages.
+ * Retrieve historical app op stats for a period.
+ *
+ * <p>Historical data can be obtained
+ * for a specific package by specifying the <code>packageName</code> argument,
+ * for a specific UID if specifying the <code>uid</code> argument, for a
+ * specific package in a UID by specifying the <code>packageName</code>
+ * and the <code>uid</code> arguments, for all packages by passing
+ * {@link android.os.Process#INVALID_UID} and <code>null</code> for the
+ * <code>uid</code> and <code>packageName</code> arguments, respectively.
+ * Similarly, you can specify the <code>opNames</code> argument to get
+ * data only for these ops or <code>null</code> for all ops.
+ * <p>
+ * This method queries only the on disk state and the returned ops are raw,
+ * which is their times are relative to the history start as opposed to the
+ * epoch start.
*
+ * @param uid The UID to query for.
+ * @param packageName The package to query for.
* @param beginTimeMillis The beginning of the interval in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be non
- * negative.
+ * history start. History time grows as one goes into the past.
* @param endTimeMillis The end of the interval in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after
+ * history start. History time grows as one goes into the past. Must be after
* {@code beginTimeMillis}.
* @param opNames The ops to query for. Pass {@code null} for all ops.
- *
- * @return The historical ops or an empty list if there are no ops for any package.
+ * @param executor Executor on which to run the callback. If <code>null</code>
+ * the callback is executed on the default executor running on the main thread.
+ * @param callback Callback on which to deliver the result.
*
* @throws IllegalArgumentException If any of the argument contracts is violated.
*
* @hide
*/
@TestApi
- @SystemApi
@RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
- public @NonNull List<HistoricalPackageOps> getAllHistoricPackagesOps(
- @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis) {
+ public void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @Nullable Executor executor, @NonNull Consumer<HistoricalOps> callback) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
try {
- @SuppressWarnings("unchecked")
- final ParceledListSlice<HistoricalPackageOps> payload =
- mService.getAllHistoricalPackagesOps(opNames, beginTimeMillis, endTimeMillis);
- if (payload != null) {
- return payload.getList();
- }
- return Collections.emptyList();
+ mService.getHistoricalOpsFromDiskRaw(uid, packageName, opNames, beginTimeMillis,
+ endTimeMillis, new RemoteCallback((result) -> {
+ final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.accept(ops));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2627,7 +3738,7 @@ public class AppOpsManager {
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
- public void setUidMode(int code, int uid, int mode) {
+ public void setUidMode(int code, int uid, @Mode int mode) {
try {
mService.setUidMode(code, uid, mode);
} catch (RemoteException e) {
@@ -2648,7 +3759,7 @@ public class AppOpsManager {
@SystemApi
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
- public void setUidMode(String appOp, int uid, int mode) {
+ public void setUidMode(String appOp, int uid, @Mode int mode) {
try {
mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
} catch (RemoteException e) {
@@ -2680,7 +3791,7 @@ public class AppOpsManager {
/** @hide */
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
- public void setMode(int code, int uid, String packageName, int mode) {
+ public void setMode(int code, int uid, String packageName, @Mode int mode) {
try {
mService.setMode(code, uid, packageName, mode);
} catch (RemoteException e) {
@@ -2701,7 +3812,7 @@ public class AppOpsManager {
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
- public void setMode(String op, int uid, String packageName, int mode) {
+ public void setMode(String op, int uid, String packageName, @Mode int mode) {
try {
mService.setMode(strOpToOp(op), uid, packageName, mode);
} catch (RemoteException e) {
@@ -2722,7 +3833,7 @@ public class AppOpsManager {
*/
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
@UnsupportedAppUsage
- public void setRestriction(int code, @AttributeUsage int usage, int mode,
+ public void setRestriction(int code, @AttributeUsage int usage, @Mode int mode,
String[] exceptionPackages) {
try {
final int uid = Binder.getCallingUid();
@@ -3507,6 +4618,104 @@ public class AppOpsManager {
}
/**
+ * Configures the app ops persistence for testing.
+ *
+ * @param mode The mode in which the historical registry operates.
+ * @param baseSnapshotInterval The base interval on which we would be persisting a snapshot of
+ * the historical data. The history is recursive where every subsequent step encompasses
+ * {@code compressionStep} longer interval with {@code compressionStep} distance between
+ * snapshots.
+ * @param compressionStep The compression step in every iteration.
+ *
+ * @see #HISTORICAL_MODE_DISABLED
+ * @see #HISTORICAL_MODE_ENABLED_ACTIVE
+ * @see #HISTORICAL_MODE_ENABLED_PASSIVE
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+ public void setHistoryParameters(@HistoricalMode int mode, long baseSnapshotInterval,
+ int compressionStep) {
+ try {
+ mService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Offsets the history by the given duration.
+ *
+ * @param offsetMillis The offset duration.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+ public void offsetHistory(long offsetMillis) {
+ try {
+ mService.offsetHistory(offsetMillis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds ops to the history directly. This could be useful for testing especially
+ * when the historical registry operates in {@link #HISTORICAL_MODE_ENABLED_PASSIVE}
+ * mode.
+ *
+ * @param ops The ops to add to the history.
+ *
+ * @see #setHistoryParameters(int, long, int)
+ * @see #HISTORICAL_MODE_ENABLED_PASSIVE
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+ public void addHistoricalOps(@NonNull HistoricalOps ops) {
+ try {
+ mService.addHistoricalOps(ops);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Resets the app ops persistence for testing.
+ *
+ * @see #setHistoryParameters(int, long, int)
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+ public void resetHistoryParameters() {
+ try {
+ mService.resetHistoryParameters();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears all app ops history.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+ public void clearHistory() {
+ try {
+ mService.clearHistory();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns all supported operation names.
* @hide
*/
@@ -3538,4 +4747,64 @@ public class AppOpsManager {
}
return time;
}
+
+ /** @hide */
+ public static String uidStateToString(@UidState int uidState) {
+ switch (uidState) {
+ case UID_STATE_PERSISTENT: {
+ return "UID_STATE_PERSISTENT";
+ }
+ case UID_STATE_TOP: {
+ return "UID_STATE_TOP";
+ }
+ case UID_STATE_FOREGROUND_SERVICE: {
+ return "UID_STATE_FOREGROUND_SERVICE";
+ }
+ case UID_STATE_FOREGROUND: {
+ return "UID_STATE_FOREGROUND";
+ }
+ case UID_STATE_BACKGROUND: {
+ return "UID_STATE_BACKGROUND";
+ }
+ case UID_STATE_CACHED: {
+ return "UID_STATE_CACHED";
+ }
+ default: {
+ return "UNKNOWN";
+ }
+ }
+ }
+
+ /** @hide */
+ public static int parseHistoricalMode(@NonNull String mode) {
+ switch (mode) {
+ case "HISTORICAL_MODE_ENABLED_ACTIVE": {
+ return HISTORICAL_MODE_ENABLED_ACTIVE;
+ }
+ case "HISTORICAL_MODE_ENABLED_PASSIVE": {
+ return HISTORICAL_MODE_ENABLED_PASSIVE;
+ }
+ default: {
+ return HISTORICAL_MODE_DISABLED;
+ }
+ }
+ }
+
+ /** @hide */
+ public static String historicalModeToString(@HistoricalMode int mode) {
+ switch (mode) {
+ case HISTORICAL_MODE_DISABLED: {
+ return "HISTORICAL_MODE_DISABLED";
+ }
+ case HISTORICAL_MODE_ENABLED_ACTIVE: {
+ return "HISTORICAL_MODE_ENABLED_ACTIVE";
+ }
+ case HISTORICAL_MODE_ENABLED_PASSIVE: {
+ return "HISTORICAL_MODE_ENABLED_PASSIVE";
+ }
+ default: {
+ return "UNKNOWN";
+ }
+ }
+ }
}
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index b5295d15ee8d..07dbb6bee9fd 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -124,6 +124,10 @@ public abstract class ClientTransactionHandler {
/** Restart the activity after it was stopped. */
public abstract void performRestartActivity(IBinder token, boolean start);
+ /** Set pending activity configuration in case it will be updated by other transaction item. */
+ public abstract void updatePendingActivityConfiguration(IBinder activityToken,
+ Configuration overrideConfig);
+
/** Deliver activity (override) configuration change. */
public abstract void handleActivityConfigurationChanged(IBinder activityToken,
Configuration overrideConfig, int displayId);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 83c6fac6158e..d827f6cb9654 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -21,8 +21,11 @@ import android.accounts.IAccountManager;
import android.app.ContextImpl.ServiceInitializationState;
import android.app.admin.DevicePolicyManager;
import android.app.admin.IDevicePolicyManager;
+import android.app.contentsuggestions.ContentSuggestionsManager;
+import android.app.contentsuggestions.IContentSuggestionsManager;
import android.app.job.IJobScheduler;
import android.app.job.JobScheduler;
+import android.app.prediction.AppPredictionManager;
import android.app.role.RoleManager;
import android.app.slice.SliceManager;
import android.app.timedetector.TimeDetector;
@@ -1120,6 +1123,29 @@ final class SystemServiceRegistry {
return null;
}});
+ registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
+ new CachedServiceFetcher<AppPredictionManager>() {
+ @Override
+ public AppPredictionManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new AppPredictionManager(ctx);
+ }
+ });
+
+ registerService(Context.CONTENT_SUGGESTIONS_SERVICE,
+ ContentSuggestionsManager.class,
+ new CachedServiceFetcher<ContentSuggestionsManager>() {
+ @Override
+ public ContentSuggestionsManager createService(ContextImpl ctx) {
+ // No throw as this is an optional service
+ IBinder b = ServiceManager.getService(
+ Context.CONTENT_SUGGESTIONS_SERVICE);
+ IContentSuggestionsManager service =
+ IContentSuggestionsManager.Stub.asInterface(b);
+ return new ContentSuggestionsManager(service);
+ }
+ });
+
registerService(Context.VR_SERVICE, VrManager.class, new CachedServiceFetcher<VrManager>() {
@Override
public VrManager createService(ContextImpl ctx) throws ServiceNotFoundException {
diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl b/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl
new file mode 100644
index 000000000000..a66413becc29
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+parcelable ClassificationsRequest;
diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.java b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
new file mode 100644
index 000000000000..3f715186abfb
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ClassificationsRequest implements Parcelable {
+ @NonNull
+ private final List<ContentSelection> mSelections;
+ @Nullable
+ private final Bundle mExtras;
+
+ private ClassificationsRequest(@NonNull List<ContentSelection> selections,
+ @Nullable Bundle extras) {
+ mSelections = selections;
+ mExtras = extras;
+ }
+
+ /**
+ * Return request selections.
+ */
+ public List<ContentSelection> getSelections() {
+ return mSelections;
+ }
+
+ /**
+ * Return the request extras.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedList(mSelections);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Creator<ClassificationsRequest> CREATOR =
+ new Creator<ClassificationsRequest>() {
+ @Override
+ public ClassificationsRequest createFromParcel(Parcel source) {
+ return new ClassificationsRequest(
+ source.createTypedArrayList(ContentSelection.CREATOR),
+ source.readBundle());
+ }
+
+ @Override
+ public ClassificationsRequest[] newArray(int size) {
+ return new ClassificationsRequest[size];
+ }
+ };
+
+ /**
+ * A builder for classifications request events.
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ private final List<ContentSelection> mSelections;
+ private Bundle mExtras;
+
+ public Builder(@NonNull List<ContentSelection> selections) {
+ mSelections = selections;
+ }
+
+ /**
+ * Sets the request extras.
+ */
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds a new request instance.
+ */
+ public ClassificationsRequest build() {
+ return new ClassificationsRequest(mSelections, mExtras);
+ }
+ }
+}
diff --git a/core/java/android/app/contentsuggestions/ContentClassification.aidl b/core/java/android/app/contentsuggestions/ContentClassification.aidl
new file mode 100644
index 000000000000..f67b2c8cea36
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentClassification.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+parcelable ContentClassification;
diff --git a/core/java/android/app/contentsuggestions/ContentClassification.java b/core/java/android/app/contentsuggestions/ContentClassification.java
new file mode 100644
index 000000000000..f18520f1053c
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentClassification.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ContentClassification implements Parcelable {
+ @NonNull
+ private final String mClassificationId;
+ @NonNull
+ private final Bundle mExtras;
+
+ public ContentClassification(@NonNull String classificationId, @NonNull Bundle extras) {
+ mClassificationId = classificationId;
+ mExtras = extras;
+ }
+
+ /**
+ * Return the classification id.
+ */
+ public String getId() {
+ return mClassificationId;
+ }
+
+ /**
+ * Return the classification extras.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mClassificationId);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Creator<ContentClassification> CREATOR =
+ new Creator<ContentClassification>() {
+ @Override
+ public ContentClassification createFromParcel(Parcel source) {
+ return new ContentClassification(
+ source.readString(),
+ source.readBundle());
+ }
+
+ @Override
+ public ContentClassification[] newArray(int size) {
+ return new ContentClassification[size];
+ }
+ };
+}
diff --git a/core/java/android/app/contentsuggestions/ContentSelection.aidl b/core/java/android/app/contentsuggestions/ContentSelection.aidl
new file mode 100644
index 000000000000..e626d69278a4
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentSelection.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+parcelable ContentSelection;
diff --git a/core/java/android/app/contentsuggestions/ContentSelection.java b/core/java/android/app/contentsuggestions/ContentSelection.java
new file mode 100644
index 000000000000..a8917f782e83
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentSelection.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ContentSelection implements Parcelable {
+ @NonNull
+ private final String mSelectionId;
+ @NonNull
+ private final Bundle mExtras;
+
+ public ContentSelection(@NonNull String selectionId, @NonNull Bundle extras) {
+ mSelectionId = selectionId;
+ mExtras = extras;
+ }
+
+ /**
+ * Return the selection id.
+ */
+ public String getId() {
+ return mSelectionId;
+ }
+
+ /**
+ * Return the selection extras.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mSelectionId);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Creator<ContentSelection> CREATOR =
+ new Creator<ContentSelection>() {
+ @Override
+ public ContentSelection createFromParcel(Parcel source) {
+ return new ContentSelection(
+ source.readString(),
+ source.readBundle());
+ }
+
+ @Override
+ public ContentSelection[] newArray(int size) {
+ return new ContentSelection[size];
+ }
+ };
+}
diff --git a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
new file mode 100644
index 000000000000..b4d89774cc3b
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * When provided with content from an app, can suggest selections and classifications of that
+ * content.
+ *
+ * <p>The content is mainly a snapshot of a running task, the selections will be text and image
+ * selections with that image content. These mSelections can then be classified to find actions and
+ * entities on those selections.
+ *
+ * <p>Only accessible to blessed components such as Overview.
+ *
+ * @hide
+ */
+@SystemApi
+public final class ContentSuggestionsManager {
+ private static final String TAG = ContentSuggestionsManager.class.getSimpleName();
+
+ @Nullable
+ private final IContentSuggestionsManager mService;
+
+ /** @hide */
+ public ContentSuggestionsManager(@Nullable IContentSuggestionsManager service) {
+ mService = service;
+ }
+
+ /**
+ * Hints to the system that a new context image for the provided task should be sent to the
+ * system content suggestions service.
+ *
+ * @param taskId of the task to snapshot.
+ * @param imageContextRequestExtras sent with with request to provide implementation specific
+ * extra information.
+ */
+ public void provideContextImage(int taskId, @NonNull Bundle imageContextRequestExtras) {
+ if (mService == null) {
+ Log.e(TAG, "provideContextImage called, but no ContentSuggestionsManager configured");
+ return;
+ }
+
+ try {
+ mService.provideContextImage(taskId, imageContextRequestExtras);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Suggest content selections, based on the provided task id and optional
+ * location on screen provided in the request. Called after provideContextImage().
+ * The result can be passed to
+ * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)}
+ * to classify actions and entities on these selections.
+ *
+ * @param request containing the task and point location.
+ * @param callbackExecutor to execute the provided callback on.
+ * @param callback to receive the selections.
+ */
+ public void suggestContentSelections(
+ @NonNull SelectionsRequest request,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull SelectionsCallback callback) {
+ if (mService == null) {
+ Log.e(TAG,
+ "suggestContentSelections called, but no ContentSuggestionsManager configured");
+ return;
+ }
+
+ try {
+ mService.suggestContentSelections(
+ request, new SelectionsCallbackWrapper(callback, callbackExecutor));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Classify actions and entities in content selections, as returned from
+ * suggestContentSelections. Note these selections may be modified by the
+ * caller before being passed here.
+ *
+ * @param request containing the selections to classify.
+ * @param callbackExecutor to execute the provided callback on.
+ * @param callback to receive the classifications.
+ */
+ public void classifyContentSelections(
+ @NonNull ClassificationsRequest request,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull ClassificationsCallback callback) {
+ if (mService == null) {
+ Log.e(TAG, "classifyContentSelections called, "
+ + "but no ContentSuggestionsManager configured");
+ return;
+ }
+
+ try {
+ mService.classifyContentSelections(
+ request, new ClassificationsCallbackWrapper(callback, callbackExecutor));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Report telemetry for interaction with suggestions / classifications.
+ *
+ * @param requestId the id for the associated interaction
+ * @param interaction to report back to the system content suggestions service.
+ */
+ public void notifyInteraction(@NonNull String requestId, @NonNull Bundle interaction) {
+ if (mService == null) {
+ Log.e(TAG, "notifyInteraction called, but no ContentSuggestionsManager configured");
+ return;
+ }
+
+ try {
+ mService.notifyInteraction(requestId, interaction);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Callback to receive content selections from
+ * {@link #suggestContentSelections(SelectionsRequest, Executor, SelectionsCallback)}.
+ */
+ public interface SelectionsCallback {
+ /**
+ * Async callback called when the content suggestions service has selections available.
+ * These can be modified and sent back to the manager for classification. The contents of
+ * the selection is implementation dependent.
+ *
+ * @param statusCode as defined by the implementation of content suggestions service.
+ * @param selections not {@code null}, but can be size {@code 0}.
+ */
+ void onContentSelectionsAvailable(
+ int statusCode, @NonNull List<ContentSelection> selections);
+ }
+
+ /**
+ * Callback to receive classifications from
+ * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)}
+ */
+ public interface ClassificationsCallback {
+ /**
+ * Async callback called when the content suggestions service has classified selections. The
+ * contents of the classification is implementation dependent.
+ *
+ * @param statusCode as defined by the implementation of content suggestions service.
+ * @param classifications not {@code null}, but can be size {@code 0}.
+ */
+ void onContentClassificationsAvailable(int statusCode,
+ @NonNull List<ContentClassification> classifications);
+ }
+
+ private static class SelectionsCallbackWrapper extends ISelectionsCallback.Stub {
+ private final SelectionsCallback mCallback;
+ private final Executor mExecutor;
+
+ SelectionsCallbackWrapper(
+ @NonNull SelectionsCallback callback, @NonNull Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onContentSelectionsAvailable(
+ int statusCode, List<ContentSelection> selections) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() ->
+ mCallback.onContentSelectionsAvailable(statusCode, selections));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ private static final class ClassificationsCallbackWrapper extends
+ IClassificationsCallback.Stub {
+ private final ClassificationsCallback mCallback;
+ private final Executor mExecutor;
+
+ ClassificationsCallbackWrapper(@NonNull ClassificationsCallback callback,
+ @NonNull Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onContentClassificationsAvailable(
+ int statusCode, List<ContentClassification> classifications) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() ->
+ mCallback.onContentClassificationsAvailable(statusCode, classifications));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl b/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl
new file mode 100644
index 000000000000..69f5d5595986
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+import android.app.contentsuggestions.ContentClassification;
+
+/** @hide */
+oneway interface IClassificationsCallback {
+ void onContentClassificationsAvailable(
+ int statusCode, in List<ContentClassification> classifications);
+}
diff --git a/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl
new file mode 100644
index 000000000000..24f5ad866635
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.os.Bundle;
+
+/** @hide */
+oneway interface IContentSuggestionsManager {
+ void provideContextImage(
+ int taskId,
+ in Bundle imageContextRequestExtras);
+ void suggestContentSelections(
+ in SelectionsRequest request,
+ in ISelectionsCallback callback);
+ void classifyContentSelections(
+ in ClassificationsRequest request,
+ in IClassificationsCallback callback);
+ void notifyInteraction(in String requestId, in Bundle interaction);
+}
diff --git a/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl b/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl
new file mode 100644
index 000000000000..19525832a4cd
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+import android.app.contentsuggestions.ContentSelection;
+import android.os.Bundle;
+
+/** @hide */
+oneway interface ISelectionsCallback {
+ void onContentSelectionsAvailable(int statusCode, in List<ContentSelection> selections);
+}
diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.aidl b/core/java/android/app/contentsuggestions/SelectionsRequest.aidl
new file mode 100644
index 000000000000..f5ce7c31bd94
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/SelectionsRequest.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+parcelable SelectionsRequest;
diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.java b/core/java/android/app/contentsuggestions/SelectionsRequest.java
new file mode 100644
index 000000000000..16f3e6b35aa4
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/SelectionsRequest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class SelectionsRequest implements Parcelable {
+ private final int mTaskId;
+ @Nullable
+ private final Point mInterestPoint;
+ @Nullable
+ private final Bundle mExtras;
+
+ private SelectionsRequest(int taskId, @Nullable Point interestPoint, @Nullable Bundle extras) {
+ mTaskId = taskId;
+ mInterestPoint = interestPoint;
+ mExtras = extras;
+ }
+
+ /**
+ * Return the request task id.
+ */
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ /**
+ * Return the request point of interest.
+ */
+ public Point getInterestPoint() {
+ return mInterestPoint;
+ }
+
+ /**
+ * Return the request extras.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTaskId);
+ dest.writeTypedObject(mInterestPoint, flags);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Creator<SelectionsRequest> CREATOR =
+ new Creator<SelectionsRequest>() {
+ @Override
+ public SelectionsRequest createFromParcel(Parcel source) {
+ return new SelectionsRequest(
+ source.readInt(), source.readTypedObject(Point.CREATOR), source.readBundle());
+ }
+
+ @Override
+ public SelectionsRequest[] newArray(int size) {
+ return new SelectionsRequest[size];
+ }
+ };
+
+ /**
+ * A builder for selections requests events.
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ private final int mTaskId;
+ private Point mInterestPoint;
+ private Bundle mExtras;
+
+ public Builder(int taskId) {
+ mTaskId = taskId;
+ }
+
+ /**
+ * Sets the request extras.
+ */
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Sets the request interest point.
+ */
+ public Builder setInterestPoint(@NonNull Point interestPoint) {
+ mInterestPoint = interestPoint;
+ return this;
+ }
+
+ /**
+ * Builds a new request instance.
+ */
+ public SelectionsRequest build() {
+ return new SelectionsRequest(mTaskId, mInterestPoint, mExtras);
+ }
+ }
+}
diff --git a/core/java/android/app/prediction/AppPredictionContext.aidl b/core/java/android/app/prediction/AppPredictionContext.aidl
new file mode 100644
index 000000000000..5767bf4dbc9a
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionContext.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.prediction;
+
+parcelable AppPredictionContext;
diff --git a/core/java/android/app/prediction/AppPredictionContext.java b/core/java/android/app/prediction/AppPredictionContext.java
new file mode 100644
index 000000000000..87ccb660e5bd
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionContext.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * TODO(b/111701043): Add java docs
+ *
+ * @hide
+ */
+@SystemApi
+public final class AppPredictionContext implements Parcelable {
+
+ private final int mPredictedTargetCount;
+ @NonNull
+ private final String mUiSurface;
+ @NonNull
+ private final String mPackageName;
+ @Nullable
+ private final Bundle mExtras;
+
+ private AppPredictionContext(@NonNull String uiSurface, int numPredictedTargets,
+ @NonNull String packageName, @Nullable Bundle extras) {
+ mUiSurface = uiSurface;
+ mPredictedTargetCount = numPredictedTargets;
+ mPackageName = packageName;
+ mExtras = extras;
+ }
+
+ private AppPredictionContext(Parcel parcel) {
+ mUiSurface = parcel.readString();
+ mPredictedTargetCount = parcel.readInt();
+ mPackageName = parcel.readString();
+ mExtras = parcel.readBundle();
+ }
+
+ public String getUiSurface() {
+ return mUiSurface;
+ }
+
+ public int getPredictedTargetCount() {
+ return mPredictedTargetCount;
+ }
+
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Nullable
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mUiSurface);
+ dest.writeInt(mPredictedTargetCount);
+ dest.writeString(mPackageName);
+ dest.writeBundle(mExtras);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AppPredictionContext> CREATOR =
+ new Parcelable.Creator<AppPredictionContext>() {
+ public AppPredictionContext createFromParcel(Parcel parcel) {
+ return new AppPredictionContext(parcel);
+ }
+
+ public AppPredictionContext[] newArray(int size) {
+ return new AppPredictionContext[size];
+ }
+ };
+
+ /**
+ * A builder for app prediction contexts.
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ @NonNull
+ private final String mPackageName;
+
+ private int mPredictedTargetCount;
+ @Nullable
+ private String mUiSurface;
+ @Nullable
+ private Bundle mExtras;
+
+ /**
+ * @hide
+ */
+ public Builder(@NonNull Context context) {
+ mPackageName = context.getPackageName();
+ }
+
+
+ /**
+ * Sets the number of prediction targets as a hint.
+ */
+ public Builder setPredictedTargetCount(int predictedTargetCount) {
+ mPredictedTargetCount = predictedTargetCount;
+ return this;
+ }
+
+ /**
+ * Sets the UI surface.
+ */
+ public Builder setUiSurface(@Nullable String uiSurface) {
+ mUiSurface = uiSurface;
+ return this;
+ }
+
+ /**
+ * Sets the extras.
+ */
+ public Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds a new context instance.
+ */
+ public AppPredictionContext build() {
+ return new AppPredictionContext(mUiSurface, mPredictedTargetCount, mPackageName,
+ mExtras);
+ }
+ }
+}
diff --git a/core/java/android/app/prediction/AppPredictionManager.java b/core/java/android/app/prediction/AppPredictionManager.java
new file mode 100644
index 000000000000..f8578d4533ef
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionManager.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * TODO (b/111701043) : Add java doc
+ * @hide
+ */
+@SystemApi
+public final class AppPredictionManager {
+
+ private final Context mContext;
+
+ /**
+ * @hide
+ */
+ public AppPredictionManager(Context context) {
+ mContext = Preconditions.checkNotNull(context);
+ }
+
+ /**
+ * Creates a new app prediction session.
+ */
+ public AppPredictor createAppPredictionSession(
+ @NonNull AppPredictionContext predictionContext) {
+ return new AppPredictor(mContext, predictionContext);
+ }
+}
diff --git a/core/java/android/app/prediction/AppPredictionSessionId.aidl b/core/java/android/app/prediction/AppPredictionSessionId.aidl
new file mode 100644
index 000000000000..e8295261ae2a
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionSessionId.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.prediction;
+
+parcelable AppPredictionSessionId;
diff --git a/core/java/android/app/prediction/AppPredictionSessionId.java b/core/java/android/app/prediction/AppPredictionSessionId.java
new file mode 100644
index 000000000000..1d7308ec2404
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionSessionId.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * TODO (b/111701043) : Add java doc
+ *
+ * @hide
+ */
+@SystemApi
+public final class AppPredictionSessionId implements Parcelable {
+
+ private final String mId;
+
+ /**
+ * @hide
+ */
+ public AppPredictionSessionId(@NonNull String id) {
+ mId = id;
+ }
+
+ private AppPredictionSessionId(Parcel p) {
+ mId = p.readString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+ AppPredictionSessionId other = (AppPredictionSessionId) o;
+ return mId.equals(other.mId);
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return mId;
+ }
+
+ @Override
+ public int hashCode() {
+ // Ensure that the id has a consistent hash
+ return mId.hashCode();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AppPredictionSessionId> CREATOR =
+ new Parcelable.Creator<AppPredictionSessionId>() {
+ public AppPredictionSessionId createFromParcel(Parcel parcel) {
+ return new AppPredictionSessionId(parcel);
+ }
+
+ public AppPredictionSessionId[] newArray(int size) {
+ return new AppPredictionSessionId[size];
+ }
+ };
+}
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
new file mode 100644
index 000000000000..2ddbd08c7477
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.prediction;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.prediction.IPredictionCallback.Stub;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * TODO (b/111701043) : Add java doc
+ *
+ * <p>
+ * Usage: <pre> {@code
+ *
+ * class MyActivity {
+ * private AppPredictor mClient
+ *
+ * void onCreate() {
+ * mClient = new AppPredictor(...)
+ * }
+ *
+ * void onStart() {
+ * mClient.requestPredictionUpdate();
+ * }
+ *
+ * void onDestroy() {
+ * mClient.close();
+ * }
+ *
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class AppPredictor {
+
+ private static final String TAG = AppPredictor.class.getSimpleName();
+
+
+ private final IPredictionManager mPredictionManager;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+ private final AppPredictionSessionId mSessionId;
+ private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
+
+ /**
+ * Creates a new Prediction client.
+ * <p>
+ * The caller should call {@link AppPredictor#destroy()} to dispose the client once it
+ * no longer used.
+ *
+ * @param predictionContext The prediction context
+ */
+ AppPredictor(@NonNull Context context, @NonNull AppPredictionContext predictionContext) {
+ IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE);
+ mPredictionManager = IPredictionManager.Stub.asInterface(b);
+ mSessionId = new AppPredictionSessionId(
+ context.getPackageName() + ":" + UUID.randomUUID().toString());
+ try {
+ mPredictionManager.createPredictionSession(predictionContext, mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to create predictor", e);
+ return;
+ }
+
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Notifies the prediction service of an app target event.
+ */
+ public void notifyAppTargetEvent(@NonNull AppTargetEvent event) {
+ try {
+ mPredictionManager.notifyAppTargetEvent(mSessionId, event);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify app target event", e);
+ }
+ }
+
+ /**
+ * Notifies the prediction service when the targets in a launch location are shown to the user.
+ */
+ public void notifyLocationShown(@NonNull String launchLocation,
+ @NonNull List<AppTargetId> targetIds) {
+ try {
+ mPredictionManager.notifyLocationShown(mSessionId, launchLocation,
+ new ParceledListSlice<>(targetIds));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify location shown event", e);
+ }
+ }
+
+ /**
+ * Requests the prediction service provide continuous updates of App predictions via the
+ * provided callback, until the given callback is unregistered.
+ *
+ * @see Callback#onTargetsAvailable(List)
+ */
+ public void registerPredictionUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull AppPredictor.Callback callback) {
+ if (mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback is already registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = new CallbackWrapper(callbackExecutor,
+ callback::onTargetsAvailable);
+ mPredictionManager.registerPredictionUpdates(mSessionId, callbackWrapper);
+ mRegisteredCallbacks.put(callback, callbackWrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register for prediction updates", e);
+ }
+ }
+
+ /**
+ * Requests the prediction service to stop providing continuous updates to the provided
+ * callback until the callback is re-registered.
+ */
+ public void unregisterPredictionUpdates(@NonNull AppPredictor.Callback callback) {
+ if (!mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback was never registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(callback);
+ mPredictionManager.unregisterPredictionUpdates(mSessionId, callbackWrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unregister for prediction updates", e);
+ }
+ }
+
+ /**
+ * Requests the prediction service to dispatch a new set of App predictions via the provided
+ * callback.
+ *
+ * @see Callback#onTargetsAvailable(List)
+ */
+ public void requestPredictionUpdate() {
+ try {
+ mPredictionManager.requestPredictionUpdate(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to request prediction update", e);
+ }
+ }
+
+ /**
+ * Returns a new list of AppTargets sorted based on prediction rank or {@code null} if the
+ * ranker is not available.
+ */
+ @Nullable
+ public void sortTargets(@NonNull List<AppTarget> targets,
+ @NonNull Executor callbackExecutor, @NonNull Consumer<List<AppTarget>> callback) {
+ try {
+ mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice(targets),
+ new CallbackWrapper(callbackExecutor, callback));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to sort targets", e);
+ }
+ }
+
+ /**
+ * Destroys the client and unregisters the callback. Any method on this class after this call
+ * with throw {@link IllegalStateException}.
+ *
+ * TODO(b/111701043): Add state check in other methods.
+ */
+ public void destroy() {
+ if (!mIsClosed.getAndSet(true)) {
+ mCloseGuard.close();
+
+ // Do destroy;
+ try {
+ mPredictionManager.onDestroyPredictionSession(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify app target event", e);
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ destroy();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Callback for receiving prediction updates.
+ */
+ public interface Callback {
+
+ /**
+ * Called when a new set of predicted app targets are available.
+ * @param targets Sorted list of predicted targets
+ */
+ void onTargetsAvailable(@NonNull List<AppTarget> targets);
+ }
+
+ static class CallbackWrapper extends Stub {
+
+ private final Consumer<List<AppTarget>> mCallback;
+ private final Executor mExecutor;
+
+ CallbackWrapper(@NonNull Executor callbackExecutor,
+ @NonNull Consumer<List<AppTarget>> callback) {
+ mCallback = callback;
+ mExecutor = callbackExecutor;
+ }
+
+ @Override
+ public void onResult(ParceledListSlice result) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.accept(result.getList()));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/prediction/AppTarget.aidl b/core/java/android/app/prediction/AppTarget.aidl
new file mode 100644
index 000000000000..e4e2bc2aaaf5
--- /dev/null
+++ b/core/java/android/app/prediction/AppTarget.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.prediction;
+
+parcelable AppTarget;
diff --git a/core/java/android/app/prediction/AppTarget.java b/core/java/android/app/prediction/AppTarget.java
new file mode 100644
index 000000000000..99c1c44866fe
--- /dev/null
+++ b/core/java/android/app/prediction/AppTarget.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.pm.ShortcutInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A representation of a launchable target.
+ * @hide
+ */
+@SystemApi
+public final class AppTarget implements Parcelable {
+
+ private final AppTargetId mId;
+ private final String mPackageName;
+ private final String mClassName;
+ private final UserHandle mUser;
+
+ private final ShortcutInfo mShortcutInfo;
+
+ private int mRank;
+
+ /**
+ * @hide
+ */
+ public AppTarget(@NonNull AppTargetId id, @NonNull String packageName,
+ @Nullable String className, @NonNull UserHandle user) {
+ mId = id;
+ mShortcutInfo = null;
+
+ mPackageName = Preconditions.checkNotNull(packageName);
+ mClassName = className;
+ mUser = Preconditions.checkNotNull(user);
+ }
+
+ /**
+ * @hide
+ */
+ public AppTarget(@NonNull AppTargetId id, @NonNull ShortcutInfo shortcutInfo) {
+ mId = id;
+ mShortcutInfo = Preconditions.checkNotNull(shortcutInfo);
+
+ mPackageName = mShortcutInfo.getPackage();
+ mUser = mShortcutInfo.getUserHandle();
+ mClassName = null;
+ }
+
+ private AppTarget(Parcel parcel) {
+ mId = parcel.readTypedObject(AppTargetId.CREATOR);
+ mShortcutInfo = parcel.readTypedObject(ShortcutInfo.CREATOR);
+ if (mShortcutInfo == null) {
+ mPackageName = parcel.readString();
+ mClassName = parcel.readString();
+ mUser = UserHandle.of(parcel.readInt());
+ } else {
+ mPackageName = mShortcutInfo.getPackage();
+ mUser = mShortcutInfo.getUserHandle();
+ mClassName = null;
+ }
+ mRank = parcel.readInt();
+ }
+
+ /**
+ * Returns the target id.
+ */
+ @NonNull
+ public AppTargetId getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the class name for the app target.
+ */
+ @Nullable
+ public String getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Returns the package name for the app target.
+ */
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Returns the user for the app target.
+ */
+ @NonNull
+ public UserHandle getUser() {
+ return mUser;
+ }
+
+ /**
+ * Returns the shortcut info for the target.
+ */
+ @Nullable
+ public ShortcutInfo getShortcutInfo() {
+ return mShortcutInfo;
+ }
+
+ /**
+ * Sets the rank of the for the target.
+ * @hide
+ */
+ public void setRank(int rank) {
+ mRank = rank;
+ }
+
+ public int getRank() {
+ return mRank;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mId, flags);
+ dest.writeTypedObject(mShortcutInfo, flags);
+ if (mShortcutInfo == null) {
+ dest.writeString(mPackageName);
+ dest.writeString(mClassName);
+ dest.writeInt(mUser.getIdentifier());
+ }
+ dest.writeInt(mRank);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AppTarget> CREATOR =
+ new Parcelable.Creator<AppTarget>() {
+ public AppTarget createFromParcel(Parcel parcel) {
+ return new AppTarget(parcel);
+ }
+
+ public AppTarget[] newArray(int size) {
+ return new AppTarget[size];
+ }
+ };
+}
diff --git a/core/java/android/app/prediction/AppTargetEvent.aidl b/core/java/android/app/prediction/AppTargetEvent.aidl
new file mode 100644
index 000000000000..ebed2dadbee6
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetEvent.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.prediction;
+
+parcelable AppTargetEvent;
diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java
new file mode 100644
index 000000000000..18317e1bf99d
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetEvent.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.prediction;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A representation of an app target event.
+ * @hide
+ */
+@SystemApi
+public final class AppTargetEvent implements Parcelable {
+
+ /**
+ * @hide
+ */
+ @IntDef({ACTION_LAUNCH, ACTION_DISMISS, ACTION_PIN})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ActionType {}
+
+ /**
+ * Event type constant indicating an app target has been launched.
+ */
+ public static final int ACTION_LAUNCH = 1;
+
+ /**
+ * Event type constant indicating an app target has been dismissed.
+ */
+ public static final int ACTION_DISMISS = 2;
+
+ /**
+ * Event type constant indicating an app target has been pinned.
+ */
+ public static final int ACTION_PIN = 3;
+
+ private final AppTarget mTarget;
+ private final String mLocation;
+ private final int mAction;
+
+ private AppTargetEvent(@Nullable AppTarget target, @Nullable String location,
+ @ActionType int actionType) {
+ mTarget = target;
+ mLocation = location;
+ mAction = actionType;
+ }
+
+ private AppTargetEvent(Parcel parcel) {
+ mTarget = parcel.readParcelable(null);
+ mLocation = parcel.readString();
+ mAction = parcel.readInt();
+ }
+
+ /**
+ * Returns the app target.
+ */
+ @Nullable
+ public AppTarget getTarget() {
+ return mTarget;
+ }
+
+ /**
+ * Returns the launch location.
+ */
+ @NonNull
+ public String getLaunchLocation() {
+ return mLocation;
+ }
+
+ /**
+ * Returns the action type.
+ */
+ @NonNull
+ public int getAction() {
+ return mAction;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mTarget, 0);
+ dest.writeString(mLocation);
+ dest.writeInt(mAction);
+ }
+
+ /**
+ * @see Creator
+ */
+ public static final Creator<AppTargetEvent> CREATOR =
+ new Creator<AppTargetEvent>() {
+ public AppTargetEvent createFromParcel(Parcel parcel) {
+ return new AppTargetEvent(parcel);
+ }
+
+ public AppTargetEvent[] newArray(int size) {
+ return new AppTargetEvent[size];
+ }
+ };
+
+ /**
+ * A builder for app target events.
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ private AppTarget mTarget;
+ private String mLocation;
+ private @ActionType int mAction;
+
+ public Builder(@Nullable AppTarget target, @ActionType int actionType) {
+ mTarget = target;
+ mAction = actionType;
+ }
+
+ /**
+ * Sets the launch location.
+ */
+ public Builder setLaunchLocation(String location) {
+ mLocation = location;
+ return this;
+ }
+
+ /**
+ * Builds a new event instance.
+ */
+ public AppTargetEvent build() {
+ return new AppTargetEvent(mTarget, mLocation, mAction);
+ }
+ }
+}
diff --git a/core/java/android/app/prediction/AppTargetId.aidl b/core/java/android/app/prediction/AppTargetId.aidl
new file mode 100644
index 000000000000..bf69eeaca691
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetId.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.prediction;
+
+parcelable AppTargetId;
diff --git a/core/java/android/app/prediction/AppTargetId.java b/core/java/android/app/prediction/AppTargetId.java
new file mode 100644
index 000000000000..0b8fb47377d2
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetId.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The id for a prediction target.
+ * @hide
+ */
+@SystemApi
+public final class AppTargetId implements Parcelable {
+
+ @NonNull
+ private final String mId;
+
+ /**
+ * @hide
+ */
+ public AppTargetId(@NonNull String id) {
+ mId = id;
+ }
+
+ private AppTargetId(Parcel parcel) {
+ mId = parcel.readString();
+ }
+
+ /**
+ * Returns the id.
+ * @hide
+ */
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+ AppTargetId other = (AppTargetId) o;
+ return mId.equals(other.mId);
+ }
+
+ @Override
+ public int hashCode() {
+ // Ensure that the id has a consistent hash
+ return mId.hashCode();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ }
+
+ /**
+ * @see Creator
+ */
+ public static final Creator<AppTargetId> CREATOR =
+ new Creator<AppTargetId>() {
+ public AppTargetId createFromParcel(Parcel parcel) {
+ return new AppTargetId(parcel);
+ }
+
+ public AppTargetId[] newArray(int size) {
+ return new AppTargetId[size];
+ }
+ };
+}
diff --git a/core/java/android/app/prediction/IPredictionCallback.aidl b/core/java/android/app/prediction/IPredictionCallback.aidl
new file mode 100644
index 000000000000..f6f241e5560d
--- /dev/null
+++ b/core/java/android/app/prediction/IPredictionCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.prediction;
+
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+oneway interface IPredictionCallback {
+
+ void onResult(in ParceledListSlice result);
+}
diff --git a/core/java/android/app/prediction/IPredictionManager.aidl b/core/java/android/app/prediction/IPredictionManager.aidl
new file mode 100644
index 000000000000..114a1ffb0eb2
--- /dev/null
+++ b/core/java/android/app/prediction/IPredictionManager.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.prediction;
+
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.IPredictionCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+interface IPredictionManager {
+
+ void createPredictionSession(in AppPredictionContext context,
+ in AppPredictionSessionId sessionId);
+
+ void notifyAppTargetEvent(in AppPredictionSessionId sessionId, in AppTargetEvent event);
+
+ void notifyLocationShown(in AppPredictionSessionId sessionId, in String launchLocation,
+ in ParceledListSlice targetIds);
+
+ void sortAppTargets(in AppPredictionSessionId sessionId, in ParceledListSlice targets,
+ in IPredictionCallback callback);
+
+ void registerPredictionUpdates(in AppPredictionSessionId sessionId,
+ in IPredictionCallback callback);
+
+ void unregisterPredictionUpdates(in AppPredictionSessionId sessionId,
+ in IPredictionCallback callback);
+
+ void requestPredictionUpdate(in AppPredictionSessionId sessionId);
+
+ void onDestroyPredictionSession(in AppPredictionSessionId sessionId);
+}
diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl
index 0c9b41bf8d68..2964fbc0084b 100644
--- a/core/java/android/app/role/IRoleManager.aidl
+++ b/core/java/android/app/role/IRoleManager.aidl
@@ -50,4 +50,6 @@ interface IRoleManager {
boolean removeRoleHolderFromController(in String roleName, in String packageName);
List<String> getHeldRolesFromController(in String packageName);
+
+ String getDefaultSmsPackage(int userId);
}
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index d73e73f442d6..f14c56892807 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -19,6 +19,7 @@ package android.app.role;
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -592,7 +593,6 @@ public final class RoleManager {
}
}
-
/**
* Returns the list of all roles that the given package is currently holding
*
@@ -613,6 +613,22 @@ public final class RoleManager {
}
}
+ /**
+ * Allows getting the role holder for {@link #ROLE_SMS} without
+ * {@link Manifest.permission#OBSERVE_ROLE_HOLDERS}, as required by
+ * {@link android.provider.Telephony.Sms#getDefaultSmsPackage(Context)}
+ *
+ * @hide
+ */
+ @Nullable
+ public String getDefaultSmsPackage(@UserIdInt int userId) {
+ try {
+ return mService.getDefaultSmsPackage(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private static class RoleManagerCallbackDelegate extends IRoleManagerCallback.Stub {
@NonNull
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index a2b7d5809c4d..8ee9e5381b86 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -36,6 +36,11 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem {
private Configuration mConfiguration;
@Override
+ public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
+ client.updatePendingActivityConfiguration(token, mConfiguration);
+ }
+
+ @Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
// TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6f866eab56ea..cefc700d372a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -337,6 +337,15 @@ public abstract class Context {
public static final int BIND_ADJUST_BELOW_PERCEPTIBLE = 0x0100;
/**
+ * @hide Flag for {@link #bindService}: the service being bound to represents a
+ * protected system component, so must have association restrictions applied to it.
+ * That is, a system config must have one or more allow-association tags limiting
+ * which packages it can interact with. If it does not have any such association
+ * restrictions, a default empty set will be created.
+ */
+ public static final int BIND_RESTRICT_ASSOCIATIONS = 0x00200000;
+
+ /**
* @hide Flag for {@link #bindService}: allows binding to a service provided
* by an instant app. Note that the caller may not have access to the instant
* app providing the service which is a violation of the instant app sandbox.
@@ -3984,6 +3993,24 @@ public abstract class Context {
public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
/**
+ * Used for getting content selections and classifications for task snapshots.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
+
+ /**
+ * Official published name of the app prediction service.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String APP_PREDICTION_SERVICE = "app_prediction";
+
+ /**
* Use with {@link #getSystemService(String)} to access the
* {@link com.android.server.voiceinteraction.SoundTriggerService}.
*
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2978058b2848..576466f21bcb 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1866,7 +1866,15 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0;
}
- /** @hide */
+ /**
+ * Check whether the application is encryption aware.
+ *
+ * @see #isDirectBootAware()
+ * @see #isPartiallyDirectBootAware()
+ *
+ * @hide
+ */
+ @SystemApi
public boolean isEncryptionAware() {
return isDirectBootAware() || isPartiallyDirectBootAware();
}
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index be6ed51e3c89..74dd08fc1d6b 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -29,8 +29,11 @@ import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import android.annotation.UnsupportedAppUsage;
import android.os.BaseBundle;
+import android.os.Debug;
import android.os.PersistableBundle;
import android.util.ArraySet;
+import android.util.DebugUtils;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -43,6 +46,9 @@ import java.util.Objects;
* @hide
*/
public class PackageUserState {
+ private static final boolean DEBUG = false;
+ private static final String LOG_TAG = "PackageUserState";
+
public long ceDataInode;
public boolean installed;
public boolean stopped;
@@ -132,12 +138,12 @@ public class PackageUserState {
final boolean isSystemApp = componentInfo.applicationInfo.isSystemApp();
final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0;
if (!isAvailable(flags)
- && !(isSystemApp && matchUninstalled)) return false;
- if (!isEnabled(componentInfo, flags)) return false;
+ && !(isSystemApp && matchUninstalled)) return reportIfDebug(false, flags);
+ if (!isEnabled(componentInfo, flags)) return reportIfDebug(false, flags);
if ((flags & MATCH_SYSTEM_ONLY) != 0) {
if (!isSystemApp) {
- return false;
+ return reportIfDebug(false, flags);
}
}
@@ -145,7 +151,16 @@ public class PackageUserState {
&& !componentInfo.directBootAware;
final boolean matchesAware = ((flags & MATCH_DIRECT_BOOT_AWARE) != 0)
&& componentInfo.directBootAware;
- return matchesUnaware || matchesAware;
+ return reportIfDebug(matchesUnaware || matchesAware, flags);
+ }
+
+ private boolean reportIfDebug(boolean result, int flags) {
+ if (DEBUG && !result) {
+ Slog.i(LOG_TAG, "No match!; flags: "
+ + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " "
+ + Debug.getCaller());
+ }
+ return result;
}
/**
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index a8c3b889421b..6e519c1863e8 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -475,6 +475,14 @@ public abstract class RegisteredServicesCache<V> {
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
for (ResolveInfo resolveInfo : resolveInfos) {
try {
+ // if this package is not one of those changedUids, we don't need to scan it,
+ // since nothing in it changed, so save a call to parseServiceInfo, which
+ // can cause a large amount of the package apk to be loaded into memory.
+ // if this is the initial scan, changedUids will be null, and containsUid will
+ // trivially return true, and will call parseServiceInfo
+ if (!containsUid(changedUids, resolveInfo.serviceInfo.applicationInfo.uid)) {
+ continue;
+ }
ServiceInfo<V> info = parseServiceInfo(resolveInfo);
if (info == null) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index dd782ec3a42f..0cf2d18e5c04 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -16,7 +16,9 @@
package android.hardware.display;
+import android.Manifest;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.IBinder;
@@ -31,6 +33,7 @@ import com.android.internal.R;
*
* @hide
*/
+@SystemApi
@SystemService(Context.COLOR_DISPLAY_SERVICE)
public final class ColorDisplayManager {
@@ -48,13 +51,29 @@ public final class ColorDisplayManager {
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
public boolean isDeviceColorManaged() {
return mManager.isDeviceColorManaged();
}
/**
+ * Set the level of color saturation to apply to the display.
+ *
+ * @param saturationLevel 0-100 (inclusive), where 100 is full saturation
+ * @return whether the saturation level change was applied successfully
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public boolean setSaturationLevel(int saturationLevel) {
+ return mManager.setSaturationLevel(saturationLevel);
+ }
+
+ /**
* Returns {@code true} if Night Display is supported by the device.
+ *
+ * @hide
*/
public static boolean isNightDisplayAvailable(Context context) {
return context.getResources().getBoolean(R.bool.config_nightDisplayAvailable);
@@ -62,6 +81,8 @@ public final class ColorDisplayManager {
/**
* Returns {@code true} if display white balance is supported by the device.
+ *
+ * @hide
*/
public static boolean isDisplayWhiteBalanceAvailable(Context context) {
return context.getResources().getBoolean(R.bool.config_displayWhiteBalanceAvailable);
@@ -99,5 +120,13 @@ public final class ColorDisplayManager {
throw e.rethrowFromSystemServer();
}
}
+
+ boolean setSaturationLevel(int saturationLevel) {
+ try {
+ return mCdm.setSaturationLevel(saturationLevel);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 82e765dee447..44b653c314ad 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -563,11 +563,16 @@ public final class DisplayManager {
* 0 produces a grayscale image, 1 is normal.
*
* @hide
+ * @deprecated use {@link ColorDisplayManager#setSaturationLevel(int)}.
*/
@SystemApi
@RequiresPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION)
public void setSaturationLevel(float level) {
- mGlobal.setSaturationLevel(level);
+ if (level < 0f || level > 1f) {
+ throw new IllegalArgumentException("Saturation level must be between 0 and 1");
+ }
+ final ColorDisplayManager cdm = mContext.getSystemService(ColorDisplayManager.class);
+ cdm.setSaturationLevel(Math.round(level * 100f));
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 7304ab4aa1df..cda8498c4f01 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -394,17 +394,6 @@ public final class DisplayManagerGlobal {
}
}
- /**
- * Set the level of color saturation to apply to the display.
- */
- public void setSaturationLevel(float level) {
- try {
- mDm.setSaturationLevel(level);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection,
String name, int width, int height, int densityDpi, Surface surface, int flags,
VirtualDisplay.Callback callback, Handler handler, String uniqueId) {
diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl
index f7865899812a..81b82c64461f 100644
--- a/core/java/android/hardware/display/IColorDisplayManager.aidl
+++ b/core/java/android/hardware/display/IColorDisplayManager.aidl
@@ -19,4 +19,6 @@ package android.hardware.display;
/** @hide */
interface IColorDisplayManager {
boolean isDeviceColorManaged();
+
+ boolean setSaturationLevel(int saturationLevel);
} \ No newline at end of file
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index b57599724ad5..2d81cdfec179 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -66,9 +66,6 @@ interface IDisplayManager {
// Requires CONFIGURE_DISPLAY_COLOR_MODE
void requestColorMode(int displayId, int colorMode);
- // Requires CONTROL_DISPLAY_SATURATION
- void setSaturationLevel(float level);
-
// Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate
// MediaProjection token for certain combinations of flags.
int createVirtualDisplay(in IVirtualDisplayCallback callback,
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index cc6bb12a0894..b9cdcc096962 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -899,6 +899,33 @@ public final class Parcel {
}
/**
+ * Flatten an {@link ArrayMap} with string keys containing a particular object
+ * type into the parcel at the current dataPosition() and growing dataCapacity()
+ * if needed. The type of the objects in the array must be one that implements
+ * Parcelable. Only the raw data of the objects is written and not their type,
+ * so you must use the corresponding {@link #createTypedArrayMap(Parcelable.Creator)}
+ *
+ * @param val The map of objects to be written.
+ * @param parcelableFlags The parcelable flags to use.
+ *
+ * @see #createTypedArrayMap(Parcelable.Creator)
+ * @see Parcelable
+ */
+ public <T extends Parcelable> void writeTypedArrayMap(@Nullable ArrayMap<String, T> val,
+ int parcelableFlags) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+ final int count = val.size();
+ writeInt(count);
+ for (int i = 0; i < count; i++) {
+ writeString(val.keyAt(i));
+ writeTypedObject(val.valueAt(i), parcelableFlags);
+ }
+ }
+
+ /**
* Write an array set to the parcel.
*
* @param val The array set to write.
@@ -1001,7 +1028,7 @@ public final class Parcel {
* values are written using {@link #writeValue} and must follow the
* specification there.
*/
- public final void writeSparseArray(@Nullable SparseArray<Object> val) {
+ public final <T> void writeSparseArray(@Nullable SparseArray<T> val) {
if (val == null) {
writeInt(-1);
return;
@@ -1400,6 +1427,34 @@ public final class Parcel {
}
/**
+ * Flatten a {@link SparseArray} containing a particular object type into the parcel
+ * at the current dataPosition() and growing dataCapacity() if needed. The
+ * type of the objects in the array must be one that implements Parcelable.
+ * Unlike the generic {@link #writeSparseArray(SparseArray)} method, however, only
+ * the raw data of the objects is written and not their type, so you must use the
+ * corresponding {@link #createTypedSparseArray(Parcelable.Creator)}.
+ *
+ * @param val The list of objects to be written.
+ * @param parcelableFlags The parcelable flags to use.
+ *
+ * @see #createTypedSparseArray(Parcelable.Creator)
+ * @see Parcelable
+ */
+ public final <T extends Parcelable> void writeTypedSparseArray(@Nullable SparseArray<T> val,
+ int parcelableFlags) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+ final int count = val.size();
+ writeInt(count);
+ for (int i = 0; i < count; i++) {
+ writeInt(val.keyAt(i));
+ writeTypedObject(val.valueAt(i), parcelableFlags);
+ }
+ }
+
+ /**
* @hide
*/
public <T extends Parcelable> void writeTypedList(@Nullable List<T> val, int parcelableFlags) {
@@ -2369,7 +2424,7 @@ public final class Parcel {
* Parcelables.
*/
@Nullable
- public final SparseArray readSparseArray(@Nullable ClassLoader loader) {
+ public final <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader) {
int N = readInt();
if (N < 0) {
return null;
@@ -2466,6 +2521,62 @@ public final class Parcel {
}
/**
+ * Read into a new {@link SparseArray} items containing a particular object type
+ * that were written with {@link #writeTypedSparseArray(SparseArray, int)} at the
+ * current dataPosition(). The list <em>must</em> have previously been written
+ * via {@link #writeTypedSparseArray(SparseArray, int)} with the same object type.
+ *
+ * @param creator The creator to use when for instantiation.
+ *
+ * @return A newly created {@link SparseArray} containing objects with the same data
+ * as those that were previously written.
+ *
+ * @see #writeTypedSparseArray(SparseArray, int)
+ */
+ public final @Nullable <T extends Parcelable> SparseArray<T> createTypedSparseArray(
+ @NonNull Parcelable.Creator<T> creator) {
+ final int count = readInt();
+ if (count < 0) {
+ return null;
+ }
+ final SparseArray<T> array = new SparseArray<>(count);
+ for (int i = 0; i < count; i++) {
+ final int index = readInt();
+ final T value = readTypedObject(creator);
+ array.append(index, value);
+ }
+ return array;
+ }
+
+ /**
+ * Read into a new {@link ArrayMap} with string keys items containing a particular
+ * object type that were written with {@link #writeTypedArrayMap(ArrayMap, int)} at the
+ * current dataPosition(). The list <em>must</em> have previously been written
+ * via {@link #writeTypedArrayMap(ArrayMap, int)} with the same object type.
+ *
+ * @param creator The creator to use when for instantiation.
+ *
+ * @return A newly created {@link ArrayMap} containing objects with the same data
+ * as those that were previously written.
+ *
+ * @see #writeTypedArrayMap(ArrayMap, int)
+ */
+ public final @Nullable <T extends Parcelable> ArrayMap<String, T> createTypedArrayMap(
+ @NonNull Parcelable.Creator<T> creator) {
+ final int count = readInt();
+ if (count < 0) {
+ return null;
+ }
+ final ArrayMap<String, T> map = new ArrayMap<>(count);
+ for (int i = 0; i < count; i++) {
+ final String key = readString();
+ final T value = readTypedObject(creator);
+ map.append(key, value);
+ }
+ return map;
+ }
+
+ /**
* Read and return a new ArrayList containing String objects from
* the parcel that was written with {@link #writeStringList} at the
* current dataPosition(). Returns null if the
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index c3e04894c6c9..0942d97df6c7 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1326,7 +1326,7 @@ public final class PowerManager {
* @return Battery saver state data.
*
* @hide
- * @see com.android.server.power.BatterySaverPolicy
+ * @see com.android.server.power.batterysaver.BatterySaverPolicy
* @see PowerSaveState
*/
public PowerSaveState getPowerSaveState(@ServiceType int serviceType) {
diff --git a/core/java/android/os/PowerSaveState.java b/core/java/android/os/PowerSaveState.java
index de1128dfdef5..4918ad107763 100644
--- a/core/java/android/os/PowerSaveState.java
+++ b/core/java/android/os/PowerSaveState.java
@@ -27,7 +27,7 @@ public class PowerSaveState implements Parcelable {
/**
* Whether we should enable battery saver for this service.
*
- * @see com.android.server.power.BatterySaverPolicy
+ * @see com.android.server.power.batterysaver.BatterySaverPolicy
*/
public final boolean batterySaverEnabled;
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8d3de842f720..4649776b3d50 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6016,6 +6016,15 @@ public final class Settings {
"lock_screen_allow_remote_input";
/**
+ * Indicates which clock face to show on lock screen and AOD.
+ * @hide
+ */
+ public static final String LOCK_SCREEN_CUSTOM_CLOCK_FACE = "lock_screen_custom_clock_face";
+
+ private static final Validator LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR =
+ ANY_STRING_VALIDATOR;
+
+ /**
* Set by the system to track if the user needs to see the call to action for
* the lockscreen notification policy.
* @hide
@@ -8459,6 +8468,7 @@ public final class Settings {
HUSH_GESTURE_USED,
IN_CALL_NOTIFICATION_ENABLED,
LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ LOCK_SCREEN_CUSTOM_CLOCK_FACE,
LOCK_SCREEN_SHOW_NOTIFICATIONS,
ZEN_DURATION,
SHOW_ZEN_UPGRADE_NOTIFICATION,
@@ -8637,6 +8647,7 @@ public final class Settings {
VALIDATORS.put(ASSIST_GESTURE_SETUP_COMPLETE, BOOLEAN_VALIDATOR);
VALIDATORS.put(NOTIFICATION_NEW_INTERRUPTION_MODEL, BOOLEAN_VALIDATOR);
VALIDATORS.put(TRUST_AGENTS_EXTEND_UNLOCK, TRUST_AGENTS_EXTEND_UNLOCK_VALIDATOR);
+ VALIDATORS.put(LOCK_SCREEN_CUSTOM_CLOCK_FACE, LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR);
VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR);
}
@@ -11207,14 +11218,14 @@ public final class Settings {
* quick_doze_enabled (boolean)
* </pre>
* @hide
- * @see com.android.server.power.BatterySaverPolicy
+ * @see com.android.server.power.batterysaver.BatterySaverPolicy
*/
public static final String BATTERY_SAVER_CONSTANTS = "battery_saver_constants";
/**
* Battery Saver device specific settings
* This is encoded as a key=value list, separated by commas.
- * See {@link com.android.server.power.BatterySaverPolicy} for the details.
+ * See {@link com.android.server.power.batterysaver.BatterySaverPolicy} for the details.
*
* @hide
*/
@@ -13955,6 +13966,53 @@ public final class Settings {
*/
public static final String NATIVE_FLAGS_HEALTH_CHECK_ENABLED =
"native_flags_health_check_enabled";
+
+ /**
+ * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls the mode
+ * in which the historical registry operates.
+ *
+ * @hide
+ */
+ public static final String APPOP_HISTORY_MODE = "mode";
+
+ /**
+ * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls how long
+ * is the interval between snapshots in the base case i.e. the most recent
+ * part of the history.
+ *
+ * @hide
+ */
+ public static final String APPOP_HISTORY_BASE_INTERVAL_MILLIS = "baseIntervalMillis";
+
+ /**
+ * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls the base
+ * for the logarithmic step when building app op history.
+ *
+ * @hide
+ */
+ public static final String APPOP_HISTORY_INTERVAL_MULTIPLIER = "intervalMultiplier";
+
+ /**
+ * Appop history parameters. These parameters are represented by
+ * a comma-delimited key-value list.
+ *
+ * The following strings are supported as keys:
+ * <pre>
+ * mode (int)
+ * baseIntervalMillis (long)
+ * intervalMultiplier (int)
+ * </pre>
+ *
+ * Ex: "enabled=true,baseIntervalMillis=1000,intervalMultiplier=10"
+ *
+ * @see #APPOP_HISTORY_MODE
+ * @see #APPOP_HISTORY_BASE_INTERVAL_MILLIS
+ * @see #APPOP_HISTORY_INTERVAL_MULTIPLIER
+ *
+ * @hide
+ */
+ public static final String APPOP_HISTORY_PARAMETERS =
+ "appop_history_parameters";
}
/**
diff --git a/core/java/android/service/appprediction/AppPredictionService.java b/core/java/android/service/appprediction/AppPredictionService.java
new file mode 100644
index 000000000000..b77405affaec
--- /dev/null
+++ b/core/java/android/service/appprediction/AppPredictionService.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.appprediction;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.app.prediction.IPredictionCallback;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.service.appprediction.IPredictionService.Stub;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * TODO(b/111701043): Add java docs
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class AppPredictionService extends Service {
+
+ private static final String TAG = "AppPredictionService";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * TODO(b/111701043): Add any docs about permissions the service must hold
+ *
+ * @hide
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.appprediction.AppPredictionService";
+
+ private final ArrayMap<AppPredictionSessionId, ArrayList<CallbackWrapper>> mSessionCallbacks =
+ new ArrayMap<>();
+ private Handler mHandler;
+
+ private final IPredictionService mInterface = new Stub() {
+
+ @Override
+ public void onCreatePredictionSession(AppPredictionContext context,
+ AppPredictionSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::doCreatePredictionSession,
+ AppPredictionService.this, context, sessionId));
+ }
+
+ @Override
+ public void notifyAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::onAppTargetEvent,
+ AppPredictionService.this, sessionId, event));
+ }
+
+ @Override
+ public void notifyLocationShown(AppPredictionSessionId sessionId, String launchLocation,
+ ParceledListSlice targetIds) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::onLocationShown, AppPredictionService.this,
+ sessionId, launchLocation, targetIds.getList()));
+ }
+
+ @Override
+ public void sortAppTargets(AppPredictionSessionId sessionId, ParceledListSlice targets,
+ IPredictionCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::onSortAppTargets,
+ AppPredictionService.this, sessionId, targets.getList(), null,
+ new CallbackWrapper(callback)));
+ }
+
+ @Override
+ public void registerPredictionUpdates(AppPredictionSessionId sessionId,
+ IPredictionCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::doRegisterPredictionUpdates,
+ AppPredictionService.this, sessionId, callback));
+ }
+
+ @Override
+ public void unregisterPredictionUpdates(AppPredictionSessionId sessionId,
+ IPredictionCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::doUnregisterPredictionUpdates,
+ AppPredictionService.this, sessionId, callback));
+ }
+
+ @Override
+ public void requestPredictionUpdate(AppPredictionSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::doRequestPredictionUpdate,
+ AppPredictionService.this, sessionId));
+ }
+
+ @Override
+ public void onDestroyPredictionSession(AppPredictionSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::doDestroyPredictionSession,
+ AppPredictionService.this, sessionId));
+ }
+ };
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mInterface.asBinder();
+ }
+
+ /**
+ * Called by a client app to indicate a target launch
+ */
+ @MainThread
+ public abstract void onAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
+ @NonNull AppTargetEvent event);
+
+ /**
+ * Called by a client app to indication a particular location has been shown to the user.
+ */
+ @MainThread
+ public abstract void onLocationShown(@NonNull AppPredictionSessionId sessionId,
+ @NonNull String launchLocation, @NonNull List<AppTargetId> targetIds);
+
+ private void doCreatePredictionSession(@NonNull AppPredictionContext context,
+ @NonNull AppPredictionSessionId sessionId) {
+ mSessionCallbacks.put(sessionId, new ArrayList<>());
+ onCreatePredictionSession(context, sessionId);
+ }
+
+ /**
+ * Creates a new interaction session.
+ *
+ * @param context interaction context
+ * @param sessionId the session's Id
+ */
+ public void onCreatePredictionSession(@NonNull AppPredictionContext context,
+ @NonNull AppPredictionSessionId sessionId) {}
+
+ /**
+ * Called by the client app to request sorting of targets based on prediction rank.
+ * TODO(b/111701043): Implement CancellationSignal so caller can cancel a long running request
+ */
+ @MainThread
+ public abstract void onSortAppTargets(@NonNull AppPredictionSessionId sessionId,
+ @NonNull List<AppTarget> targets, @NonNull CancellationSignal cancellationSignal,
+ @NonNull Consumer<List<AppTarget>> callback);
+
+ private void doRegisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ if (wrapper == null) {
+ callbacks.add(new CallbackWrapper(callback));
+ if (callbacks.size() == 1) {
+ onStartPredictionUpdates();
+ }
+ }
+ }
+
+ /**
+ * Called when any continuous prediction callback is registered.
+ */
+ @MainThread
+ public void onStartPredictionUpdates() {}
+
+ private void doUnregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ if (wrapper != null) {
+ callbacks.remove(wrapper);
+ if (callbacks.isEmpty()) {
+ onStopPredictionUpdates();
+ }
+ }
+ }
+
+ /**
+ * Called when there are no longer any continuous prediction callbacks registered.
+ */
+ @MainThread
+ public void onStopPredictionUpdates() {}
+
+ private void doRequestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks != null && !callbacks.isEmpty()) {
+ onRequestPredictionUpdate(sessionId);
+ }
+ }
+
+ /**
+ * Called by the client app to request target predictions. This method is only called if there
+ * are one or more prediction callbacks registered.
+ * TODO(b/111701043): Add java docs
+ *
+ * @see #updatePredictions(AppPredictionSessionId, List)
+ */
+ @MainThread
+ public abstract void onRequestPredictionUpdate(@NonNull AppPredictionSessionId sessionId);
+
+ private void doDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
+ mSessionCallbacks.remove(sessionId);
+ onDestroyPredictionSession(sessionId);
+ }
+
+ /**
+ * Destroys the interaction session.
+ *
+ * @param sessionId the id of the session to destroy
+ */
+ @MainThread
+ public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {}
+
+ /**
+ * Used by the prediction factory to send back results the client app. The can be called
+ * in response to {@link #onRequestPredictionUpdate(AppPredictionSessionId)} or proactively as
+ * a result of changes in predictions.
+ */
+ public final void updatePredictions(@NonNull AppPredictionSessionId sessionId,
+ @NonNull List<AppTarget> targets) {
+ List<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks != null) {
+ for (CallbackWrapper callback : callbacks) {
+ callback.accept(targets);
+ }
+ }
+ }
+
+ /**
+ * Finds the callback wrapper for the given callback.
+ */
+ private CallbackWrapper findCallbackWrapper(ArrayList<CallbackWrapper> callbacks,
+ IPredictionCallback callback) {
+ for (int i = callbacks.size() - 1; i >= 0; i--) {
+ if (callbacks.get(i).isCallback(callback)) {
+ return callbacks.get(i);
+ }
+ }
+ return null;
+ }
+
+ private static final class CallbackWrapper implements Consumer<List<AppTarget>>,
+ IBinder.DeathRecipient {
+
+ private IPredictionCallback mCallback;
+
+ CallbackWrapper(IPredictionCallback callback) {
+ mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death: " + e);
+ }
+ }
+
+ public boolean isCallback(@NonNull IPredictionCallback callback) {
+ return mCallback.equals(callback);
+ }
+
+ @Override
+ public void accept(List<AppTarget> ts) {
+ try {
+ if (mCallback != null) {
+ mCallback.onResult(new ParceledListSlice(ts));
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result:" + e);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ mCallback = null;
+ }
+ }
+}
diff --git a/core/java/android/service/appprediction/IPredictionService.aidl b/core/java/android/service/appprediction/IPredictionService.aidl
new file mode 100644
index 000000000000..3a6d1666f4b9
--- /dev/null
+++ b/core/java/android/service/appprediction/IPredictionService.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.appprediction;
+
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.IPredictionCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * Interface from the system to a prediction service.
+ *
+ * @hide
+ */
+oneway interface IPredictionService {
+
+ void onCreatePredictionSession(in AppPredictionContext context,
+ in AppPredictionSessionId sessionId);
+
+ void notifyAppTargetEvent(in AppPredictionSessionId sessionId, in AppTargetEvent event);
+
+ void notifyLocationShown(in AppPredictionSessionId sessionId, in String launchLocation,
+ in ParceledListSlice targetIds);
+
+ void sortAppTargets(in AppPredictionSessionId sessionId, in ParceledListSlice targets,
+ in IPredictionCallback callback);
+
+ void registerPredictionUpdates(in AppPredictionSessionId sessionId,
+ in IPredictionCallback callback);
+
+ void unregisterPredictionUpdates(in AppPredictionSessionId sessionId,
+ in IPredictionCallback callback);
+
+ void requestPredictionUpdate(in AppPredictionSessionId sessionId);
+
+ void onDestroyPredictionSession(in AppPredictionSessionId sessionId);
+}
diff --git a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
new file mode 100644
index 000000000000..0da80397ec75
--- /dev/null
+++ b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.contentsuggestions;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.ContentSuggestionsManager;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * @hide
+ */
+@SystemApi
+public abstract class ContentSuggestionsService extends Service {
+
+ private static final String TAG = ContentSuggestionsService.class.getSimpleName();
+
+ private Handler mHandler;
+
+ /**
+ * The action for the intent used to define the content suggestions service.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.contentsuggestions.ContentSuggestionsService";
+
+ private final IContentSuggestionsService mInterface = new IContentSuggestionsService.Stub() {
+ @Override
+ public void provideContextImage(int taskId, GraphicBuffer contextImage,
+ Bundle imageContextRequestExtras) {
+ mHandler.sendMessage(
+ obtainMessage(ContentSuggestionsService::processContextImage,
+ ContentSuggestionsService.this, taskId,
+ Bitmap.createHardwareBitmap(contextImage),
+ imageContextRequestExtras));
+ }
+
+ @Override
+ public void suggestContentSelections(SelectionsRequest request,
+ ISelectionsCallback callback) {
+ mHandler.sendMessage(obtainMessage(ContentSuggestionsService::suggestContentSelections,
+ ContentSuggestionsService.this, request, wrapSelectionsCallback(callback)));
+
+ }
+
+ @Override
+ public void classifyContentSelections(ClassificationsRequest request,
+ IClassificationsCallback callback) {
+ mHandler.sendMessage(obtainMessage(ContentSuggestionsService::classifyContentSelections,
+ ContentSuggestionsService.this, request, wrapClassificationCallback(callback)));
+ }
+
+ @Override
+ public void notifyInteraction(String requestId, Bundle interaction) {
+ mHandler.sendMessage(
+ obtainMessage(ContentSuggestionsService::notifyInteraction,
+ ContentSuggestionsService.this, requestId, interaction));
+ }
+ };
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+
+ /** @hide */
+ @Override
+ public final IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+ return null;
+ }
+
+ /**
+ * Called by the system to provide the snapshot for the task associated with the given
+ * {@param taskId}.
+ */
+ public abstract void processContextImage(
+ int taskId, @Nullable Bitmap contextImage, @NonNull Bundle extras);
+
+ /**
+ * Called by a client app to make a request for content selections.
+ */
+ public abstract void suggestContentSelections(@NonNull SelectionsRequest request,
+ @NonNull ContentSuggestionsManager.SelectionsCallback callback);
+
+ /**
+ * Called by a client app to classify the provided content selections.
+ */
+ public abstract void classifyContentSelections(@NonNull ClassificationsRequest request,
+ @NonNull ContentSuggestionsManager.ClassificationsCallback callback);
+
+ /**
+ * Called by a client app to report an interaction.
+ */
+ public abstract void notifyInteraction(@NonNull String requestId, @NonNull Bundle interaction);
+
+ private ContentSuggestionsManager.SelectionsCallback wrapSelectionsCallback(
+ ISelectionsCallback callback) {
+ return (statusCode, selections) -> {
+ try {
+ callback.onContentSelectionsAvailable(statusCode, selections);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ };
+ }
+
+ private ContentSuggestionsManager.ClassificationsCallback wrapClassificationCallback(
+ IClassificationsCallback callback) {
+ return ((statusCode, classifications) -> {
+ try {
+ callback.onContentClassificationsAvailable(statusCode, classifications);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ });
+ }
+}
diff --git a/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl b/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl
new file mode 100644
index 000000000000..1926478322c8
--- /dev/null
+++ b/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.contentsuggestions;
+
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+
+/**
+ * Interface from the system to an implementation of a content suggestions service.
+ *
+ * @hide
+ */
+oneway interface IContentSuggestionsService {
+ void provideContextImage(
+ int taskId,
+ in GraphicBuffer contextImage,
+ in Bundle imageContextRequestExtras);
+ void suggestContentSelections(
+ in SelectionsRequest request,
+ in ISelectionsCallback callback);
+ void classifyContentSelections(
+ in ClassificationsRequest request,
+ in IClassificationsCallback callback);
+ void notifyInteraction(in String requestId, in Bundle interaction);
+}
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index af73a16e012a..265b0d303f7f 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -18,6 +18,7 @@ package android.util;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
+
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -250,7 +251,7 @@ public class DebugUtils {
if (value == 0 && flagsWasZero) {
return constNameWithoutPrefix(prefix, field);
}
- if ((flags & value) != 0) {
+ if ((flags & value) == value) {
flags &= ~value;
res.append(constNameWithoutPrefix(prefix, field)).append('|');
}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index e59bee42c21c..c4ab91f8b429 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -17,8 +17,10 @@
package com.android.internal.app;
import android.app.AppOpsManager;
+import android.app.AppOpsManager;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
+import android.os.RemoteCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsNotedCallback;
@@ -42,10 +44,15 @@ interface IAppOpsService {
int checkPackage(int uid, String packageName);
List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
- ParceledListSlice getAllHistoricalPackagesOps(in String[] ops,
- long beginTimeMillis, long endTimeMillis);
- AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int uid, String packageName,
- in String[] ops, long beginTimeMillis, long endTimeMillis);
+ void getHistoricalOps(int uid, String packageName, in String[] ops, long beginTimeMillis,
+ long endTimeMillis, in RemoteCallback callback);
+ void getHistoricalOpsFromDiskRaw(int uid, String packageName, in String[] ops,
+ long beginTimeMillis, long endTimeMillis, in RemoteCallback callback);
+ void offsetHistory(long duration);
+ void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep);
+ void addHistoricalOps(in AppOpsManager.HistoricalOps ops);
+ void resetHistoryParameters();
+ void clearHistory();
List<AppOpsManager.PackageOps> getUidOps(int uid, in int[] ops);
void setUidMode(int code, int uid, int mode);
void setMode(int code, int uid, String packageName, int mode);
diff --git a/core/java/com/android/internal/os/AtomicDirectory.java b/core/java/com/android/internal/os/AtomicDirectory.java
new file mode 100644
index 000000000000..f24d12e0c3af
--- /dev/null
+++ b/core/java/com/android/internal/os/AtomicDirectory.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FileUtils;
+import android.util.ArrayMap;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Helper class for performing atomic operations on a directory, by creating a
+ * backup directory until a write has successfully completed.
+ * <p>
+ * Atomic directory guarantees directory integrity by ensuring that a directory has
+ * been completely written and sync'd to disk before removing its backup.
+ * As long as the backup directory exists, the original directory is considered
+ * to be invalid (leftover from a previous attempt to write).
+ * <p>
+ * Atomic directory does not confer any file locking semantics. Do not use this
+ * class when the directory may be accessed or modified concurrently
+ * by multiple threads or processes. The caller is responsible for ensuring
+ * appropriate mutual exclusion invariants whenever it accesses the directory.
+ * <p>
+ * To ensure atomicity you must always use this class to interact with the
+ * backing directory when checking existence, making changes, and deleting.
+ */
+public final class AtomicDirectory {
+ private final @NonNull ArrayMap<File, FileOutputStream> mOpenFiles = new ArrayMap<>();
+ private final @NonNull File mBaseDirectory;
+ private final @NonNull File mBackupDirectory;
+
+ private int mBaseDirectoryFd = -1;
+ private int mBackupDirectoryFd = -1;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param baseDirectory The base directory to treat atomically.
+ */
+ public AtomicDirectory(@NonNull File baseDirectory) {
+ Preconditions.checkNotNull(baseDirectory, "baseDirectory cannot be null");
+ mBaseDirectory = baseDirectory;
+ mBackupDirectory = new File(baseDirectory.getPath() + "_bak");
+ }
+
+ /**
+ * Gets the backup directory if present. This could be useful if you are
+ * writing new state to the dir but need to access the last persisted state
+ * at the same time. This means that this call is useful in between
+ * {@link #startWrite()} and {@link #finishWrite()} or {@link #failWrite()}.
+ * You should not modify the content returned by this method.
+ *
+ * @see #startRead()
+ */
+ public @Nullable File getBackupDirectory() {
+ return mBackupDirectory;
+ }
+
+ /**
+ * Starts reading this directory. After calling this method you should
+ * not make any changes to its contents.
+ *
+ * @throws IOException If an error occurs.
+ *
+ * @see #finishRead()
+ * @see #startWrite()
+ */
+ public @NonNull File startRead() throws IOException {
+ restore();
+ return getOrCreateBaseDirectory();
+ }
+
+ /**
+ * Finishes reading this directory.
+ *
+ * @see #startRead()
+ * @see #startWrite()
+ */
+ public void finishRead() {
+ mBaseDirectoryFd = -1;
+ mBackupDirectoryFd = -1;
+ }
+
+ /**
+ * Starts editing this directory. After calling this method you should
+ * add content to the directory only via the APIs on this class. To open a
+ * file for writing in this directory you should use {@link #openWrite(File)}
+ * and to close the file {@link #closeWrite(FileOutputStream)}. Once all
+ * content has been written and all files closed you should commit via a
+ * call to {@link #finishWrite()} or discard via a call to {@link #failWrite()}.
+ *
+ * @throws IOException If an error occurs.
+ *
+ * @see #startRead()
+ * @see #openWrite(File)
+ * @see #finishWrite()
+ * @see #failWrite()
+ */
+ public @NonNull File startWrite() throws IOException {
+ backup();
+ return getOrCreateBaseDirectory();
+ }
+
+ /**
+ * Opens a file in this directory for writing.
+ *
+ * @param file The file to open. Must be a file in the base directory.
+ * @return An input stream for reading.
+ *
+ * @throws IOException If an I/O error occurs.
+ *
+ * @see #closeWrite(FileOutputStream)
+ */
+ public @NonNull FileOutputStream openWrite(@NonNull File file) throws IOException {
+ if (file.isDirectory() || !file.getParentFile().equals(getOrCreateBaseDirectory())) {
+ throw new IllegalArgumentException("Must be a file in " + getOrCreateBaseDirectory());
+ }
+ final FileOutputStream destination = new FileOutputStream(file);
+ if (mOpenFiles.put(file, destination) != null) {
+ throw new IllegalArgumentException("Already open file" + file.getCanonicalPath());
+ }
+ return destination;
+ }
+
+ /**
+ * Closes a previously opened file.
+ *
+ * @param destination The stream to the file returned by {@link #openWrite(File)}.
+ *
+ * @see #openWrite(File)
+ */
+ public void closeWrite(@NonNull FileOutputStream destination) {
+ final int indexOfValue = mOpenFiles.indexOfValue(destination);
+ if (mOpenFiles.removeAt(indexOfValue) == null) {
+ throw new IllegalArgumentException("Unknown file stream " + destination);
+ }
+ FileUtils.sync(destination);
+ try {
+ destination.close();
+ } catch (IOException ignored) {}
+ }
+
+ public void failWrite(@NonNull FileOutputStream destination) {
+ final int indexOfValue = mOpenFiles.indexOfValue(destination);
+ if (indexOfValue >= 0) {
+ mOpenFiles.removeAt(indexOfValue);
+ }
+ }
+
+ /**
+ * Finishes the edit and commits all changes.
+ *
+ * @see #startWrite()
+ *
+ * @throws IllegalStateException is some files are not closed.
+ */
+ public void finishWrite() {
+ throwIfSomeFilesOpen();
+ fsyncDirectoryFd(mBaseDirectoryFd);
+ deleteDirectory(mBackupDirectory);
+ fsyncDirectoryFd(mBackupDirectoryFd);
+ mBaseDirectoryFd = -1;
+ mBackupDirectoryFd = -1;
+ }
+
+ /**
+ * Finishes the edit and discards all changes.
+ *
+ * @see #startWrite()
+ */
+ public void failWrite() {
+ throwIfSomeFilesOpen();
+ try{
+ restore();
+ } catch (IOException ignored) {}
+ mBaseDirectoryFd = -1;
+ mBackupDirectoryFd = -1;
+ }
+
+ /**
+ * @return Whether this directory exists.
+ */
+ public boolean exists() {
+ return mBaseDirectory.exists() || mBackupDirectory.exists();
+ }
+
+ /**
+ * Deletes this directory.
+ */
+ public void delete() {
+ if (mBaseDirectory.exists()) {
+ deleteDirectory(mBaseDirectory);
+ fsyncDirectoryFd(mBaseDirectoryFd);
+ }
+ if (mBackupDirectory.exists()) {
+ deleteDirectory(mBackupDirectory);
+ fsyncDirectoryFd(mBackupDirectoryFd);
+ }
+ }
+
+ private @NonNull File getOrCreateBaseDirectory() throws IOException {
+ if (!mBaseDirectory.exists()) {
+ if (!mBaseDirectory.mkdirs()) {
+ throw new IOException("Couldn't create directory " + mBaseDirectory);
+ }
+ FileUtils.setPermissions(mBaseDirectory.getPath(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH,
+ -1, -1);
+ }
+ if (mBaseDirectoryFd < 0) {
+ mBaseDirectoryFd = getDirectoryFd(mBaseDirectory.getCanonicalPath());
+ }
+ return mBaseDirectory;
+ }
+
+ private void throwIfSomeFilesOpen() {
+ if (!mOpenFiles.isEmpty()) {
+ throw new IllegalStateException("Unclosed files: "
+ + Arrays.toString(mOpenFiles.keySet().toArray()));
+ }
+ }
+
+ private void backup() throws IOException {
+ if (!mBaseDirectory.exists()) {
+ return;
+ }
+ if (mBaseDirectoryFd < 0) {
+ mBaseDirectoryFd = getDirectoryFd(mBaseDirectory.getCanonicalPath());
+ }
+ if (mBackupDirectory.exists()) {
+ deleteDirectory(mBackupDirectory);
+ }
+ if (!mBaseDirectory.renameTo(mBackupDirectory)) {
+ throw new IOException("Couldn't backup " + mBaseDirectory
+ + " to " + mBackupDirectory);
+ }
+ mBackupDirectoryFd = mBaseDirectoryFd;
+ mBaseDirectoryFd = -1;
+ fsyncDirectoryFd(mBackupDirectoryFd);
+ }
+
+ private void restore() throws IOException {
+ if (!mBackupDirectory.exists()) {
+ return;
+ }
+ if (mBackupDirectoryFd == -1) {
+ mBackupDirectoryFd = getDirectoryFd(mBackupDirectory.getCanonicalPath());
+ }
+ if (mBaseDirectory.exists()) {
+ deleteDirectory(mBaseDirectory);
+ }
+ if (!mBackupDirectory.renameTo(mBaseDirectory)) {
+ throw new IOException("Couldn't restore " + mBackupDirectory
+ + " to " + mBaseDirectory);
+ }
+ mBaseDirectoryFd = mBackupDirectoryFd;
+ mBackupDirectoryFd = -1;
+ fsyncDirectoryFd(mBaseDirectoryFd);
+ }
+
+ private static void deleteDirectory(@NonNull File file) {
+ final File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ deleteDirectory(child);
+ }
+ }
+ file.delete();
+ }
+
+ private static native int getDirectoryFd(String path);
+ private static native void fsyncDirectoryFd(int fd);
+}
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 470533f2d002..605df040da59 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -330,4 +330,18 @@ public class CollectionUtils {
public static @NonNull <T> List<T> defeatNullable(@Nullable List<T> val) {
return (val != null) ? val : Collections.emptyList();
}
+
+ /**
+ * @return the first element if not empty/null, null otherwise
+ */
+ public static @Nullable <T> T firstOrNull(@Nullable List<T> cur) {
+ return isEmpty(cur) ? null : cur.get(0);
+ }
+
+ /**
+ * @return list of single given element if it's not null, empty list otherwise
+ */
+ public static @NonNull <T> List<T> singletonOrEmpty(@Nullable T item) {
+ return item == null ? Collections.emptyList() : Collections.singletonList(item);
+ }
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index dc6a73a7d75c..088e13ff92bc 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -41,7 +41,7 @@ cc_library_shared {
"com_google_android_gles_jni_EGLImpl.cpp",
"com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm
"android_app_Activity.cpp",
- "android_app_ActivityThread.cpp",
+ "android_app_ActivityThread.cpp",
"android_app_NativeActivity.cpp",
"android_app_admin_SecurityLog.cpp",
"android_opengl_EGL14.cpp",
@@ -202,6 +202,7 @@ cc_library_shared {
"android_animation_PropertyValuesHolder.cpp",
"android_security_Scrypt.cpp",
"com_android_internal_net_NetworkStatsFactory.cpp",
+ "com_android_internal_os_AtomicDirectory.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
"com_android_internal_os_Zygote.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 687b1055c3d6..109222298fe5 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -218,6 +218,7 @@ extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
extern int register_android_security_Scrypt(JNIEnv *env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env);
+extern int register_com_android_internal_os_AtomicDirectory(JNIEnv *env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
@@ -1495,6 +1496,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_security_Scrypt),
REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
REG_JNI(register_com_android_internal_net_NetworkStatsFactory),
+ REG_JNI(register_com_android_internal_os_AtomicDirectory),
REG_JNI(register_com_android_internal_os_FuseAppLoop),
};
diff --git a/core/jni/com_android_internal_os_AtomicDirectory.cpp b/core/jni/com_android_internal_os_AtomicDirectory.cpp
new file mode 100644
index 000000000000..50b2288a614a
--- /dev/null
+++ b/core/jni/com_android_internal_os_AtomicDirectory.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <nativehelper/ScopedUtfChars.h>
+#include "jni.h"
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static jint com_android_internal_os_AtomicDirectory_getDirectoryFd(JNIEnv* env,
+ jobject /*clazz*/, jstring path) {
+ ScopedUtfChars path8(env, path);
+ if (path8.c_str() == NULL) {
+ ALOGE("Invalid path: %s", path8.c_str());
+ return -1;
+ }
+ int fd;
+ if ((fd = TEMP_FAILURE_RETRY(open(path8.c_str(), O_DIRECTORY | O_RDONLY))) == -1) {
+ ALOGE("Cannot open directory %s, error: %s\n", path8.c_str(), strerror(errno));
+ return -1;
+ }
+ return fd;
+}
+
+static void com_android_internal_os_AtomicDirectory_fsyncDirectoryFd(JNIEnv* env,
+ jobject /*clazz*/, jint fd) {
+ if (TEMP_FAILURE_RETRY(fsync(fd)) == -1) {
+ ALOGE("Cannot fsync directory %d, error: %s\n", fd, strerror(errno));
+ }
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gRegisterMethods[] = {
+ /* name, signature, funcPtr */
+ { "fsyncDirectoryFd",
+ "(I)V",
+ (void*) com_android_internal_os_AtomicDirectory_fsyncDirectoryFd
+ },
+ { "getDirectoryFd",
+ "(Ljava/lang/String;)I",
+ (void*) com_android_internal_os_AtomicDirectory_getDirectoryFd
+ },
+};
+
+int register_com_android_internal_os_AtomicDirectory(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "com/android/internal/os/AtomicDirectory",
+ gRegisterMethods, NELEM(gRegisterMethods));
+}
+
+}; // namespace android
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index a00fde9ed0e3..cc5aa20258ce 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -1018,7 +1018,9 @@ message GlobalSettingsProto {
optional SettingProto zram_enabled = 139 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto app_ops_constants = 148 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 148;
+ // Next tag = 149;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 449a7b3305ad..8b85a28c4a40 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4254,6 +4254,16 @@
<permission android:name="android.permission.MANAGE_CONTENT_CAPTURE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage the content suggestions service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to manage the app predictions service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_APP_PREDICTIONS"
+ android:protectionLevel="signature" />
+
<!-- Allows an app to set the theme overlay in /vendor/overlay
being used.
@hide <p>Not for use by third-party applications.</p> -->
@@ -4334,6 +4344,11 @@
<permission android:name="android.permission.BIND_SMS_APP_SERVICE"
android:protectionLevel="signature" />
+ <!-- @hide Permission that allows configuring appops.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_APPOPS"
+ android:protectionLevel="signature" />
+
<!-- @hide Permission that allows background clipboard access.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a7e8deef346a..1feb59a52ad1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3423,6 +3423,23 @@
-->
<string name="config_defaultAugmentedAutofillService" translatable="false"></string>
+ <!-- The package name for the system's app prediction service.
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ Example: "com.android.intelligence/.AppPredictionService"
+ -->
+ <string name="config_defaultAppPredictionService" translatable="false"></string>
+
+ <!-- The package name for the system's content suggestions service.
+ Provides suggestions for text and image selection regions in snapshots of apps and should
+ be able to classify the type of entities in those selections.
+
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ If no service with the specified name exists on the device, content suggestions wil be
+ disabled.
+ Example: "com.android.contentsuggestions/.ContentSuggestionsService"
+ -->
+ <string name="config_defaultContentSuggestionsService" translatable="false"></string>
+
<!-- Whether the device uses the default focus highlight when focus state isn't specified. -->
<bool name="config_useDefaultFocusHighlight">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f707cedfd88f..010accf19d18 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3275,6 +3275,8 @@
<java-symbol type="string" name="config_defaultWellbeingPackage" />
<java-symbol type="string" name="config_defaultContentCaptureService" />
<java-symbol type="string" name="config_defaultAugmentedAutofillService" />
+ <java-symbol type="string" name="config_defaultAppPredictionService" />
+ <java-symbol type="string" name="config_defaultContentSuggestionsService" />
<java-symbol type="string" name="notification_channel_foreground_service" />
<java-symbol type="string" name="foreground_service_app_in_background" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 1750dac60e33..0f83a29d8d36 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -19,28 +19,35 @@ package android.app.activity;
import static android.content.Intent.ACTION_EDIT;
import static android.content.Intent.ACTION_VIEW;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.IApplicationThread;
+import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.StopActivityItem;
import android.content.Intent;
+import android.content.res.Configuration;
import android.os.IBinder;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.MergedConfiguration;
+import android.view.Display;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+
/**
* Test for verifying {@link android.app.ActivityThread} class.
* Build/Install/Run:
@@ -50,8 +57,12 @@ import org.junit.runner.RunWith;
@MediumTest
public class ActivityThreadTest {
- private final ActivityTestRule mActivityTestRule =
- new ActivityTestRule(TestActivity.class, true /* initialTouchMode */,
+ // The first sequence number to try with. Use a large number to avoid conflicts with the first a
+ // few sequence numbers the framework used to launch the test activity.
+ private static final int BASE_SEQ = 10000;
+
+ private final ActivityTestRule<TestActivity> mActivityTestRule =
+ new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
@Test
@@ -129,6 +140,179 @@ public class ActivityThreadTest {
});
}
+ @Test
+ public void testHandleActivityConfigurationChanged() {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ final int numOfConfig = activity.mNumOfConfigChanges;
+ applyConfigurationChange(activity, BASE_SEQ);
+ assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+ });
+ }
+
+ @Test
+ public void testHandleActivityConfigurationChanged_DropStaleConfigurations() {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // Set the sequence number to BASE_SEQ.
+ applyConfigurationChange(activity, BASE_SEQ);
+
+ final int orientation = activity.mConfig.orientation;
+ final int numOfConfig = activity.mNumOfConfigChanges;
+
+ // Try to apply an old configuration change.
+ applyConfigurationChange(activity, BASE_SEQ - 1);
+ assertEquals(numOfConfig, activity.mNumOfConfigChanges);
+ assertEquals(orientation, activity.mConfig.orientation);
+ });
+ }
+
+ @Test
+ public void testHandleActivityConfigurationChanged_ApplyNewConfigurations() {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // Set the sequence number to BASE_SEQ and record the final sequence number it used.
+ final int seq = applyConfigurationChange(activity, BASE_SEQ);
+
+ final int orientation = activity.mConfig.orientation;
+ final int numOfConfig = activity.mNumOfConfigChanges;
+
+ // Try to apply an new configuration change.
+ applyConfigurationChange(activity, seq + 1);
+ assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+ assertNotEquals(orientation, activity.mConfig.orientation);
+ });
+ }
+
+ @Test
+ public void testHandleActivityConfigurationChanged_PickNewerPendingConfiguration() {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // Set the sequence number to BASE_SEQ and record the final sequence number it used.
+ final int seq = applyConfigurationChange(activity, BASE_SEQ);
+
+ final int orientation = activity.mConfig.orientation;
+ final int numOfConfig = activity.mNumOfConfigChanges;
+
+ final ActivityThread activityThread = activity.getActivityThread();
+
+ final Configuration pendingConfig = new Configuration();
+ pendingConfig.orientation = orientation == Configuration.ORIENTATION_LANDSCAPE
+ ? Configuration.ORIENTATION_PORTRAIT
+ : Configuration.ORIENTATION_LANDSCAPE;
+ pendingConfig.seq = seq + 2;
+ activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
+ pendingConfig);
+
+ final Configuration newConfig = new Configuration();
+ newConfig.orientation = orientation;
+ newConfig.seq = seq + 1;
+
+ activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+ newConfig, Display.INVALID_DISPLAY);
+ assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+ assertEquals(pendingConfig.orientation, activity.mConfig.orientation);
+ });
+ }
+
+ @Test
+ public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration()
+ throws Exception {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+ final ActivityThread activityThread = activity.getActivityThread();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ final Configuration config = new Configuration();
+ config.seq = BASE_SEQ;
+ config.orientation = Configuration.ORIENTATION_PORTRAIT;
+
+ activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+ config, Display.INVALID_DISPLAY);
+ });
+
+ final int numOfConfig = activity.mNumOfConfigChanges;
+ final IApplicationThread appThread = activityThread.getApplicationThread();
+
+ activity.mConfigLatch = new CountDownLatch(1);
+ activity.mTestLatch = new CountDownLatch(1);
+
+ Configuration config = new Configuration();
+ config.seq = BASE_SEQ + 1;
+ config.smallestScreenWidthDp = 100;
+ appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+ // Wait until the main thread is performing the configuration change for the configuration
+ // with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where
+ // the activity takes very long time to process configuration changes.
+ activity.mTestLatch.await();
+
+ config = new Configuration();
+ config.seq = BASE_SEQ + 2;
+ config.smallestScreenWidthDp = 200;
+ appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+ config = new Configuration();
+ config.seq = BASE_SEQ + 3;
+ config.smallestScreenWidthDp = 300;
+ appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+ config = new Configuration();
+ config.seq = BASE_SEQ + 4;
+ config.smallestScreenWidthDp = 400;
+ appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+ activity.mConfigLatch.countDown();
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ activity.mConfigLatch = null;
+ activity.mTestLatch = null;
+
+ // Only two more configuration changes: one with seq BASE_SEQ + 1; another with seq
+ // BASE_SEQ + 4. Configurations scheduled in between should be dropped.
+ assertEquals(numOfConfig + 2, activity.mNumOfConfigChanges);
+ assertEquals(400, activity.mConfig.smallestScreenWidthDp);
+ }
+
+ /**
+ * Calls {@link ActivityThread#handleActivityConfigurationChanged(IBinder, Configuration, int)}
+ * to try to push activity configuration to the activity for the given sequence number.
+ * <p>
+ * It uses orientation to push the configuration and it tries a different orientation if the
+ * first attempt doesn't make through, to rule out the possibility that the previous
+ * configuration already has the same orientation.
+ *
+ * @param activity the test target activity
+ * @param seq the specified sequence number
+ * @return the sequence number this method tried with the last time, so that the caller can use
+ * the next sequence number for next configuration update.
+ */
+ private int applyConfigurationChange(TestActivity activity, int seq) {
+ final ActivityThread activityThread = activity.getActivityThread();
+
+ final int numOfConfig = activity.mNumOfConfigChanges;
+ Configuration config = new Configuration();
+ config.orientation = Configuration.ORIENTATION_PORTRAIT;
+ config.seq = seq;
+ activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
+ Display.INVALID_DISPLAY);
+
+ if (activity.mNumOfConfigChanges > numOfConfig) {
+ return config.seq;
+ }
+
+ config = new Configuration();
+ config.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ config.seq = seq + 1;
+ activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
+ Display.INVALID_DISPLAY);
+
+ return config.seq;
+ }
+
private static ClientTransaction newRelaunchResumeTransaction(Activity activity) {
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null,
null, 0, new MergedConfiguration(), false /* preserveWindow */);
@@ -162,6 +346,16 @@ public class ActivityThreadTest {
return transaction;
}
+ private static ClientTransaction newActivityConfigTransaction(Activity activity,
+ Configuration config) {
+ final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config);
+
+ final ClientTransaction transaction = newTransaction(activity);
+ transaction.addCallback(item);
+
+ return transaction;
+ }
+
private static ClientTransaction newTransaction(Activity activity) {
final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
return ClientTransaction.obtain(appThread, activity.getActivityToken());
@@ -169,5 +363,37 @@ public class ActivityThreadTest {
// Test activity
public static class TestActivity extends Activity {
+ int mNumOfConfigChanges;
+ final Configuration mConfig = new Configuration();
+
+ /**
+ * A latch used to notify tests that we're about to wait for configuration latch. This
+ * is used to notify test code that preExecute phase for activity configuration change
+ * transaction has passed.
+ */
+ volatile CountDownLatch mTestLatch;
+ /**
+ * If not {@code null} {@link #onConfigurationChanged(Configuration)} won't return until the
+ * latch reaches 0.
+ */
+ volatile CountDownLatch mConfigLatch;
+
+ @Override
+ public void onConfigurationChanged(Configuration config) {
+ super.onConfigurationChanged(config);
+ mConfig.setTo(config);
+ ++mNumOfConfigChanges;
+
+ if (mConfigLatch != null) {
+ if (mTestLatch != null) {
+ mTestLatch.countDown();
+ }
+ try {
+ mConfigLatch.await();
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
}
}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index f8633565cc86..4e405cab19b8 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -27,6 +27,7 @@ import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import android.platform.test.annotations.Presubmit;
+import android.provider.Settings.Global;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -550,7 +551,12 @@ public class SettingsBackupTest {
Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
Settings.Global.BACKUP_MULTI_USER_ENABLED,
Settings.Global.ISOLATED_STORAGE_LOCAL,
- Settings.Global.ISOLATED_STORAGE_REMOTE);
+ Settings.Global.ISOLATED_STORAGE_REMOTE,
+ Settings.Global.APPOP_HISTORY_PARAMETERS,
+ Settings.Global.APPOP_HISTORY_MODE,
+ Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER,
+ Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS);
+
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index b9b1cdc6cbc9..b4f19c99c6fe 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -39,7 +39,7 @@ protected:
virtual ~WeakLooperCallback() { }
public:
- WeakLooperCallback(const wp<LooperCallback>& callback) :
+ explicit WeakLooperCallback(const wp<LooperCallback>& callback) :
mCallback(callback) {
}
diff --git a/libs/protoutil/include/android/util/EncodedBuffer.h b/libs/protoutil/include/android/util/EncodedBuffer.h
index c84de4c1c083..0b7f6e46be65 100644
--- a/libs/protoutil/include/android/util/EncodedBuffer.h
+++ b/libs/protoutil/include/android/util/EncodedBuffer.h
@@ -38,13 +38,13 @@ class EncodedBuffer
{
public:
EncodedBuffer();
- EncodedBuffer(size_t chunkSize);
+ explicit EncodedBuffer(size_t chunkSize);
~EncodedBuffer();
class Pointer {
public:
Pointer();
- Pointer(size_t chunkSize);
+ explicit Pointer(size_t chunkSize);
size_t pos() const;
size_t index() const;
@@ -161,7 +161,7 @@ public:
friend class iterator;
class iterator {
public:
- iterator(const EncodedBuffer& buffer);
+ explicit iterator(const EncodedBuffer& buffer);
/**
* Returns the number of bytes written in the buffer
diff --git a/packages/AppPredictionLib/Android.bp b/packages/AppPredictionLib/Android.bp
new file mode 100644
index 000000000000..e0f4dedc9368
--- /dev/null
+++ b/packages/AppPredictionLib/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_library {
+ name: "app_prediction",
+
+ sdk_version: "system_current",
+ min_sdk_version: "system_current",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+}
diff --git a/packages/AppPredictionLib/AndroidManifest.xml b/packages/AppPredictionLib/AndroidManifest.xml
new file mode 100644
index 000000000000..b99278881281
--- /dev/null
+++ b/packages/AppPredictionLib/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.app.prediction">
+</manifest>
diff --git a/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java b/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java
new file mode 100644
index 000000000000..0993c9a13f99
--- /dev/null
+++ b/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.prediction;
+
+/**
+ * Constants to be used with {@link android.app.prediction.AppPredictor}.
+ */
+public class Constants {
+
+ /**
+ * UI surface for predictions displayed on the user's home screen
+ */
+ public static final String UI_SURFACE_HOME = "home";
+
+ /**
+ * UI surface for predictions displayed on the recents/task switcher view
+ */
+ public static final String UI_SURFACE_RECENTS = "recents";
+
+ /**
+ * UI surface for predictions displayed on the share sheet.
+ */
+ public static final String UI_SURFACE_SHARE = "share";
+
+ /**
+ * Location constant when an app target or shortcut is started from the apps list
+ */
+ public static final String LAUNCH_LOCATION_APPS_LIST = "apps_list";
+
+ /**
+ * Location constant when an app target or shortcut is started from the user's home screen
+ */
+ public static final String LAUNCH_LOCATION_APPS_HOME = "home";
+
+ /**
+ * Location constant when an app target or shortcut is started from task switcher
+ */
+ public static final String LAUNCH_LOCATION_APPS_RECENTS = "recents";
+
+ /**
+ * Location constant when an app target or shortcut is started in the share sheet while it is
+ * in collapsed state (showing a limited set of result).
+ */
+ public static final String LAUNCH_LOCATION_APPS_SHARE_COLLAPSED = "share_collapsed";
+
+ /**
+ * Location constant when an app target or shortcut is started in the share sheet while it is
+ * in expended state and showing all the results.
+ */
+ public static final String LAUNCH_LOCATION_APPS_SHARE_EXPANDED = "shared_expanded";
+
+ /**
+ * Location constant when an app target or shortcut is started in the share sheet when the
+ * target is displayed as a placeholder for an deprecated object.
+ */
+ public static final String LAUNCH_LOCATION_APPS_SHARE_LEGACY = "share_legacy";
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 419273ee28bb..bcf37ffef262 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1557,6 +1557,10 @@ class SettingsProtoDumpUtil {
Settings.Global.ZRAM_ENABLED,
GlobalSettingsProto.ZRAM_ENABLED);
+ dumpSetting(s, p,
+ Global.APP_OPS_CONSTANTS,
+ GlobalSettingsProto.APP_OPS_CONSTANTS);
+
p.end(token);
// Please insert new settings using the same order as in GlobalSettingsProto.
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bff2c844bb48..fa95bf2ee302 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -134,6 +134,8 @@
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
<uses-permission android:name="android.permission.MANAGE_AUTO_FILL" />
<uses-permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" />
+ <uses-permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" />
+ <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" />
<uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.SET_TIME" />
@@ -162,6 +164,8 @@
<uses-permission android:name="android.permission.SUSPEND_APPS" />
<uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+ <uses-permission android:name="android.permission.MANAGE_APPOPS" />
+
<application android:label="@string/app_label"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 1c8a672faf87..17cc1d57582c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -55,6 +55,10 @@ public class KeyguardClockSwitch extends RelativeLayout {
* Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
*/
private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin);
+ /**
+ * Maintain state so that a newly connected plugin can be initialized.
+ */
+ private float mDarkAmount;
private final StatusBarStateController.StateListener mStateListener =
new StatusBarStateController.StateListener() {
@@ -147,6 +151,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
mClockPlugin = plugin;
mClockPlugin.setStyle(getPaint().getStyle());
mClockPlugin.setTextColor(getCurrentTextColor());
+ mClockPlugin.setDarkAmount(mDarkAmount);
}
/**
@@ -208,6 +213,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
* @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
*/
public void setDarkAmount(float darkAmount) {
+ mDarkAmount = darkAmount;
if (mClockPlugin != null) {
mClockPlugin.setDarkAmount(darkAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 6864ea185834..8e273efb6171 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -40,7 +40,6 @@ import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
-import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -56,7 +55,6 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.Utils.DisableStateTracker;
@@ -276,9 +274,6 @@ public class BatteryMeterView extends LinearLayout implements
public void onTuningChanged(String key, String newValue) {
if (StatusBarIconController.ICON_BLACKLIST.equals(key)) {
ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue);
- boolean hidden = icons.contains(mSlotBattery);
- Dependency.get(IconLogger.class).onIconVisibility(mSlotBattery, !hidden);
- setVisibility(hidden ? View.GONE : View.VISIBLE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 9d810645f6a2..ec6ecc64d07e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -86,7 +86,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -217,7 +216,6 @@ public class Dependency extends SystemUI {
@Inject Lazy<LeakDetector> mLeakDetector;
@Inject Lazy<LeakReporter> mLeakReporter;
@Inject Lazy<GarbageMonitor> mGarbageMonitor;
- @Inject Lazy<IconLogger> mIconLogger;
@Inject Lazy<TunerService> mTunerService;
@Inject Lazy<StatusBarWindowController> mStatusBarWindowController;
@Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher;
@@ -392,8 +390,6 @@ public class Dependency extends SystemUI {
mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
- mProviders.put(IconLogger.class, mIconLogger::get);
-
mProviders.put(LightBarController.class, mLightBarController::get);
mProviders.put(IWindowManager.class, mIWindowManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
index f324a05bafff..ce9c637cebf6 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
@@ -46,8 +46,6 @@ import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.IconLogger;
-import com.android.systemui.statusbar.policy.IconLoggerImpl;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.policy.LocationController;
@@ -133,11 +131,6 @@ public abstract class DependencyBinder {
/**
*/
@Binds
- public abstract IconLogger provideIconLogger(IconLoggerImpl loggerImpl);
-
- /**
- */
- @Binds
public abstract CastController provideCastController(CastControllerImpl controllerImpl);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index db7589d0f333..f846036248d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -36,7 +36,6 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconStat
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -61,7 +60,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
private final ArraySet<String> mIconBlacklist = new ArraySet<>();
- private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
// Points to light or dark context depending on the... context?
private Context mContext;
@@ -147,7 +145,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
int viewIndex = getViewIndex(index, holder.getTag());
boolean blocked = mIconBlacklist.contains(slot);
- mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible());
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder));
}
@@ -281,8 +278,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
return;
}
- mIconLogger.onIconHidden(slotName);
-
int slotIndex = getSlotIndex(slotName);
List<StatusBarIconHolder> iconsToRemove = slot.getHolderListInViewOrder();
for (StatusBarIconHolder holder : iconsToRemove) {
@@ -297,7 +292,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
if (getIcon(index, tag) == null) {
return;
}
- mIconLogger.onIconHidden(getSlotName(index));
super.removeIcon(index, tag);
int viewIndex = getViewIndex(index, 0);
mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
@@ -305,7 +299,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
private void handleSet(int index, StatusBarIconHolder holder) {
int viewIndex = getViewIndex(index, holder.getTag());
- mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible());
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index de7ef3ba645d..4299af7daf66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -274,7 +274,6 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C
private void updateClockVisibility() {
boolean visible = shouldBeVisible();
- Dependency.get(IconLogger.class).onIconVisibility("clock", visible);
int visibility = visible ? View.VISIBLE : View.GONE;
super.setVisibility(visibility);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java
deleted file mode 100644
index 710e1df61404..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java
+++ /dev/null
@@ -1,29 +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.systemui.statusbar.policy;
-
-public interface IconLogger {
-
- void onIconShown(String tag);
- void onIconHidden(String tag);
-
- default void onIconVisibility(String tag, boolean visible) {
- if (visible) {
- onIconShown(tag);
- } else {
- onIconHidden(tag);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java
deleted file mode 100644
index ba6369e2c90e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java
+++ /dev/null
@@ -1,115 +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.systemui.statusbar.policy;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_NUM_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.STATUS_BAR_ICONS_CHANGED;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
-
-import android.content.Context;
-import android.metrics.LogMaker;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.ArraySet;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.logging.MetricsLogger;
-
-import java.util.Arrays;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-public class IconLoggerImpl implements IconLogger {
-
- // Minimum ms between log statements.
- // NonFinalForTesting
- @VisibleForTesting
- protected static long MIN_LOG_INTERVAL = 1000;
-
- private final Context mContext;
- private final Handler mHandler;
- private final MetricsLogger mLogger;
- private final ArraySet<String> mIcons = new ArraySet<>();
- private final List<String> mIconIndex;
- private long mLastLog = System.currentTimeMillis();
-
- @Inject
- public IconLoggerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper,
- MetricsLogger logger) {
- mContext = context;
- mHandler = new Handler(bgLooper);
- mLogger = logger;
- String[] icons = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_statusBarIcons);
- mIconIndex = Arrays.asList(icons);
- doLog();
- }
-
- @Override
- public void onIconShown(String tag) {
- synchronized (mIcons) {
- if (mIcons.contains(tag)) return;
- mIcons.add(tag);
- }
- if (!mHandler.hasCallbacks(mLog)) {
- mHandler.postDelayed(mLog, MIN_LOG_INTERVAL);
- }
- }
-
- @Override
- public void onIconHidden(String tag) {
- synchronized (mIcons) {
- if (!mIcons.contains(tag)) return;
- mIcons.remove(tag);
- }
- if (!mHandler.hasCallbacks(mLog)) {
- mHandler.postDelayed(mLog, MIN_LOG_INTERVAL);
- }
- }
-
- private void doLog() {
- long time = System.currentTimeMillis();
- long timeSinceLastLog = time - mLastLog;
- mLastLog = time;
-
- ArraySet<String> icons;
- synchronized (mIcons) {
- icons = new ArraySet<>(mIcons);
- }
- mLogger.write(new LogMaker(STATUS_BAR_ICONS_CHANGED)
- .setType(TYPE_ACTION)
- .setLatency(timeSinceLastLog)
- .addTaggedData(FIELD_NUM_STATUS_ICONS, icons.size())
- .addTaggedData(FIELD_STATUS_ICONS, getBitField(icons)));
- }
-
- private int getBitField(ArraySet<String> icons) {
- int iconsVisible = 0;
- for (String icon : icons) {
- int index = mIconIndex.indexOf(icon);
- if (index >= 0) {
- iconsVisible |= (1 << index);
- }
- }
- return iconsVisible;
- }
-
- private final Runnable mLog = this::doLog;
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 8e02f578f6fd..fbc1c20755a1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -132,6 +132,17 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
}
@Test
+ public void onPluginConnected_darkAmountInitialized() {
+ // GIVEN that the dark amount has already been set
+ mKeyguardClockSwitch.setDarkAmount(0.5f);
+ // WHEN a plugin is connected
+ ClockPlugin plugin = mock(ClockPlugin.class);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ // THEN dark amount should be initalized on the plugin.
+ verify(plugin).setDarkAmount(0.5f);
+ }
+
+ @Test
public void onPluginDisconnected_showDefaultClock() {
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java
deleted file mode 100644
index 5c347301bd8c..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java
+++ /dev/null
@@ -1,177 +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.systemui.statusbar.policy;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_NUM_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
- .RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS;
-
-import static org.junit.Assert.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static java.lang.Thread.sleep;
-
-import android.metrics.LogMaker;
-import android.support.test.filters.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.MessageHandler;
-import android.testing.TestableLooper.RunWithLooper;
-import android.util.Log;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatcher;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class IconLoggerImplTest extends SysuiTestCase {
-
- private MetricsLogger mMetricsLogger;
- private IconLoggerImpl mIconLogger;
- private TestableLooper mTestableLooper;
- private MessageHandler mMessageHandler;
-
- @Before
- public void setup() {
- IconLoggerImpl.MIN_LOG_INTERVAL = 5; // Low interval for testing
- mMetricsLogger = mock(MetricsLogger.class);
- mTestableLooper = TestableLooper.get(this);
- mMessageHandler = mock(MessageHandler.class);
- mTestableLooper.setMessageHandler(mMessageHandler);
- String[] iconArray = new String[] {
- "test_icon_1",
- "test_icon_2",
- };
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.array.config_statusBarIcons, iconArray);
- mIconLogger = new IconLoggerImpl(mContext, mTestableLooper.getLooper(), mMetricsLogger);
- when(mMessageHandler.onMessageHandled(any())).thenReturn(true);
- clearInvocations(mMetricsLogger);
- }
-
- @Test
- public void testIconShown() throws InterruptedException {
- // Should only get one message, for the same icon shown twice.
- mIconLogger.onIconShown("test_icon_2");
- mIconLogger.onIconShown("test_icon_2");
-
- // There should be some delay before execute.
- mTestableLooper.processAllMessages();
- verify(mMessageHandler, never()).onMessageHandled(any());
-
- sleep(10);
- mTestableLooper.processAllMessages();
- verify(mMessageHandler, times(1)).onMessageHandled(any());
- }
-
- @Test
- public void testIconHidden() throws InterruptedException {
- // Add the icon so that it can be removed.
- mIconLogger.onIconShown("test_icon_2");
- sleep(10);
- mTestableLooper.processAllMessages();
- clearInvocations(mMessageHandler);
-
- // Should only get one message, for the same icon shown twice.
- mIconLogger.onIconHidden("test_icon_2");
- mIconLogger.onIconHidden("test_icon_2");
-
- // There should be some delay before execute.
- mTestableLooper.processAllMessages();
- verify(mMessageHandler, never()).onMessageHandled(any());
-
- sleep(10);
- mTestableLooper.processAllMessages();
- verify(mMessageHandler, times(1)).onMessageHandled(any());
- }
-
- @Test
- public void testLog() throws InterruptedException {
- mIconLogger.onIconShown("test_icon_2");
- sleep(10);
- mTestableLooper.processAllMessages();
-
- verify(mMetricsLogger).write(argThat(maker -> {
- if (IconLoggerImpl.MIN_LOG_INTERVAL >
- (long) maker.getTaggedData(RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS)) {
- Log.e("IconLoggerImplTest", "Invalid latency "
- + maker.getTaggedData(RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS));
- return false;
- }
- if (1 != (int) maker.getTaggedData(FIELD_NUM_STATUS_ICONS)) {
- Log.e("IconLoggerImplTest", "Invalid icon count "
- + maker.getTaggedData(FIELD_NUM_STATUS_ICONS));
- return false;
- }
- return true;
- }));
- }
-
- @Test
- public void testBitField() throws InterruptedException {
- mIconLogger.onIconShown("test_icon_2");
- sleep(10);
- mTestableLooper.processAllMessages();
-
- verify(mMetricsLogger).write(argThat(maker -> {
- if ((1 << 1) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) {
- Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString(
- (Integer) maker.getTaggedData(FIELD_NUM_STATUS_ICONS)));
- return false;
- }
- return true;
- }));
-
- mIconLogger.onIconShown("test_icon_1");
- sleep(10);
- mTestableLooper.processAllMessages();
-
- verify(mMetricsLogger).write(argThat(maker -> {
- if ((1 << 1 | 1 << 0) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) {
- Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString(
- (Integer) maker.getTaggedData(FIELD_NUM_STATUS_ICONS)));
- return false;
- }
- return true;
- }));
-
- mIconLogger.onIconHidden("test_icon_2");
- sleep(10);
- mTestableLooper.processAllMessages();
-
- verify(mMetricsLogger).write(argThat(maker -> {
- if ((1 << 0) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) {
- Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString(
- (Integer) maker.getTaggedData(FIELD_STATUS_ICONS)));
- return false;
- }
- return true;
- }));
- }
-}
diff --git a/services/Android.bp b/services/Android.bp
index 0abe53daf818..31385edd015f 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -17,11 +17,13 @@ java_library {
static_libs: [
"services.core",
"services.accessibility",
+ "services.appprediction",
"services.appwidget",
"services.autofill",
"services.backup",
"services.companion",
"services.contentcapture",
+ "services.contentsuggestions",
"services.coverage",
"services.devicepolicy",
"services.ipmemorystore",
diff --git a/services/appprediction/Android.bp b/services/appprediction/Android.bp
new file mode 100644
index 000000000000..a7be58783aab
--- /dev/null
+++ b/services/appprediction/Android.bp
@@ -0,0 +1,5 @@
+java_library_static {
+ name: "services.appprediction",
+ srcs: ["java/**/*.java"],
+ libs: ["services.core"],
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
new file mode 100644
index 000000000000..833eaa06d759
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appprediction;
+
+import static android.Manifest.permission.MANAGE_APP_PREDICTIONS;
+import static android.content.Context.APP_PREDICTION_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.app.prediction.IPredictionManager;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+
+import java.io.FileDescriptor;
+import java.util.function.Consumer;
+
+/**
+ * A service used to predict app and shortcut usage.
+ *
+ * <p>The data collected by this service can be analyzed and combined with other sources to provide
+ * predictions in different areas of the system such as Launcher and Share sheet.
+ */
+public class AppPredictionManagerService extends
+ AbstractMasterSystemService<AppPredictionManagerService, AppPredictionPerUserService> {
+
+ private static final String TAG = AppPredictionManagerService.class.getSimpleName();
+
+ private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+ public AppPredictionManagerService(Context context) {
+ super(context, new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultAppPredictionService), null);
+ }
+
+ @Override
+ protected AppPredictionPerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
+ return new AppPredictionPerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(APP_PREDICTION_SERVICE, new PredictionManagerServiceStub());
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_APP_PREDICTIONS, TAG);
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMP_SERVICE_DURATION_MS;
+ }
+
+ private class PredictionManagerServiceStub extends IPredictionManager.Stub {
+
+ @Override
+ public void createPredictionSession(@NonNull AppPredictionContext context,
+ @NonNull AppPredictionSessionId sessionId) {
+ runForUserLocked((service) ->
+ service.onCreatePredictionSessionLocked(context, sessionId));
+ }
+
+ @Override
+ public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
+ @NonNull AppTargetEvent event) {
+ runForUserLocked((service) -> service.notifyAppTargetEventLocked(sessionId, event));
+ }
+
+ @Override
+ public void notifyLocationShown(@NonNull AppPredictionSessionId sessionId,
+ @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
+ runForUserLocked((service) ->
+ service.notifyLocationShownLocked(sessionId, launchLocation, targetIds));
+ }
+
+ @Override
+ public void sortAppTargets(@NonNull AppPredictionSessionId sessionId,
+ @NonNull ParceledListSlice targets,
+ IPredictionCallback callback) {
+ runForUserLocked((service) ->
+ service.sortAppTargetsLocked(sessionId, targets, callback));
+ }
+
+ @Override
+ public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ runForUserLocked((service) ->
+ service.registerPredictionUpdatesLocked(sessionId, callback));
+ }
+
+ public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ runForUserLocked((service) ->
+ service.unregisterPredictionUpdatesLocked(sessionId, callback));
+ }
+
+ @Override
+ public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
+ runForUserLocked((service) -> service.requestPredictionUpdateLocked(sessionId));
+ }
+
+ @Override
+ public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
+ runForUserLocked((service) -> service.onDestroyPredictionSessionLocked(sessionId));
+ }
+
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ new AppPredictionManagerServiceShellCommand(AppPredictionManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ private void runForUserLocked(@NonNull Consumer<AppPredictionPerUserService> c) {
+ final int userId = UserHandle.getCallingUserId();
+ // TODO(b/111701043): Determine what permission model we want for this
+ long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ final AppPredictionPerUserService service = getServiceForUserLocked(userId);
+ c.accept(service);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java
new file mode 100644
index 000000000000..ed7cc67aa46f
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appprediction;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the AppPredictionManagerService.
+ */
+public class AppPredictionManagerServiceShellCommand extends ShellCommand {
+
+ private static final String TAG =
+ AppPredictionManagerServiceShellCommand.class.getSimpleName();
+
+ private final AppPredictionManagerService mService;
+
+ public AppPredictionManagerServiceShellCommand(@NonNull AppPredictionManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "set": {
+ final String what = getNextArgRequired();
+ switch (what) {
+ case "temporary-service": {
+ final int userId = Integer.parseInt(getNextArgRequired());
+ String serviceName = getNextArg();
+ if (serviceName == null) {
+ mService.resetTemporaryService(userId);
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ pw.println("AppPredictionService temporarily set to " + serviceName
+ + " for " + duration + "ms");
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter()) {
+ pw.println("AppPredictionManagerService commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implemtation.");
+ pw.println(" To reset, call with just the USER_ID argument.");
+ pw.println("");
+ }
+ }
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
new file mode 100644
index 000000000000..24d592c38afb
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appprediction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.service.appprediction.AppPredictionService;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link AppPredictionManagerService}.
+ */
+public class AppPredictionPerUserService extends
+ AbstractPerUserSystemService<AppPredictionPerUserService, AppPredictionManagerService>
+ implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks {
+
+ private static final String TAG = AppPredictionPerUserService.class.getSimpleName();
+
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteAppPredictionService mRemoteService;
+
+ protected AppPredictionPerUserService(AppPredictionManagerService master,
+ Object lock, int userId) {
+ super(master, lock, userId);
+ }
+
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws NameNotFoundException {
+
+ ServiceInfo si;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new NameNotFoundException("Could not get service for " + serviceComponent);
+ }
+ // TODO(b/111701043): must check that either the service is from a system component,
+ // or it matches a service set by shell cmd (so it can be used on CTS tests and when
+ // OEMs are implementing the real service and also verify the proper permissions
+ return si;
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected boolean updateLocked(boolean disabled) {
+ final boolean enabledChanged = super.updateLocked(disabled);
+ if (enabledChanged) {
+ if (!isEnabledLocked()) {
+ // Clear the remote service for the next call
+ mRemoteService = null;
+ }
+ }
+ return enabledChanged;
+ }
+
+ /**
+ * Notifies the service of a new prediction session.
+ */
+ @GuardedBy("mLock")
+ public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
+ @NonNull AppPredictionSessionId sessionId) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.onCreatePredictionSession(context, sessionId);
+ }
+ }
+
+ /**
+ * Records an app target event to the service.
+ */
+ @GuardedBy("mLock")
+ public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId,
+ @NonNull AppTargetEvent event) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.notifyAppTargetEvent(sessionId, event);
+ }
+ }
+
+ /**
+ * Records when a launch location is shown.
+ */
+ @GuardedBy("mLock")
+ public void notifyLocationShownLocked(@NonNull AppPredictionSessionId sessionId,
+ @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.notifyLocationShown(sessionId, launchLocation, targetIds);
+ }
+ }
+
+ /**
+ * Requests the service to sort a list of apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId,
+ @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.sortAppTargets(sessionId, targets, callback);
+ }
+ }
+
+ /**
+ * Registers a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.registerPredictionUpdates(sessionId, callback);
+ }
+ }
+
+ /**
+ * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.unregisterPredictionUpdates(sessionId, callback);
+ }
+ }
+
+ /**
+ * Requests a new set of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.requestPredictionUpdate(sessionId);
+ }
+ }
+
+ /**
+ * Notifies the service of the end of an existing prediction session.
+ */
+ @GuardedBy("mLock")
+ public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.onDestroyPredictionSession(sessionId);
+ }
+ }
+
+ @Override
+ public void onFailureOrTimeout(boolean timedOut) {
+ if (isDebug()) {
+ Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
+ }
+
+ // Do nothing, we are just proxying to the prediction service
+ }
+
+ @Override
+ public void onServiceDied(RemoteAppPredictionService service) {
+ if (isDebug()) {
+ Slog.d(TAG, "onServiceDied():");
+ }
+
+ // Do nothing, we are just proxying to the prediction service
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteAppPredictionService getRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "getRemoteServiceLocked(): not set");
+ }
+ return null;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+ mRemoteService = new RemoteAppPredictionService(getContext(),
+ AppPredictionService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
+ mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
new file mode 100644
index 000000000000..45ea86f9e74e
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appprediction;
+
+import android.annotation.NonNull;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.IBinder;
+import android.service.appprediction.IPredictionService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the {@link android.service.appprediction.AppPredictionService} implemention in another
+ * process.
+ */
+public class RemoteAppPredictionService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteAppPredictionService,
+ IPredictionService> {
+
+ private static final String TAG = "RemoteAppPredictionService";
+
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+ public RemoteAppPredictionService(Context context, String serviceInterface,
+ ComponentName componentName, int userId,
+ RemoteAppPredictionServiceCallbacks callback, boolean bindInstantServiceAllowed,
+ boolean verbose) {
+ super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed,
+ verbose, /* initialCapacity= */ 1);
+ }
+
+ @Override
+ protected IPredictionService getServiceInterface(IBinder service) {
+ return IPredictionService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getTimeoutIdleBindMillis() {
+ return PERMANENT_BOUND_TIMEOUT_MS;
+ }
+
+ @Override
+ protected long getRemoteRequestMillis() {
+ return TIMEOUT_REMOTE_REQUEST_MILLIS;
+ }
+
+ /**
+ * Notifies the service of a new prediction session.
+ */
+ public void onCreatePredictionSession(@NonNull AppPredictionContext context,
+ @NonNull AppPredictionSessionId sessionId) {
+ scheduleAsyncRequest((s) -> s.onCreatePredictionSession(context, sessionId));
+ }
+
+ /**
+ * Records an app target event to the service.
+ */
+ public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
+ @NonNull AppTargetEvent event) {
+ scheduleAsyncRequest((s) -> s.notifyAppTargetEvent(sessionId, event));
+ }
+
+ /**
+ * Records when a launch location is shown.
+ */
+ public void notifyLocationShown(@NonNull AppPredictionSessionId sessionId,
+ @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
+ scheduleAsyncRequest((s) -> s.notifyLocationShown(sessionId, launchLocation, targetIds));
+ }
+
+ /**
+ * Requests the service to sort a list of apps or shortcuts.
+ */
+ public void sortAppTargets(@NonNull AppPredictionSessionId sessionId,
+ @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
+ scheduleAsyncRequest((s) -> s.sortAppTargets(sessionId, targets, callback));
+ }
+
+
+ /**
+ * Registers a callback for continuous updates of predicted apps or shortcuts.
+ */
+ public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ scheduleAsyncRequest((s) -> s.registerPredictionUpdates(sessionId, callback));
+ }
+
+ /**
+ * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+ */
+ public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ scheduleAsyncRequest((s) -> s.unregisterPredictionUpdates(sessionId, callback));
+ }
+
+ /**
+ * Requests a new set of predicted apps or shortcuts.
+ */
+ public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
+ scheduleAsyncRequest((s) -> s.requestPredictionUpdate(sessionId));
+ }
+
+ /**
+ * Notifies the service of the end of an existing prediction session.
+ */
+ public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
+ scheduleAsyncRequest((s) -> s.onDestroyPredictionSession(sessionId));
+ }
+
+ /**
+ * Failure callback
+ */
+ public interface RemoteAppPredictionServiceCallbacks
+ extends VultureCallback<RemoteAppPredictionService> {
+
+ /**
+ * Notifies a the failure or timeout of a remote call.
+ */
+ void onFailureOrTimeout(boolean timedOut);
+ }
+}
diff --git a/services/contentsuggestions/Android.bp b/services/contentsuggestions/Android.bp
new file mode 100644
index 000000000000..fc09d2e5196a
--- /dev/null
+++ b/services/contentsuggestions/Android.bp
@@ -0,0 +1,5 @@
+java_library_static {
+ name: "services.contentsuggestions",
+ srcs: ["java/**/*.java"],
+ libs: ["services.core"],
+} \ No newline at end of file
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
new file mode 100644
index 000000000000..58dbea469b9c
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.contentsuggestions;
+
+import static android.Manifest.permission.MANAGE_CONTENT_SUGGESTIONS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.IContentSuggestionsManager;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+
+/**
+ * The system service for providing recents / overview with content suggestion selections and
+ * classifications.
+ *
+ * <p>Calls are received here from
+ * {@link android.app.contentsuggestions.ContentSuggestionsManager} then delegated to
+ * a per user version of the service. From there they are routed to the remote actual implementation
+ * that provides the suggestion selections and classifications.
+ */
+public class ContentSuggestionsManagerService extends
+ AbstractMasterSystemService<
+ ContentSuggestionsManagerService, ContentSuggestionsPerUserService> {
+
+ private static final String TAG = ContentSuggestionsManagerService.class.getSimpleName();
+ private static final boolean VERBOSE = false; // TODO: make dynamic
+
+ private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+ private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ public ContentSuggestionsManagerService(Context context) {
+ super(context, new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultContentSuggestionsService), null);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @Override
+ protected ContentSuggestionsPerUserService newServiceLocked(int resolvedUserId,
+ boolean disabled) {
+ return new ContentSuggestionsPerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(
+ Context.CONTENT_SUGGESTIONS_SERVICE, new ContentSuggestionsManagerStub());
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_CONTENT_SUGGESTIONS, TAG);
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMP_SERVICE_DURATION_MS;
+ }
+
+ private boolean isCallerRecents(int userId) {
+ if (mServiceNameResolver.isTemporary(userId)) {
+ // If a temporary service is set then skip the recents check
+ return true;
+ }
+ return mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid());
+ }
+
+ private void enforceCallerIsRecents(int userId, String func) {
+ if (isCallerRecents(userId)) {
+ return;
+ }
+
+ String msg = "Permission Denial: " + func + " from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " expected caller is recents";
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ private class ContentSuggestionsManagerStub extends IContentSuggestionsManager.Stub {
+ @Override
+ public void provideContextImage(int taskId, @NonNull Bundle imageContextRequestExtras) {
+ if (imageContextRequestExtras == null) {
+ throw new IllegalArgumentException("Expected non-null imageContextRequestExtras");
+ }
+
+ final int userId = UserHandle.getCallingUserId();
+ enforceCallerIsRecents(userId, "provideContextImage");
+
+ synchronized (mLock) {
+ final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.provideContextImageLocked(taskId, imageContextRequestExtras);
+ } else {
+ if (VERBOSE) {
+ Slog.v(TAG, "provideContextImageLocked: no service for " + userId);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void suggestContentSelections(
+ @NonNull SelectionsRequest selectionsRequest,
+ @NonNull ISelectionsCallback selectionsCallback) {
+ final int userId = UserHandle.getCallingUserId();
+ enforceCallerIsRecents(userId, "suggestContentSelections");
+
+ synchronized (mLock) {
+ final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.suggestContentSelectionsLocked(selectionsRequest, selectionsCallback);
+ } else {
+ if (VERBOSE) {
+ Slog.v(TAG, "suggestContentSelectionsLocked: no service for " + userId);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void classifyContentSelections(
+ @NonNull ClassificationsRequest classificationsRequest,
+ @NonNull IClassificationsCallback callback) {
+ final int userId = UserHandle.getCallingUserId();
+ enforceCallerIsRecents(userId, "classifyContentSelections");
+
+ synchronized (mLock) {
+ final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.classifyContentSelectionsLocked(classificationsRequest, callback);
+ } else {
+ if (VERBOSE) {
+ Slog.v(TAG, "classifyContentSelectionsLocked: no service for " + userId);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void notifyInteraction(@NonNull String requestId, @NonNull Bundle bundle) {
+ final int userId = UserHandle.getCallingUserId();
+ enforceCallerIsRecents(userId, "notifyInteraction");
+
+ synchronized (mLock) {
+ final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.notifyInteractionLocked(requestId, bundle);
+ } else {
+ if (VERBOSE) {
+ Slog.v(TAG, "reportInteractionLocked: no service for " + userId);
+ }
+ }
+ }
+ }
+
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ // Ensure that the caller is the shell process
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != android.os.Process.SHELL_UID
+ && callingUid != android.os.Process.ROOT_UID) {
+ Slog.e(TAG, "Expected shell caller");
+ return;
+ }
+ new ContentSuggestionsManagerServiceShellCommand(ContentSuggestionsManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+ }
+}
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java
new file mode 100644
index 000000000000..e34f1eadcd02
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the ContentSuggestionsManagerService.
+ */
+public class ContentSuggestionsManagerServiceShellCommand extends ShellCommand {
+
+ private static final String TAG =
+ ContentSuggestionsManagerServiceShellCommand.class.getSimpleName();
+
+ private final ContentSuggestionsManagerService mService;
+
+ public ContentSuggestionsManagerServiceShellCommand(
+ @NonNull ContentSuggestionsManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "set": {
+ final String what = getNextArgRequired();
+ switch (what) {
+ case "temporary-service": {
+ final int userId = Integer.parseInt(getNextArgRequired());
+ String serviceName = getNextArg();
+ if (serviceName == null) {
+ mService.resetTemporaryService(userId);
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ pw.println("ContentSuggestionsService temporarily set to " + serviceName
+ + " for " + duration + "ms");
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter()) {
+ pw.println("ContentSuggestionsManagerService commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implemtation.");
+ pw.println(" To reset, call with just the USER_ID argument.");
+ pw.println("");
+ }
+ }
+}
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
new file mode 100644
index 000000000000..385bc6cf3932
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+/**
+ * Per user delegate of {@link ContentSuggestionsManagerService}.
+ *
+ * <p>Main job is to forward calls to the remote implementation that can provide suggestion
+ * selections and classifications.
+ */
+public final class ContentSuggestionsPerUserService extends
+ AbstractPerUserSystemService<
+ ContentSuggestionsPerUserService, ContentSuggestionsManagerService> {
+ private static final String TAG = ContentSuggestionsPerUserService.class.getSimpleName();
+
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteContentSuggestionsService mRemoteService;
+
+ @NonNull
+ private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ ContentSuggestionsPerUserService(
+ ContentSuggestionsManagerService master, Object lock, int userId) {
+ super(master, lock, userId);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws PackageManager.NameNotFoundException {
+ ServiceInfo si;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new PackageManager.NameNotFoundException(
+ "Could not get service for " + serviceComponent);
+ }
+ return si;
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected boolean updateLocked(boolean disabled) {
+ final boolean enabledChanged = super.updateLocked(disabled);
+ if (enabledChanged) {
+ if (!isEnabledLocked()) {
+ // Clear the remote service for the next call
+ mRemoteService = null;
+ }
+ }
+ return enabledChanged;
+ }
+
+ @GuardedBy("mLock")
+ void provideContextImageLocked(int taskId, @NonNull Bundle imageContextRequestExtras) {
+ RemoteContentSuggestionsService service = getRemoteServiceLocked();
+ if (service != null) {
+ ActivityManager.TaskSnapshot snapshot =
+ mActivityTaskManagerInternal.getTaskSnapshot(taskId, false);
+ GraphicBuffer snapshotBuffer = null;
+ if (snapshot != null) {
+ snapshotBuffer = snapshot.getSnapshot();
+ }
+
+ service.provideContextImage(taskId, snapshotBuffer, imageContextRequestExtras);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void suggestContentSelectionsLocked(
+ @NonNull SelectionsRequest selectionsRequest,
+ @NonNull ISelectionsCallback selectionsCallback) {
+ RemoteContentSuggestionsService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.suggestContentSelections(selectionsRequest, selectionsCallback);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void classifyContentSelectionsLocked(
+ @NonNull ClassificationsRequest classificationsRequest,
+ @NonNull IClassificationsCallback callback) {
+ RemoteContentSuggestionsService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.classifyContentSelections(classificationsRequest, callback);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void notifyInteractionLocked(@NonNull String requestId, @NonNull Bundle bundle) {
+ RemoteContentSuggestionsService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.notifyInteraction(requestId, bundle);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteContentSuggestionsService getRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "getRemoteServiceLocked(): not set");
+ }
+ return null;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+ mRemoteService = new RemoteContentSuggestionsService(getContext(),
+ serviceComponent, mUserId,
+ new RemoteContentSuggestionsService.Callbacks() {
+ @Override
+ public void onServiceDied(
+ @NonNull RemoteContentSuggestionsService service) {
+ // TODO(b/120865921): properly implement
+ Slog.w(TAG, "remote content suggestions service died");
+ }
+ }, mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+}
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java
new file mode 100644
index 000000000000..bf48d7623255
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.service.contentsuggestions.ContentSuggestionsService;
+import android.service.contentsuggestions.IContentSuggestionsService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+/**
+ * Delegates calls from {@link ContentSuggestionsPerUserService} to the remote actual implementation
+ * of the suggestion selection and classification service.
+ */
+public class RemoteContentSuggestionsService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteContentSuggestionsService,
+ IContentSuggestionsService> {
+
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+ RemoteContentSuggestionsService(Context context, ComponentName serviceName,
+ int userId, Callbacks callbacks,
+ boolean bindInstantServiceAllowed, boolean verbose) {
+ super(context, ContentSuggestionsService.SERVICE_INTERFACE, serviceName, userId, callbacks,
+ bindInstantServiceAllowed, verbose, /* initialCapacity= */ 1);
+ }
+
+ @Override
+ protected IContentSuggestionsService getServiceInterface(IBinder service) {
+ return IContentSuggestionsService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getTimeoutIdleBindMillis() {
+ return PERMANENT_BOUND_TIMEOUT_MS;
+ }
+
+ @Override
+ protected long getRemoteRequestMillis() {
+ return TIMEOUT_REMOTE_REQUEST_MILLIS;
+ }
+
+ void provideContextImage(int taskId, @Nullable GraphicBuffer contextImage,
+ @NonNull Bundle imageContextRequestExtras) {
+ scheduleAsyncRequest((s) -> s.provideContextImage(taskId, contextImage,
+ imageContextRequestExtras));
+ }
+
+ void suggestContentSelections(
+ @NonNull SelectionsRequest selectionsRequest,
+ @NonNull ISelectionsCallback selectionsCallback) {
+ scheduleAsyncRequest(
+ (s) -> s.suggestContentSelections(selectionsRequest, selectionsCallback));
+ }
+
+ void classifyContentSelections(
+ @NonNull ClassificationsRequest classificationsRequest,
+ @NonNull IClassificationsCallback callback) {
+ scheduleAsyncRequest((s) -> s.classifyContentSelections(classificationsRequest, callback));
+ }
+
+ void notifyInteraction(@NonNull String requestId, @NonNull Bundle bundle) {
+ scheduleAsyncRequest((s) -> s.notifyInteraction(requestId, bundle));
+ }
+
+ interface Callbacks
+ extends VultureCallback<RemoteContentSuggestionsService> {
+ // NOTE: so far we don't need to notify the callback implementation
+ // (ContentSuggestionsManager) of the request results (success, timeouts, etc..), so this
+ // callback interface is empty.
+ }
+}
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index ea80ac1aecef..80fda19aa10c 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -16,32 +16,31 @@
package com.android.server;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
+import android.content.Intent;
+import android.content.IntentFilter;
import android.net.Uri;
-import android.util.FastImmutableArraySet;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.FastImmutableArraySet;
import android.util.Log;
+import android.util.LogPrinter;
import android.util.MutableInt;
import android.util.PrintWriterPrinter;
-import android.util.Slog;
-import android.util.LogPrinter;
import android.util.Printer;
-
-import android.content.Intent;
-import android.content.IntentFilter;
+import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FastPrintWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
/**
* {@hide}
*/
@@ -788,6 +787,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
+ filter.hasCategory(Intent.CATEGORY_DEFAULT));
if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
final R oneResult = newResult(filter, match, userId);
+ if (debug) Slog.v(TAG, " Created result: " + oneResult);
if (oneResult != null) {
dest.add(oneResult);
if (debug) {
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index f27d37315ada..8adc416fda95 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
@@ -29,6 +30,7 @@ import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
@@ -46,8 +48,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
/**
* Monitors the health of packages on the system and notifies interested observers when packages
@@ -58,7 +62,7 @@ public class PackageWatchdog {
// Duration to count package failures before it resets to 0
private static final int TRIGGER_DURATION_MS = 60000;
// Number of package failures within the duration above before we notify observers
- private static final int TRIGGER_FAILURE_COUNT = 5;
+ static final int TRIGGER_FAILURE_COUNT = 5;
private static final int DB_VERSION = 1;
private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
private static final String TAG_PACKAGE = "package";
@@ -75,20 +79,13 @@ public class PackageWatchdog {
// Handler to run package cleanup runnables
private final Handler mTimerHandler;
private final Handler mIoHandler;
- // Contains (observer-name -> external-observer-handle) that have been registered during the
- // current boot.
- // It is populated when observers call #registerHealthObserver and it does not survive reboots.
- @GuardedBy("mLock")
- final ArrayMap<String, PackageHealthObserver> mRegisteredObservers = new ArrayMap<>();
- // Contains (observer-name -> internal-observer-handle) that have ever been registered from
+ // Contains (observer-name -> observer-handle) that have ever been registered from
// previous boots. Observers with all packages expired are periodically pruned.
// It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
@GuardedBy("mLock")
- final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
+ private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
// File containing the XML data of monitored packages /data/system/package-watchdog.xml
- private final AtomicFile mPolicyFile =
- new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
- "package-watchdog.xml"));
+ private final AtomicFile mPolicyFile;
// Runnable to prune monitored packages that have expired
private final Runnable mPackageCleanup;
// Last SystemClock#uptimeMillis a package clean up was executed.
@@ -98,14 +95,32 @@ public class PackageWatchdog {
// 0 if mPackageCleanup not running.
private long mDurationAtLastReschedule;
+ // TODO(zezeozue): Remove redundant context param
private PackageWatchdog(Context context) {
mContext = context;
+ mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
+ "package-watchdog.xml"));
mTimerHandler = new Handler(Looper.myLooper());
mIoHandler = BackgroundThread.getHandler();
mPackageCleanup = this::rescheduleCleanup;
loadFromFile();
}
+ /**
+ * Creates a PackageWatchdog for testing that uses the same {@code looper} for all handlers
+ * and creates package-watchdog.xml in an apps data directory.
+ */
+ @VisibleForTesting
+ PackageWatchdog(Context context, Looper looper) {
+ mContext = context;
+ mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
+ mTimerHandler = new Handler(looper);
+ mIoHandler = mTimerHandler;
+ mPackageCleanup = this::rescheduleCleanup;
+ loadFromFile();
+ }
+
+
/** Creates or gets singleton instance of PackageWatchdog. */
public static PackageWatchdog getInstance(Context context) {
synchronized (PackageWatchdog.class) {
@@ -124,7 +139,10 @@ public class PackageWatchdog {
*/
public void registerHealthObserver(PackageHealthObserver observer) {
synchronized (mLock) {
- mRegisteredObservers.put(observer.getName(), observer);
+ ObserverInternal internalObserver = mAllObservers.get(observer.getName());
+ if (internalObserver != null) {
+ internalObserver.mRegisteredObserver = observer;
+ }
if (mDurationAtLastReschedule == 0) {
// Nothing running, schedule
rescheduleCleanup();
@@ -143,7 +161,7 @@ public class PackageWatchdog {
* or {@code durationMs} is less than 1
*/
public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
- int durationMs) {
+ long durationMs) {
if (packageNames.isEmpty() || durationMs < 1) {
throw new IllegalArgumentException("Observation not started, no packages specified"
+ "or invalid duration");
@@ -180,11 +198,32 @@ public class PackageWatchdog {
public void unregisterHealthObserver(PackageHealthObserver observer) {
synchronized (mLock) {
mAllObservers.remove(observer.getName());
- mRegisteredObservers.remove(observer.getName());
}
saveToFileAsync();
}
+ /**
+ * Returns packages observed by {@code observer}
+ *
+ * @return an empty set if {@code observer} has some packages observerd from a previous boot
+ * but has not registered itself in the current boot to receive notifications. Returns null
+ * if there are no active packages monitored from any boot.
+ */
+ @Nullable
+ public Set<String> getPackages(PackageHealthObserver observer) {
+ synchronized (mLock) {
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ if (observer.getName().equals(mAllObservers.keyAt(i))) {
+ if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) {
+ return mAllObservers.valueAt(i).mPackages.keySet();
+ }
+ return Collections.emptySet();
+ }
+ }
+ }
+ return null;
+ }
+
// TODO(zezeozue:) Accept current versionCodes of failing packages?
/**
* Called when a process fails either due to a crash or ANR.
@@ -198,33 +237,35 @@ public class PackageWatchdog {
public void onPackageFailure(String[] packages) {
ArrayMap<String, List<PackageHealthObserver>> packagesToReport = new ArrayMap<>();
synchronized (mLock) {
- if (mRegisteredObservers.isEmpty()) {
+ if (mAllObservers.isEmpty()) {
return;
}
for (int pIndex = 0; pIndex < packages.length; pIndex++) {
+ // Observers interested in receiving packageName failures
+ List<PackageHealthObserver> observersToNotify = new ArrayList<>();
for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
- // Observers interested in receiving packageName failures
- List<PackageHealthObserver> observersToNotify = new ArrayList<>();
- PackageHealthObserver activeObserver =
- mRegisteredObservers.get(mAllObservers.valueAt(oIndex).mName);
- if (activeObserver != null) {
- observersToNotify.add(activeObserver);
- }
-
- // Save interested observers and notify them outside the lock
- if (!observersToNotify.isEmpty()) {
- packagesToReport.put(packages[pIndex], observersToNotify);
+ PackageHealthObserver registeredObserver =
+ mAllObservers.valueAt(oIndex).mRegisteredObserver;
+ if (registeredObserver != null) {
+ observersToNotify.add(registeredObserver);
}
}
+ // Save interested observers and notify them outside the lock
+ if (!observersToNotify.isEmpty()) {
+ packagesToReport.put(packages[pIndex], observersToNotify);
+ }
}
}
// Notify observers
for (int pIndex = 0; pIndex < packagesToReport.size(); pIndex++) {
List<PackageHealthObserver> observers = packagesToReport.valueAt(pIndex);
+ String packageName = packages[pIndex];
for (int oIndex = 0; oIndex < observers.size(); oIndex++) {
- if (observers.get(oIndex).onHealthCheckFailed(packages[pIndex])) {
+ PackageHealthObserver observer = observers.get(oIndex);
+ if (mAllObservers.get(observer.getName()).onPackageFailure(packageName)
+ && observer.onHealthCheckFailed(packageName)) {
// Observer has handled, do not notify others
break;
}
@@ -275,10 +316,12 @@ public class PackageWatchdog {
// O if mPackageCleanup not running
long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0
? 0 : uptimeMs - mUptimeAtLastRescheduleMs;
- // O if mPackageCleanup not running
+ // Less than O if mPackageCleanup unexpectedly didn't run yet even though
+ // and we are past the last duration scheduled to run
long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs;
-
- if (mUptimeAtLastRescheduleMs == 0 || nextDurationToScheduleMs < remainingDurationMs) {
+ if (mUptimeAtLastRescheduleMs == 0
+ || remainingDurationMs <= 0
+ || nextDurationToScheduleMs < remainingDurationMs) {
// First schedule or an earlier reschedule
pruneObservers(elapsedDurationMs);
mTimerHandler.removeCallbacks(mPackageCleanup);
@@ -305,6 +348,7 @@ public class PackageWatchdog {
}
}
Slog.v(TAG, "Earliest package time is " + shortestDurationMs);
+
return shortestDurationMs;
}
@@ -409,6 +453,8 @@ public class PackageWatchdog {
static class ObserverInternal {
public final String mName;
public final ArrayMap<String, MonitoredPackage> mPackages;
+ @Nullable
+ public PackageHealthObserver mRegisteredObserver;
ObserverInternal(String name, List<MonitoredPackage> packages) {
mName = name;
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
new file mode 100644
index 000000000000..93e1dd36797a
--- /dev/null
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index ed39d8302bf7..8ca4193a064f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1654,6 +1654,10 @@ public final class ActiveServices {
}
}
+ if ((flags & Context.BIND_RESTRICT_ASSOCIATIONS) != 0) {
+ mAm.requireAllowedAssociationsLocked(s.appInfo.packageName);
+ }
+
mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
callerApp.getCurProcState(), s.appInfo.uid, s.appInfo.longVersionCode,
s.instanceName, s.processName);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7f67230b7bad..bb239acb5c60 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -329,7 +329,7 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
import com.android.server.AlarmManagerInternal;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.DeviceIdleController;
import com.android.server.DisplayThread;
@@ -2431,6 +2431,20 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
+ * Ensures that the given package name has an explicit set of allowed associations.
+ * If it does not, give it an empty set.
+ */
+ void requireAllowedAssociationsLocked(String packageName) {
+ if (mAllowedAssociations == null) {
+ mAllowedAssociations = new ArrayMap<>(
+ SystemConfig.getInstance().getAllowedAssociations());
+ }
+ if (mAllowedAssociations.get(packageName) == null) {
+ mAllowedAssociations.put(packageName, new ArraySet<>());
+ }
+ }
+
+ /**
* Returns true if the package {@code pkg1} running under user handle {@code uid1} is
* allowed association with the package {@code pkg2} running under user handle {@code uid2}.
* <p> If either of the packages are running as part of the core system, then the
@@ -2438,7 +2452,8 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
boolean validateAssociationAllowedLocked(String pkg1, int uid1, String pkg2, int uid2) {
if (mAllowedAssociations == null) {
- mAllowedAssociations = SystemConfig.getInstance().getAllowedAssociations();
+ mAllowedAssociations = new ArrayMap<>(
+ SystemConfig.getInstance().getAllowedAssociations());
}
// Interactions with the system uid are always allowed, since that is the core system
// that everyone needs to be able to interact with. Also allow reflexive associations
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 466fb4e71496..7ede6dcd41bd 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.appop;
import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
import static android.app.AppOpsManager.UID_STATE_CACHED;
import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
@@ -35,8 +36,7 @@ import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
-import android.app.AppOpsManager.HistoricalOpEntry;
-import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalOps;
import android.app.AppOpsManagerInternal;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.content.BroadcastReceiver;
@@ -48,7 +48,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.media.AudioAttributes;
@@ -59,6 +58,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -95,6 +95,8 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.LocalServices;
+import com.android.server.LockGuard;
import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
@@ -213,6 +215,8 @@ public class AppOpsService extends IAppOpsService.Stub {
@VisibleForTesting
final SparseArray<UidState> mUidStates = new SparseArray<>();
+ private final HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+
long mLastRealtime;
/*
@@ -654,6 +658,7 @@ public class AppOpsService extends IAppOpsService.Stub {
public void systemReady() {
mConstants.startMonitoring(mContext.getContentResolver());
+ mHistoricalRegistry.systemReady(mContext.getContentResolver());
synchronized (this) {
boolean changed = false;
@@ -1012,113 +1017,99 @@ public class AppOpsService extends IAppOpsService.Stub {
}
@Override
- public @Nullable ParceledListSlice getAllHistoricalPackagesOps(@Nullable String[] opNames,
- long beginTimeMillis, long endTimeMillis) {
+ public void getHistoricalOps(int uid, @NonNull String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull RemoteCallback callback) {
+ Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0,
+ "uid must be " + Process.INVALID_UID + " or non negative");
Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis,
"beginTimeMillis must be non negative and lesser than endTimeMillis");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ checkValidOpsOrNull(opNames);
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), "getAllHistoricalPackagesOps");
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
- ArrayList<HistoricalPackageOps> historicalPackageOpsList = null;
+ if (mHistoricalRegistry.getMode() == AppOpsManager.HISTORICAL_MODE_DISABLED) {
+ // TODO (bug:122218838): Remove once the feature fully enabled.
+ getHistoricalPackagesOpsCompat(uid, packageName, opNames, beginTimeMillis,
+ endTimeMillis, callback);
+ } else {
+ // Must not hold the appops lock
+ mHistoricalRegistry.getHistoricalOps(uid, packageName, opNames,
+ beginTimeMillis, endTimeMillis, callback);
+ }
+ }
- final int uidStateCount = mUidStates.size();
- for (int i = 0; i < uidStateCount; i++) {
- final UidState uidState = mUidStates.valueAt(i);
- if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
- continue;
+ private void getHistoricalPackagesOpsCompat(int uid, @NonNull String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull RemoteCallback callback) {
+ synchronized (AppOpsService.this) {
+ final HistoricalOps ops = new HistoricalOps(beginTimeMillis, endTimeMillis);
+ if (opNames == null) {
+ opNames = AppOpsManager.getOpStrs();
}
- final ArrayMap<String, Ops> packages = uidState.pkgOps;
- final int packageCount = packages.size();
- for (int j = 0; j < packageCount; j++) {
- final Ops pkgOps = packages.valueAt(j);
- final AppOpsManager.HistoricalPackageOps historicalPackageOps =
- createHistoricalPackageOps(uidState.uid, pkgOps, opNames,
- beginTimeMillis, endTimeMillis);
- if (historicalPackageOps != null) {
- if (historicalPackageOpsList == null) {
- historicalPackageOpsList = new ArrayList<>();
+ final int uidStateCount = mUidStates.size();
+ for (int uidIdx = 0; uidIdx < uidStateCount; uidIdx++) {
+ final UidState uidState = mUidStates.valueAt(uidIdx);
+ if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()
+ || (uid != Process.INVALID_UID && uid != uidState.uid)) {
+ continue;
+ }
+ final ArrayMap<String, Ops> packages = uidState.pkgOps;
+ final int packageCount = packages.size();
+ for (int pkgIdx = 0; pkgIdx < packageCount; pkgIdx++) {
+ final Ops pkgOps = packages.valueAt(pkgIdx);
+ if (packageName != null && !packageName.equals(pkgOps.packageName)) {
+ continue;
+ }
+ final int opCount = opNames.length;
+ for (int opIdx = 0; opIdx < opCount; opIdx++) {
+ final String opName = opNames[opIdx];
+ if (!ArrayUtils.contains(opNames, opName)) {
+ continue;
+ }
+ final int opCode = AppOpsManager.strOpToOp(opName);
+ final Op op = pkgOps.get(opCode);
+ if (op == null) {
+ continue;
+ }
+ final int stateCount = AppOpsManager._NUM_UID_STATE;
+ for (int stateIdx = 0; stateIdx < stateCount; stateIdx++) {
+ if (op.rejectTime[stateIdx] != 0) {
+ ops.increaseRejectCount(opCode, uidState.uid,
+ pkgOps.packageName, stateIdx, 1);
+ } else if (op.time[stateIdx] != 0) {
+ ops.increaseAccessCount(opCode, uidState.uid,
+ pkgOps.packageName, stateIdx, 1);
+ }
+ }
}
- historicalPackageOpsList.add(historicalPackageOps);
}
}
+ final Bundle payload = new Bundle();
+ payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, ops);
+ callback.sendResult(payload);
}
-
- if (historicalPackageOpsList == null) {
- return null;
- }
-
- return new ParceledListSlice<>(historicalPackageOpsList);
- }
-
- private static @Nullable HistoricalPackageOps createHistoricalPackageOps(int uid,
- @Nullable Ops pkgOps, @Nullable String[] opNames, long beginTimeMillis,
- long endTimeMillis) {
- // TODO: Implement historical data collection
- if (pkgOps == null) {
- return null;
- }
-
- final HistoricalPackageOps historicalPackageOps = new HistoricalPackageOps(uid,
- pkgOps.packageName);
-
- if (opNames == null) {
- opNames = AppOpsManager.getOpStrs();
- }
- for (String opName : opNames) {
- addHistoricOpEntry(AppOpsManager.strOpToOp(opName), pkgOps, historicalPackageOps);
- }
-
- return historicalPackageOps;
}
@Override
- public @Nullable HistoricalPackageOps getHistoricalPackagesOps(int uid,
- @NonNull String packageName, @Nullable String[] opNames,
- long beginTimeMillis, long endTimeMillis) {
- Preconditions.checkNotNull(packageName,
- "packageName cannot be null");
+ public void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull RemoteCallback callback) {
+ Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0,
+ "uid must be " + Process.INVALID_UID + " or non negative");
Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis,
"beginTimeMillis must be non negative and lesser than endTimeMillis");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ checkValidOpsOrNull(opNames);
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalPackagesOps");
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
- final String resolvedPackageName = resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return null;
- }
-
- // TODO: Implement historical data collection
- final Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false /* edit */,
- false /* uidMismatchExpected */);
- return createHistoricalPackageOps(uid, pkgOps, opNames, beginTimeMillis, endTimeMillis);
- }
-
- private static void addHistoricOpEntry(int opCode, @NonNull Ops ops,
- @NonNull HistoricalPackageOps outHistoricalPackageOps) {
- final Op op = ops.get(opCode);
- if (op == null) {
- return;
- }
-
- final HistoricalOpEntry historicalOpEntry = new HistoricalOpEntry(opCode);
-
- // TODO: Keep per UID state duration
- for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) {
- final int acceptCount;
- final int rejectCount;
- if (op.rejectTime[uidState] == 0) {
- acceptCount = 1;
- rejectCount = 0;
- } else {
- acceptCount = 0;
- rejectCount = 1;
- }
- historicalOpEntry.addEntry(uidState, acceptCount, rejectCount, 0);
- }
-
- outHistoricalPackageOps.addEntry(historicalOpEntry);
+ // Must not hold the appops lock
+ mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, opNames,
+ beginTimeMillis, endTimeMillis, callback);
}
@Override
@@ -1884,6 +1875,8 @@ public class AppOpsService extends IAppOpsService.Stub {
+ packageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode);
+ mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+ uidState.state);
return uidMode;
}
} else {
@@ -1895,12 +1888,16 @@ public class AppOpsService extends IAppOpsService.Stub {
+ packageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
scheduleOpNotedIfNeededLocked(op.op, uid, packageName, mode);
+ mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+ uidState.state);
return mode;
}
}
if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
+ " package " + packageName);
op.time[uidState.state] = System.currentTimeMillis();
+ mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
+ uidState.state);
op.rejectTime[uidState.state] = 0;
op.proxyUid = proxyUid;
op.proxyPackageName = proxyPackageName;
@@ -2035,6 +2032,8 @@ public class AppOpsService extends IAppOpsService.Stub {
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
+ mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+ uidState.state);
return uidMode;
}
} else {
@@ -2046,6 +2045,8 @@ public class AppOpsService extends IAppOpsService.Stub {
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
+ mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+ uidState.state);
return mode;
}
}
@@ -2054,6 +2055,8 @@ public class AppOpsService extends IAppOpsService.Stub {
if (op.startNesting == 0) {
op.startRealtime = SystemClock.elapsedRealtime();
op.time[uidState.state] = System.currentTimeMillis();
+ mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
+ uidState.state);
op.rejectTime[uidState.state] = 0;
op.duration = -1;
scheduleOpActiveChangedIfNeededLocked(code, uid, packageName, true);
@@ -2216,6 +2219,8 @@ public class AppOpsService extends IAppOpsService.Stub {
if (op.startNesting <= 1 || finishNested) {
if (op.startNesting == 1 || finishNested) {
op.duration = (int)(SystemClock.elapsedRealtime() - op.startRealtime);
+ mHistoricalRegistry.increaseOpAccessDuration(op.op, op.uid, op.packageName,
+ op.uidState.state, op.duration);
op.time[op.uidState.state] = System.currentTimeMillis();
} else {
Slog.w(TAG, "Finishing op nesting under-run: uid " + op.uid + " pkg "
@@ -2344,7 +2349,7 @@ public class AppOpsService extends IAppOpsService.Stub {
ApplicationInfo appInfo = ActivityThread.getPackageManager()
.getApplicationInfo(packageName,
PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
UserHandle.getUserId(uid));
if (appInfo != null) {
pkgUid = appInfo.uid;
@@ -3427,9 +3432,9 @@ public class AppOpsService extends IAppOpsService.Stub {
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
- int dumpOp = -1;
+ int dumpOp = OP_NONE;
String dumpPackage = null;
- int dumpUid = -1;
+ int dumpUid = Process.INVALID_UID;
int dumpMode = -1;
boolean dumpWatchers = false;
@@ -3748,8 +3753,8 @@ public class AppOpsService extends IAppOpsService.Stub {
}
if (pkgOps != null) {
for (int pkgi = 0;
- (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
- pkgi++) {
+ (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
+ pkgi++) {
Ops ops = pkgOps.valueAt(pkgi);
if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
hasOp = true;
@@ -3973,6 +3978,9 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
}
+
+ // Must not hold the appops lock
+ mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpOp);
}
private static final class Restriction {
@@ -4093,6 +4101,47 @@ public class AppOpsService extends IAppOpsService.Stub {
return false;
}
+ @Override
+ public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+ long baseSnapshotInterval, int compressionStep) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "setHistoryParameters");
+ // Must not hold the appops lock
+ mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+ }
+
+ @Override
+ public void offsetHistory(long offsetMillis) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "offsetHistory");
+ // Must not hold the appops lock
+ mHistoricalRegistry.offsetHistory(offsetMillis);
+ }
+
+ @Override
+ public void addHistoricalOps(HistoricalOps ops) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "addHistoricalOps");
+ // Must not hold the appops lock
+ mHistoricalRegistry.addHistoricalOps(ops);
+ }
+
+ @Override
+ public void resetHistoryParameters() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "resetHistoryParameters");
+ // Must not hold the appops lock
+ mHistoricalRegistry.resetHistoryParameters();
+ }
+
+ @Override
+ public void clearHistory() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "clearHistory");
+ // Must not hold the appops lock
+ mHistoricalRegistry.clearHistory();
+ }
+
private void removeUidsForUserLocked(int userHandle) {
for (int i = mUidStates.size() - 1; i >= 0; --i) {
final int uid = mUidStates.keyAt(i);
@@ -4163,6 +4212,16 @@ public class AppOpsService extends IAppOpsService.Stub {
return packageNames;
}
+ private static void checkValidOpsOrNull(String[] opNames) {
+ if (opNames != null) {
+ for (String opName : opNames) {
+ if (AppOpsManager.strOpToOp(opName) == AppOpsManager.OP_NONE) {
+ throw new IllegalArgumentException("Unknown op: " + opName);
+ }
+ }
+ }
+ }
+
private final class ClientRestrictionState implements DeathRecipient {
private final IBinder token;
SparseArray<boolean[]> perUserRestrictions;
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
new file mode 100644
index 000000000000..8d7811fc8e78
--- /dev/null
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -0,0 +1,1495 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalMode;
+import android.app.AppOpsManager.HistoricalOp;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalUidOps;
+import android.app.AppOpsManager.UidState;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.AtomicDirectory;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.FgThread;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class managers historical app op state. This includes reading, persistence,
+ * accounting, querying.
+ * <p>
+ * The history is kept forever in multiple files. Each file time contains the
+ * relative offset from the current time which time is encoded in the file name.
+ * The files contain historical app op state snapshots which have times that
+ * are relative to the time of the container file.
+ *
+ * The data in the files are stored in a logarithmic fashion where where every
+ * subsequent file would contain data for ten times longer interval with ten
+ * times more time distance between snapshots. Hence, the more time passes
+ * the lesser the fidelity.
+ * <p>
+ * For example, the first file would contain data for 1 days with snapshots
+ * every 0.1 days, the next file would contain data for the period 1 to 10
+ * days with snapshots every 1 days, and so on.
+ * <p>
+ * THREADING AND LOCKING: Reported ops must be processed as quickly as possible.
+ * We keep ops pending to be persisted in memory and write to disk on a background
+ * thread. Hence, methods that report op changes are locking only the in memory
+ * state guarded by the mInMemoryLock which happens to be the app ops service lock
+ * avoiding a lock addition on the critical path. When a query comes we need to
+ * evaluate it based off both in memory and on disk state. This means they need to
+ * be frozen with respect to each other and not change from the querying caller's
+ * perspective. To achieve this we add a dedicated mOnDiskLock to guard the on
+ * disk state. To have fast critical path we need to limit the locking of the
+ * mInMemoryLock, thus for operations that touch in memory and on disk state one
+ * must grab first the mOnDiskLock and then the mInMemoryLock and limit the
+ * in memory lock to extraction of relevant data. Locking order is critical to
+ * avoid deadlocks. The convention is that xxxDLocked suffix means the method
+ * must be called with the mOnDiskLock lock, xxxMLocked suffix means the method
+ * must be called with the mInMemoryLock, xxxDMLocked suffix means the method
+ * must be called with the mOnDiskLock and mInMemoryLock locks acquired in that
+ * exact order.
+ */
+// TODO (bug:122218838): Make sure we handle start of epoch time
+// TODO (bug:122218838): Validate changed time is handled correctly
+final class HistoricalRegistry {
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = HistoricalRegistry.class.getSimpleName();
+
+ private static final String PARAMETER_DELIMITER = ",";
+ private static final String PARAMETER_ASSIGNMENT = "=";
+
+ @GuardedBy("mLock")
+ private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
+
+ // Lock for read/write access to on disk state
+ private final Object mOnDiskLock = new Object();
+
+ //Lock for read/write access to in memory state
+ private final @NonNull Object mInMemoryLock;
+
+ private static final int MSG_WRITE_PENDING_HISTORY = 1;
+
+ // See mMode
+ private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_DISABLED;
+
+ // See mBaseSnapshotInterval
+ private static final long DEFAULT_SNAPSHOT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15);
+
+ // See mIntervalCompressionMultiplier
+ private static final long DEFAULT_COMPRESSION_STEP = 10;
+
+ /**
+ * Whether history is enabled.
+ */
+ @GuardedBy("mInMemoryLock")
+ private int mMode = AppOpsManager.HISTORICAL_MODE_DISABLED;
+
+ /**
+ * This granularity has been chosen to allow clean delineation for intervals
+ * humans understand, 15 min, 60, min, a day, a week, a month (30 days).
+ */
+ @GuardedBy("mInMemoryLock")
+ private long mBaseSnapshotInterval = DEFAULT_SNAPSHOT_INTERVAL_MILLIS;
+
+ /**
+ * The compression between steps. Each subsequent step is this much longer
+ * in terms of duration and each snapshot is this much more apart from the
+ * previous step.
+ */
+ @GuardedBy("mInMemoryLock")
+ private long mIntervalCompressionMultiplier = DEFAULT_COMPRESSION_STEP;
+
+ // The current ops to which to add statistics.
+ @GuardedBy("mInMemoryLock")
+ private @Nullable HistoricalOps mCurrentHistoricalOps;
+
+ // The time we should write the next snapshot.
+ @GuardedBy("mInMemoryLock")
+ private long mNextPersistDueTimeMillis;
+
+ // How much to offset the history on the next write.
+ @GuardedBy("mInMemoryLock")
+ private long mPendingHistoryOffsetMillis;
+
+ // Object managing persistence (read/write)
+ @GuardedBy("mOnDiskLock")
+ private Persistence mPersistence = new Persistence(mBaseSnapshotInterval,
+ mIntervalCompressionMultiplier);
+
+ HistoricalRegistry(@NonNull Object lock) {
+ mInMemoryLock = lock;
+ if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) {
+ synchronized (mInMemoryLock) {
+ // When starting always adjust history to now.
+ mPendingHistoryOffsetMillis = System.currentTimeMillis()
+ - mPersistence.getLastPersistTimeMillisDLocked();
+ }
+ }
+ }
+
+ void systemReady(@NonNull ContentResolver resolver) {
+ updateParametersFromSetting(resolver);
+ final Uri uri = Settings.Global.getUriFor(Settings.Global.APPOP_HISTORY_PARAMETERS);
+ resolver.registerContentObserver(uri, false, new ContentObserver(
+ FgThread.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateParametersFromSetting(resolver);
+ }
+ });
+ }
+
+ private void updateParametersFromSetting(@NonNull ContentResolver resolver) {
+ final String setting = Settings.Global.getString(resolver,
+ Settings.Global.APPOP_HISTORY_PARAMETERS);
+ if (setting == null) {
+ return;
+ }
+ String modeValue = null;
+ String baseSnapshotIntervalValue = null;
+ String intervalMultiplierValue = null;
+ final String[] parameters = setting.split(PARAMETER_DELIMITER);
+ for (String parameter : parameters) {
+ final String[] parts = parameter.split(PARAMETER_ASSIGNMENT);
+ if (parts.length == 2) {
+ final String key = parts[0].trim();
+ switch (key) {
+ case Settings.Global.APPOP_HISTORY_MODE: {
+ modeValue = parts[1].trim();
+ } break;
+ case Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS: {
+ baseSnapshotIntervalValue = parts[1].trim();
+ } break;
+ case Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER: {
+ intervalMultiplierValue = parts[1].trim();
+ } break;
+ default: {
+ Slog.w(LOG_TAG, "Unknown parameter: " + parameter);
+ }
+ }
+ }
+ }
+ if (modeValue != null && baseSnapshotIntervalValue != null
+ && intervalMultiplierValue != null) {
+ try {
+ final int mode = AppOpsManager.parseHistoricalMode(modeValue);
+ final long baseSnapshotInterval = Long.parseLong(baseSnapshotIntervalValue);
+ final int intervalCompressionMultiplier = Integer.parseInt(intervalMultiplierValue);
+ setHistoryParameters(mode, baseSnapshotInterval,intervalCompressionMultiplier);
+ return;
+ } catch (NumberFormatException ignored) {}
+ }
+ Slog.w(LOG_TAG, "Bad value for" + Settings.Global.APPOP_HISTORY_PARAMETERS
+ + "=" + setting + " resetting!");
+ }
+
+ void dump(String prefix, PrintWriter pw, int filterUid,
+ String filterPackage, int filterOp) {
+ synchronized (mOnDiskLock) {
+ synchronized (mInMemoryLock) {
+ pw.println();
+ pw.print(prefix);
+ pw.print("History:");
+
+ pw.print(" mode=");
+ pw.println(AppOpsManager.historicalModeToString(mMode));
+
+ final StringDumpVisitor visitor = new StringDumpVisitor(prefix + " ",
+ pw, filterUid, filterPackage, filterOp);
+ final long nowMillis = System.currentTimeMillis();
+
+ // Dump in memory state first
+ final HistoricalOps currentOps = getUpdatedPendingHistoricalOpsMLocked(
+ nowMillis);
+ makeRelativeToEpochStart(currentOps, nowMillis);
+ currentOps.accept(visitor);
+
+ final List<HistoricalOps> ops = mPersistence.readHistoryDLocked();
+ if (ops != null) {
+ // TODO (bug:122218838): Make sure this is properly dumped
+ final long remainingToFillBatchMillis = mNextPersistDueTimeMillis
+ - nowMillis - mBaseSnapshotInterval;
+ final int opCount = ops.size();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOps op = ops.get(i);
+ op.offsetBeginAndEndTime(remainingToFillBatchMillis);
+ makeRelativeToEpochStart(op, nowMillis);
+ op.accept(visitor);
+ }
+ } else {
+ pw.println(" Empty");
+ }
+ }
+ }
+ }
+
+ @HistoricalMode int getMode() {
+ synchronized (mInMemoryLock) {
+ return mMode;
+ }
+ }
+
+ @Nullable void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull RemoteCallback callback) {
+ final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
+ mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
+ beginTimeMillis, endTimeMillis);
+ final Bundle payload = new Bundle();
+ payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
+ callback.sendResult(payload);
+ }
+
+ @Nullable void getHistoricalOps(int uid, @NonNull String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull RemoteCallback callback) {
+ final long currentTimeMillis = System.currentTimeMillis();
+ if (endTimeMillis == Long.MAX_VALUE) {
+ endTimeMillis = currentTimeMillis;
+ }
+
+ // Argument times are based off epoch start while our internal store is
+ // based off now, so take this into account.
+ final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0);
+ final long inMemoryAdjEndTimeMillis = Math.max(currentTimeMillis - beginTimeMillis, 0);
+ final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis,
+ inMemoryAdjEndTimeMillis);
+
+ synchronized (mOnDiskLock) {
+ final List<HistoricalOps> pendingWrites;
+ final HistoricalOps currentOps;
+ synchronized (mInMemoryLock) {
+ currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis);
+ if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis()
+ || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
+ // Some of the current batch falls into the query, so extract that.
+ final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
+ currentOpsCopy.filter(uid, packageName, opNames, inMemoryAdjBeginTimeMillis,
+ inMemoryAdjEndTimeMillis);
+ result.merge(currentOpsCopy);
+ }
+ pendingWrites = new ArrayList<>(mPendingWrites);
+ mPendingWrites.clear();
+ }
+
+ // If the query was only for in-memory state - done.
+ if (inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis()) {
+ // If there is a write in flight we need to force it now
+ persistPendingHistory(pendingWrites);
+ // Collect persisted state.
+ final long onDiskAndInMemoryOffsetMillis = currentTimeMillis
+ - mNextPersistDueTimeMillis + mBaseSnapshotInterval;
+ final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis
+ - onDiskAndInMemoryOffsetMillis, 0);
+ final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
+ - onDiskAndInMemoryOffsetMillis, 0);
+ mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
+ onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis);
+ }
+
+ // Rebase the result time to be since epoch.
+ result.setBeginAndEndTime(beginTimeMillis, endTimeMillis);
+
+ // Send back the result.
+ final Bundle payload = new Bundle();
+ payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
+ callback.sendResult(payload);
+ }
+ }
+
+ void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
+ @UidState int uidState) {
+ synchronized (mInMemoryLock) {
+ if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
+ getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
+ .increaseAccessCount(op, uid, packageName, uidState, 1);
+
+ }
+ }
+ }
+
+ void incrementOpRejected(int op, int uid, @NonNull String packageName,
+ @UidState int uidState) {
+ synchronized (mInMemoryLock) {
+ if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
+ getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
+ .increaseRejectCount(op, uid, packageName, uidState, 1);
+ }
+ }
+ }
+
+ void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ synchronized (mInMemoryLock) {
+ if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
+ getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
+ .increaseAccessDuration(op, uid, packageName, uidState, increment);
+ }
+ }
+ }
+
+ void setHistoryParameters(@HistoricalMode int mode,
+ long baseSnapshotInterval, long intervalCompressionMultiplier) {
+ synchronized (mOnDiskLock) {
+ synchronized (mInMemoryLock) {
+ boolean resampleHistory = false;
+ Slog.i(LOG_TAG, "New history parameters: mode:"
+ + AppOpsManager.historicalModeToString(mMode) + " baseSnapshotInterval:"
+ + baseSnapshotInterval + " intervalCompressionMultiplier:"
+ + intervalCompressionMultiplier);
+ if (mMode != mode) {
+ mMode = mode;
+ if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) {
+ clearHistoryOnDiskLocked();
+ }
+ }
+ if (mBaseSnapshotInterval != baseSnapshotInterval) {
+ mBaseSnapshotInterval = baseSnapshotInterval;
+ resampleHistory = true;
+ }
+ if (mIntervalCompressionMultiplier != intervalCompressionMultiplier) {
+ mIntervalCompressionMultiplier = intervalCompressionMultiplier;
+ resampleHistory = true;
+ }
+ if (resampleHistory) {
+ resampleHistoryOnDiskInMemoryDMLocked(0);
+ }
+ }
+ }
+ }
+
+ void offsetHistory(long offsetMillis) {
+ synchronized (mOnDiskLock) {
+ synchronized (mInMemoryLock) {
+ final List<HistoricalOps> history = mPersistence.readHistoryDLocked();
+ clearHistory();
+ if (history != null) {
+ final int historySize = history.size();
+ for (int i = 0; i < historySize; i++) {
+ final HistoricalOps ops = history.get(i);
+ ops.offsetBeginAndEndTime(offsetMillis);
+ }
+ if (offsetMillis < 0) {
+ pruneFutureOps(history);
+ }
+ mPersistence.persistHistoricalOpsDLocked(history);
+ }
+ }
+ }
+ }
+
+ void addHistoricalOps(HistoricalOps ops) {
+ final List<HistoricalOps> pendingWrites;
+ synchronized (mInMemoryLock) {
+ // The history files start from mBaseSnapshotInterval - take this into account.
+ ops.offsetBeginAndEndTime(mBaseSnapshotInterval);
+ mPendingWrites.offerFirst(ops);
+ pendingWrites = new ArrayList<>(mPendingWrites);
+ mPendingWrites.clear();
+ }
+ persistPendingHistory(pendingWrites);
+ }
+
+ private void resampleHistoryOnDiskInMemoryDMLocked(long offsetMillis) {
+ mPersistence = new Persistence(mBaseSnapshotInterval, mIntervalCompressionMultiplier);
+ offsetHistory(offsetMillis);
+ }
+
+ void resetHistoryParameters() {
+ setHistoryParameters(DEFAULT_MODE, DEFAULT_SNAPSHOT_INTERVAL_MILLIS,
+ DEFAULT_COMPRESSION_STEP);
+ }
+
+ void clearHistory() {
+ synchronized (mOnDiskLock) {
+ clearHistoryOnDiskLocked();
+ }
+ }
+
+ private void clearHistoryOnDiskLocked() {
+ BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
+ synchronized (mInMemoryLock) {
+ mCurrentHistoricalOps = null;
+ mNextPersistDueTimeMillis = System.currentTimeMillis();
+ mPendingWrites.clear();
+ }
+ mPersistence.clearHistoryDLocked();
+ }
+
+ private @NonNull HistoricalOps getUpdatedPendingHistoricalOpsMLocked(long now) {
+ if (mCurrentHistoricalOps != null) {
+ final long remainingTimeMillis = mNextPersistDueTimeMillis - now;
+ if (remainingTimeMillis > mBaseSnapshotInterval) {
+ // If time went backwards we need to push history to the future with the
+ // overflow over our snapshot interval. If time went forward do nothing
+ // as we would naturally push history into the past on the next write.
+ mPendingHistoryOffsetMillis = remainingTimeMillis - mBaseSnapshotInterval;
+ }
+ final long elapsedTimeMillis = mBaseSnapshotInterval - remainingTimeMillis;
+ mCurrentHistoricalOps.setEndTime(elapsedTimeMillis);
+ if (remainingTimeMillis > 0) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Returning current in-memory state");
+ }
+ return mCurrentHistoricalOps;
+ }
+ if (mCurrentHistoricalOps.isEmpty()) {
+ mCurrentHistoricalOps.setBeginAndEndTime(0, 0);
+ mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
+ return mCurrentHistoricalOps;
+ }
+ // The current batch is full, so persist taking into account overdue persist time.
+ mCurrentHistoricalOps.offsetBeginAndEndTime(mBaseSnapshotInterval);
+ mCurrentHistoricalOps.setBeginTime(mCurrentHistoricalOps.getEndTimeMillis()
+ - mBaseSnapshotInterval);
+ final long overdueTimeMillis = Math.abs(remainingTimeMillis);
+ mCurrentHistoricalOps.offsetBeginAndEndTime(overdueTimeMillis);
+ schedulePersistHistoricalOpsMLocked(mCurrentHistoricalOps);
+ }
+ // The current batch is in the future, i.e. not complete yet.
+ mCurrentHistoricalOps = new HistoricalOps(0, 0);
+ mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Returning new in-memory state");
+ }
+ return mCurrentHistoricalOps;
+ }
+
+ private void persistPendingHistory() {
+ final List<HistoricalOps> pendingWrites;
+ synchronized (mOnDiskLock) {
+ synchronized (mInMemoryLock) {
+ pendingWrites = new ArrayList<>(mPendingWrites);
+ mPendingWrites.clear();
+ if (mPendingHistoryOffsetMillis != 0) {
+ resampleHistoryOnDiskInMemoryDMLocked(mPendingHistoryOffsetMillis);
+ mPendingHistoryOffsetMillis = 0;
+ }
+ }
+ persistPendingHistory(pendingWrites);
+ }
+ }
+ private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
+ synchronized (mOnDiskLock) {
+ BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
+ if (pendingWrites.isEmpty()) {
+ return;
+ }
+ final int opCount = pendingWrites.size();
+ // Pending writes are offset relative to each other, so take this
+ // into account to persist everything in one shot - single write.
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOps current = pendingWrites.get(i);
+ if (i > 0) {
+ final HistoricalOps previous = pendingWrites.get(i - 1);
+ current.offsetBeginAndEndTime(previous.getBeginTimeMillis());
+ }
+ }
+ mPersistence.persistHistoricalOpsDLocked(pendingWrites);
+ }
+ }
+
+ private void schedulePersistHistoricalOpsMLocked(@NonNull HistoricalOps ops) {
+ final Message message = PooledLambda.obtainMessage(
+ HistoricalRegistry::persistPendingHistory, HistoricalRegistry.this);
+ message.what = MSG_WRITE_PENDING_HISTORY;
+ BackgroundThread.getHandler().sendMessage(message);
+ mPendingWrites.offerFirst(ops);
+ }
+
+ private static void makeRelativeToEpochStart(@NonNull HistoricalOps ops, long nowMillis) {
+ ops.setBeginAndEndTime(nowMillis - ops.getEndTimeMillis(),
+ nowMillis- ops.getBeginTimeMillis());
+ }
+
+ private void pruneFutureOps(@NonNull List<HistoricalOps> ops) {
+ final int opCount = ops.size();
+ for (int i = opCount - 1; i >= 0; i--) {
+ final HistoricalOps op = ops.get(i);
+ if (op.getEndTimeMillis() <= mBaseSnapshotInterval) {
+ ops.remove(i);
+ } else if (op.getBeginTimeMillis() < mBaseSnapshotInterval) {
+ final double filterScale = (double) (op.getEndTimeMillis() - mBaseSnapshotInterval)
+ / (double) op.getDurationMillis();
+ Persistence.spliceFromBeginning(op, filterScale);
+ }
+ }
+ }
+
+ private static final class Persistence {
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = Persistence.class.getSimpleName();
+
+ private static final String HISTORY_FILE_SUFFIX = ".xml";
+
+ private static final String TAG_HISTORY = "history";
+ private static final String TAG_OPS = "ops";
+ private static final String TAG_UID = "uid";
+ private static final String TAG_PACKAGE = "package";
+ private static final String TAG_OP = "op";
+ private static final String TAG_STATE = "state";
+
+ private static final String ATTR_VERSION = "version";
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_ACCESS_COUNT = "accessCount";
+ private static final String ATTR_REJECT_COUNT = "rejectCount";
+ private static final String ATTR_ACCESS_DURATION = "accessDuration";
+ private static final String ATTR_BEGIN_TIME = "beginTime";
+ private static final String ATTR_END_TIME = "endTime";
+ private static final String ATTR_OVERFLOW = "overflow";
+
+ private static final int CURRENT_VERSION = 1;
+
+ private final long mBaseSnapshotInterval;
+ private final long mIntervalCompressionMultiplier;
+
+ Persistence(long baseSnapshotInterval, long intervalCompressionMultiplier) {
+ mBaseSnapshotInterval = baseSnapshotInterval;
+ mIntervalCompressionMultiplier = intervalCompressionMultiplier;
+ }
+
+ private final AtomicDirectory mHistoricalAppOpsDir = new AtomicDirectory(
+ new File(new File(Environment.getDataSystemDeDirectory(), "appops"), "history"));
+
+ private File generateFile(@NonNull File baseDir, int depth) {
+ final long globalBeginMillis = computeGlobalIntervalBeginMillis(depth);
+ return new File(baseDir, Long.toString(globalBeginMillis) + HISTORY_FILE_SUFFIX);
+ }
+
+ void clearHistoryDLocked() {
+ mHistoricalAppOpsDir.delete();
+ }
+
+ void persistHistoricalOpsDLocked(@NonNull List<HistoricalOps> ops) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Persisting ops:\n" + opsToDebugString(ops));
+ enforceOpsWellFormed(ops);
+ }
+ try {
+ final File newBaseDir = mHistoricalAppOpsDir.startWrite();
+ final File oldBaseDir = mHistoricalAppOpsDir.getBackupDirectory();
+ handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops, 0);
+ mHistoricalAppOpsDir.finishWrite();
+ } catch (Throwable t) {
+ Slog.wtf(LOG_TAG, "Failed to write historical app ops, restoring backup", t);
+ mHistoricalAppOpsDir.failWrite();
+ }
+ }
+
+ @Nullable List<HistoricalOps> readHistoryRawDLocked() {
+ return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/,
+ null /*filterPackageName*/, null /*filterOpNames*/,
+ 0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/);
+ }
+
+ @Nullable List<HistoricalOps> readHistoryDLocked() {
+ final List<HistoricalOps> result = readHistoryRawDLocked();
+ // Take into account in memory state duration.
+ if (result != null) {
+ final int opCount = result.size();
+ for (int i = 0; i < opCount; i++) {
+ result.get(i).offsetBeginAndEndTime(mBaseSnapshotInterval);
+ }
+ }
+ return result;
+ }
+
+ long getLastPersistTimeMillisDLocked() {
+ try {
+ final File baseDir = mHistoricalAppOpsDir.startRead();
+ final File file = generateFile(baseDir, 0);
+ if (file.exists()) {
+ return file.lastModified();
+ }
+ mHistoricalAppOpsDir.finishRead();
+ } catch (IOException e) {
+ Slog.wtf("Error reading historical app ops. Deleting history.", e);
+ mHistoricalAppOpsDir.delete();
+ }
+ return 0;
+ }
+
+ private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps,
+ int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+ long filterBeingMillis, long filterEndMillis) {
+ final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid,
+ filterPackageName, filterOpNames, filterBeingMillis, filterEndMillis);
+ if (readOps != null) {
+ final int readCount = readOps.size();
+ for (int i = 0; i < readCount; i++) {
+ final HistoricalOps readOp = readOps.get(i);
+ currentOps.merge(readOp);
+ }
+ }
+ }
+
+ private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(
+ int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+ long filterBeginTimeMillis, long filterEndTimeMillis) {
+ try {
+ final File baseDir = mHistoricalAppOpsDir.startRead();
+ final File[] files = baseDir.listFiles();
+ if (files == null) {
+ return null;
+ }
+ final ArraySet<File> historyFiles = new ArraySet<>(files.length);
+ for (File file : files) {
+ if (file.isFile() && file.getName().endsWith(HISTORY_FILE_SUFFIX)) {
+ historyFiles.add(file);
+ }
+ }
+ final long[] globalContentOffsetMillis = {0};
+ final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked(
+ baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+ filterEndTimeMillis, globalContentOffsetMillis, null /*outOps*/,
+ 0 /*depth*/, historyFiles);
+ mHistoricalAppOpsDir.finishRead();
+ return ops;
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf("Error reading historical app ops. Deleting history.", e);
+ mHistoricalAppOpsDir.delete();
+ }
+ return null;
+ }
+
+ private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked(
+ @NonNull File baseDir, int filterUid, @NonNull String filterPackageName,
+ @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+ long filterEndTimeMillis, @NonNull long[] globalContentOffsetMillis,
+ @Nullable LinkedList<HistoricalOps> outOps, int depth,
+ @NonNull ArraySet<File> historyFiles)
+ throws IOException, XmlPullParserException {
+ final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+ depth) * mBaseSnapshotInterval;
+ final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+ depth + 1) * mBaseSnapshotInterval;
+
+ filterBeginTimeMillis = Math.max(filterBeginTimeMillis - previousIntervalEndMillis, 0);
+ filterEndTimeMillis = filterEndTimeMillis - previousIntervalEndMillis;
+
+ // Read historical data at this level
+ final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir,
+ previousIntervalEndMillis, currentIntervalEndMillis, filterUid,
+ filterPackageName, filterOpNames, filterBeginTimeMillis, filterEndTimeMillis,
+ globalContentOffsetMillis, depth, historyFiles);
+
+ // Empty is a special signal to stop diving
+ if (readOps != null && readOps.isEmpty()) {
+ return outOps;
+ }
+
+ // Collect older historical data from subsequent levels
+ outOps = collectHistoricalOpsRecursiveDLocked(
+ baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+ filterEndTimeMillis, globalContentOffsetMillis, outOps, depth + 1,
+ historyFiles);
+
+ // Make older historical data relative to the current historical level
+ if (outOps != null) {
+ final int opCount = outOps.size();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOps collectedOp = outOps.get(i);
+ collectedOp.offsetBeginAndEndTime(currentIntervalEndMillis);
+ }
+ }
+
+ if (readOps != null) {
+ if (outOps == null) {
+ outOps = new LinkedList<>();
+ }
+ // Add the read ops to output
+ final int opCount = readOps.size();
+ for (int i = opCount - 1; i >= 0; i--) {
+ outOps.offerFirst(readOps.get(i));
+ }
+ }
+
+ return outOps;
+ }
+
+ private boolean createHardLinkToExistingFile(@NonNull File fromFile, @NonNull File toFile)
+ throws IOException {
+ if (!fromFile.exists()) {
+ return false;
+ }
+ Files.createLink(toFile.toPath(), fromFile.toPath());
+ return true;
+ }
+
+ private void handlePersistHistoricalOpsRecursiveDLocked(@NonNull File newBaseDir,
+ @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps, int depth)
+ throws IOException, XmlPullParserException {
+ final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+ depth) * mBaseSnapshotInterval;
+ final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+ depth + 1) * mBaseSnapshotInterval;
+
+ if (passedOps == null || passedOps.isEmpty()) {
+ // If there is an old file we need to copy it over to the new state.
+ final File oldFile = generateFile(oldBaseDir, depth);
+ final File newFile = generateFile(newBaseDir, depth);
+ if (createHardLinkToExistingFile(oldFile, newFile)) {
+ handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
+ passedOps, depth + 1);
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ enforceOpsWellFormed(passedOps);
+ }
+
+ // Normalize passed ops time to be based off this interval start
+ final int passedOpCount = passedOps.size();
+ for (int i = 0; i < passedOpCount; i++) {
+ final HistoricalOps passedOp = passedOps.get(i);
+ passedOp.offsetBeginAndEndTime(-previousIntervalEndMillis);
+ }
+
+ if (DEBUG) {
+ enforceOpsWellFormed(passedOps);
+ }
+
+ // Read persisted ops for this interval
+ final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir,
+ previousIntervalEndMillis, currentIntervalEndMillis,
+ Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/,
+ null /*filterOpNames*/, Long.MIN_VALUE /*filterBeginTimeMillis*/,
+ Long.MAX_VALUE /*filterEndTimeMillis*/, null, depth,
+ null /*historyFiles*/);
+
+ if (DEBUG) {
+ enforceOpsWellFormed(existingOps);
+ }
+
+ // Offset existing ops to account for elapsed time
+ final int existingOpCount = existingOps.size();
+ if (existingOpCount > 0) {
+ // Compute elapsed time
+ final long elapsedTimeMillis = passedOps.get(passedOps.size() - 1)
+ .getEndTimeMillis();
+ for (int i = 0; i < existingOpCount; i++) {
+ final HistoricalOps existingOp = existingOps.get(i);
+ existingOp.offsetBeginAndEndTime(elapsedTimeMillis);
+ }
+ }
+
+ if (DEBUG) {
+ enforceOpsWellFormed(existingOps);
+ }
+
+ final long slotDurationMillis = previousIntervalEndMillis;
+
+ // Consolidate passed ops at the current slot duration ensuring each snapshot is
+ // full. To achieve this we put all passed and existing ops in a list and will
+ // merge them to ensure each represents a snapshot at the current granularity.
+ final List<HistoricalOps> allOps = new LinkedList<>();
+ allOps.addAll(passedOps);
+ allOps.addAll(existingOps);
+
+ if (DEBUG) {
+ enforceOpsWellFormed(allOps);
+ }
+
+ // Compute ops to persist and overflow ops
+ List<HistoricalOps> persistedOps = null;
+ List<HistoricalOps> overflowedOps = null;
+
+ // We move a snapshot into the next level only if the start time is
+ // after the end of the current interval. This avoids rewriting all
+ // files to propagate the information added to the history on every
+ // iteration. Instead, we would rewrite the next level file only if
+ // an entire snapshot from the previous level is being propagated.
+ // The trade off is that we need to store how much the last snapshot
+ // of the current interval overflows past the interval end. We write
+ // the overflow data to avoid parsing all snapshots on query.
+ long intervalOverflowMillis = 0;
+ final int opCount = allOps.size();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOps op = allOps.get(i);
+ final HistoricalOps persistedOp;
+ final HistoricalOps overflowedOp;
+ if (op.getEndTimeMillis() <= currentIntervalEndMillis) {
+ persistedOp = op;
+ overflowedOp = null;
+ } else if (op.getBeginTimeMillis() < currentIntervalEndMillis) {
+ persistedOp = op;
+ intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
+ if (intervalOverflowMillis > previousIntervalEndMillis) {
+ final double splitScale = (double) intervalOverflowMillis
+ / op.getDurationMillis();
+ overflowedOp = spliceFromEnd(op, splitScale);
+ intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
+ } else {
+ overflowedOp = null;
+ }
+ } else {
+ persistedOp = null;
+ overflowedOp = op;
+ }
+ if (persistedOp != null) {
+ if (persistedOps == null) {
+ persistedOps = new ArrayList<>();
+ }
+ persistedOps.add(persistedOp);
+ }
+ if (overflowedOp != null) {
+ if (overflowedOps == null) {
+ overflowedOps = new ArrayList<>();
+ }
+ overflowedOps.add(overflowedOp);
+ }
+ }
+
+ if (DEBUG) {
+ enforceOpsWellFormed(persistedOps);
+ enforceOpsWellFormed(overflowedOps);
+ }
+
+ if (persistedOps != null) {
+ normalizeSnapshotForSlotDuration(persistedOps, slotDurationMillis);
+ final File newFile = generateFile(newBaseDir, depth);
+ writeHistoricalOpsDLocked(persistedOps, intervalOverflowMillis, newFile);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Persisted at depth: " + depth
+ + " ops:\n" + opsToDebugString(persistedOps));
+ enforceOpsWellFormed(persistedOps);
+ }
+ }
+
+ handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
+ overflowedOps, depth + 1);
+ }
+
+ private @NonNull List<HistoricalOps> readHistoricalOpsLocked(File baseDir,
+ long intervalBeginMillis, long intervalEndMillis, int filterUid,
+ @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ long filterBeginTimeMillis, long filterEndTimeMillis,
+ @Nullable long[] cumulativeOverflowMillis, int depth,
+ @NonNull ArraySet<File> historyFiles)
+ throws IOException, XmlPullParserException {
+ final File file = generateFile(baseDir, depth);
+ if (historyFiles != null) {
+ historyFiles.remove(file);
+ }
+ if (filterBeginTimeMillis >= filterEndTimeMillis
+ || filterEndTimeMillis < intervalBeginMillis) {
+ // Don't go deeper
+ return Collections.emptyList();
+ }
+ if (filterBeginTimeMillis >= (intervalEndMillis
+ + ((intervalEndMillis - intervalBeginMillis) / mIntervalCompressionMultiplier)
+ + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0))
+ || !file.exists()) {
+ if (historyFiles == null || historyFiles.isEmpty()) {
+ // Don't go deeper
+ return Collections.emptyList();
+ } else {
+ // Keep diving
+ return null;
+ }
+ }
+ return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterOpNames,
+ filterBeginTimeMillis, filterEndTimeMillis, cumulativeOverflowMillis);
+ }
+
+ private @Nullable List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file,
+ int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ long filterBeginTimeMillis, long filterEndTimeMillis,
+ @Nullable long[] cumulativeOverflowMillis)
+ throws IOException, XmlPullParserException {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Reading ops from:" + file);
+ }
+ List<HistoricalOps> allOps = null;
+ try (FileInputStream stream = new FileInputStream(file)) {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ XmlUtils.beginDocument(parser, TAG_HISTORY);
+ final long overflowMillis = XmlUtils.readLongAttribute(parser, ATTR_OVERFLOW, 0);
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_OPS.equals(parser.getName())) {
+ final HistoricalOps ops = readeHistoricalOpsDLocked(parser,
+ filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+ filterEndTimeMillis, cumulativeOverflowMillis);
+ if (ops == null) {
+ continue;
+ }
+ if (ops.isEmpty()) {
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ if (allOps == null) {
+ allOps = new ArrayList<>();
+ }
+ allOps.add(ops);
+ }
+ }
+ if (cumulativeOverflowMillis != null) {
+ cumulativeOverflowMillis[0] += overflowMillis;
+ }
+ } catch (FileNotFoundException e) {
+ Slog.i(LOG_TAG, "No history file: " + file.getName());
+ return Collections.emptyList();
+ }
+ if (DEBUG) {
+ if (allOps != null) {
+ Slog.i(LOG_TAG, "Read from file: " + file + "ops:\n"
+ + opsToDebugString(allOps));
+ enforceOpsWellFormed(allOps);
+ }
+ }
+ return allOps;
+ }
+
+ private @Nullable HistoricalOps readeHistoricalOpsDLocked(
+ @NonNull XmlPullParser parser, int filterUid, @Nullable String filterPackageName,
+ @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+ long filterEndTimeMillis, @Nullable long[] cumulativeOverflowMillis)
+ throws IOException, XmlPullParserException {
+ final long beginTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_BEGIN_TIME, 0)
+ + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
+ final long endTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_END_TIME, 0)
+ + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
+ // Keep reading as subsequent records may start matching
+ if (filterEndTimeMillis < beginTimeMillis) {
+ return null;
+ }
+ // Stop reading as subsequent records will not match
+ if (filterBeginTimeMillis > endTimeMillis) {
+ return new HistoricalOps(0, 0);
+ }
+ final long filteredBeginTimeMillis = Math.max(beginTimeMillis, filterBeginTimeMillis);
+ final long filteredEndTimeMillis = Math.min(endTimeMillis, filterEndTimeMillis);
+ final double filterScale = (double) (filteredEndTimeMillis - filteredBeginTimeMillis)
+ / (double) (endTimeMillis - beginTimeMillis);
+ HistoricalOps ops = null;
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_UID.equals(parser.getName())) {
+ final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser,
+ filterUid, filterPackageName, filterOpNames, filterScale);
+ if (ops == null) {
+ ops = returnedOps;
+ }
+ }
+ }
+ if (ops != null) {
+ ops.setBeginAndEndTime(filteredBeginTimeMillis, filteredEndTimeMillis);
+ }
+ return ops;
+ }
+
+ private @Nullable HistoricalOps readHistoricalUidOpsDLocked(
+ @Nullable HistoricalOps ops, @NonNull XmlPullParser parser, int filterUid,
+ @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ double filterScale) throws IOException, XmlPullParserException {
+ final int uid = XmlUtils.readIntAttribute(parser, ATTR_NAME);
+ if (filterUid != Process.INVALID_UID && filterUid != uid) {
+ XmlUtils.skipCurrentTag(parser);
+ return null;
+ }
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_PACKAGE.equals(parser.getName())) {
+ final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops,
+ uid, parser, filterPackageName, filterOpNames, filterScale);
+ if (ops == null) {
+ ops = returnedOps;
+ }
+ }
+ }
+ return ops;
+ }
+
+ private @Nullable HistoricalOps readHistoricalPackageOpsDLocked(
+ @Nullable HistoricalOps ops, int uid, @NonNull XmlPullParser parser,
+ @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ double filterScale) throws IOException, XmlPullParserException {
+ final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME);
+ if (filterPackageName != null && !filterPackageName.equals(packageName)) {
+ XmlUtils.skipCurrentTag(parser);
+ return null;
+ }
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_OP.equals(parser.getName())) {
+ final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid,
+ packageName, parser, filterOpNames, filterScale);
+ if (ops == null) {
+ ops = returnedOps;
+ }
+ }
+ }
+ return ops;
+ }
+
+ private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops,
+ int uid, String packageName, @NonNull XmlPullParser parser,
+ @Nullable String[] filterOpNames, double filterScale)
+ throws IOException, XmlPullParserException {
+ final int op = XmlUtils.readIntAttribute(parser, ATTR_NAME);
+ if (filterOpNames != null && !ArrayUtils.contains(filterOpNames,
+ AppOpsManager.opToName(op))) {
+ XmlUtils.skipCurrentTag(parser);
+ return null;
+ }
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_STATE.equals(parser.getName())) {
+ final HistoricalOps returnedOps = readUidStateDLocked(ops, uid,
+ packageName, op, parser, filterScale);
+ if (ops == null) {
+ ops = returnedOps;
+ }
+ }
+ }
+ return ops;
+ }
+
+ private @Nullable HistoricalOps readUidStateDLocked(@Nullable HistoricalOps ops,
+ int uid, String packageName, int op, @NonNull XmlPullParser parser,
+ double filterScale) throws IOException {
+ final int uidState = XmlUtils.readIntAttribute(parser, ATTR_NAME);
+ long accessCount = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_COUNT, 0);
+ if (accessCount > 0) {
+ if (!Double.isNaN(filterScale)) {
+ accessCount = (long) HistoricalOps.round(
+ (double) accessCount * filterScale);
+ }
+ if (ops == null) {
+ ops = new HistoricalOps(0, 0);
+ }
+ ops.increaseAccessCount(op, uid, packageName, uidState, accessCount);
+ }
+ long rejectCount = XmlUtils.readLongAttribute(parser, ATTR_REJECT_COUNT, 0);
+ if (rejectCount > 0) {
+ if (!Double.isNaN(filterScale)) {
+ rejectCount = (long) HistoricalOps.round(
+ (double) rejectCount * filterScale);
+ }
+ if (ops == null) {
+ ops = new HistoricalOps(0, 0);
+ }
+ ops.increaseRejectCount(op, uid, packageName, uidState, rejectCount);
+ }
+ long accessDuration = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_DURATION, 0);
+ if (accessDuration > 0) {
+ if (!Double.isNaN(filterScale)) {
+ accessDuration = (long) HistoricalOps.round(
+ (double) accessDuration * filterScale);
+ }
+ if (ops == null) {
+ ops = new HistoricalOps(0, 0);
+ }
+ ops.increaseAccessDuration(op, uid, packageName, uidState, accessDuration);
+ }
+ return ops;
+ }
+
+ private void writeHistoricalOpsDLocked(@Nullable List<HistoricalOps> allOps,
+ long intervalOverflowMillis, @NonNull File file) throws IOException {
+ final FileOutputStream output = mHistoricalAppOpsDir.openWrite(file);
+ try {
+ final XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(output, StandardCharsets.UTF_8.name());
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+ true);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_HISTORY);
+ serializer.attribute(null, ATTR_VERSION, String.valueOf(CURRENT_VERSION));
+ if (intervalOverflowMillis != 0) {
+ serializer.attribute(null, ATTR_OVERFLOW,
+ Long.toString(intervalOverflowMillis));
+ }
+ if (allOps != null) {
+ final int opsCount = allOps.size();
+ for (int i = 0; i < opsCount; i++) {
+ final HistoricalOps ops = allOps.get(i);
+ writeHistoricalOpDLocked(ops, serializer);
+ }
+ }
+ serializer.endTag(null, TAG_HISTORY);
+ serializer.endDocument();
+ mHistoricalAppOpsDir.closeWrite(output);
+ } catch (IOException e) {
+ mHistoricalAppOpsDir.failWrite(output);
+ throw e;
+ }
+ }
+
+ private void writeHistoricalOpDLocked(@NonNull HistoricalOps ops,
+ @NonNull XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_OPS);
+ serializer.attribute(null, ATTR_BEGIN_TIME, Long.toString(ops.getBeginTimeMillis()));
+ serializer.attribute(null, ATTR_END_TIME, Long.toString(ops.getEndTimeMillis()));
+ final int uidCount = ops.getUidCount();
+ for (int i = 0; i < uidCount; i++) {
+ final HistoricalUidOps uidOp = ops.getUidOpsAt(i);
+ writeHistoricalUidOpsDLocked(uidOp, serializer);
+ }
+ serializer.endTag(null, TAG_OPS);
+ }
+
+ private void writeHistoricalUidOpsDLocked(@NonNull HistoricalUidOps uidOps,
+ @NonNull XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_UID);
+ serializer.attribute(null, ATTR_NAME, Integer.toString(uidOps.getUid()));
+ final int packageCount = uidOps.getPackageCount();
+ for (int i = 0; i < packageCount; i++) {
+ final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(i);
+ writeHistoricalPackageOpsDLocked(packageOps, serializer);
+ }
+ serializer.endTag(null, TAG_UID);
+ }
+
+ private void writeHistoricalPackageOpsDLocked(@NonNull HistoricalPackageOps packageOps,
+ @NonNull XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_PACKAGE);
+ serializer.attribute(null, ATTR_NAME, packageOps.getPackageName());
+ final int opCount = packageOps.getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOp op = packageOps.getOpAt(i);
+ writeHistoricalOpDLocked(op, serializer);
+ }
+ serializer.endTag(null, TAG_PACKAGE);
+ }
+
+ private void writeHistoricalOpDLocked(@NonNull HistoricalOp op,
+ @NonNull XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_OP);
+ serializer.attribute(null, ATTR_NAME, Integer.toString(op.getOpCode()));
+ for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) {
+ writeUidStateOnLocked(op, uidState, serializer);
+ }
+ serializer.endTag(null, TAG_OP);
+ }
+
+ private void writeUidStateOnLocked(@NonNull HistoricalOp op, @UidState int uidState,
+ @NonNull XmlSerializer serializer) throws IOException {
+ final long accessCount = op.getAccessCount(uidState);
+ final long rejectCount = op.getRejectCount(uidState);
+ final long accessDuration = op.getAccessDuration(uidState);
+ if (accessCount == 0 && rejectCount == 0 && accessDuration == 0) {
+ return;
+ }
+ serializer.startTag(null, TAG_STATE);
+ serializer.attribute(null, ATTR_NAME, Integer.toString(uidState));
+ if (accessCount > 0) {
+ serializer.attribute(null, ATTR_ACCESS_COUNT, Long.toString(accessCount));
+ }
+ if (rejectCount > 0) {
+ serializer.attribute(null, ATTR_REJECT_COUNT, Long.toString(rejectCount));
+ }
+ if (accessDuration > 0) {
+ serializer.attribute(null, ATTR_ACCESS_DURATION, Long.toString(accessDuration));
+ }
+ serializer.endTag(null, TAG_STATE);
+ }
+
+ private static void enforceOpsWellFormed(@NonNull List<HistoricalOps> ops) {
+ if (ops == null) {
+ return;
+ }
+ HistoricalOps previous;
+ HistoricalOps current = null;
+ final int opsCount = ops.size();
+ for (int i = 0; i < opsCount; i++) {
+ previous = current;
+ current = ops.get(i);
+ if (current.isEmpty()) {
+ throw new IllegalStateException("Empty ops:\n"
+ + opsToDebugString(ops));
+ }
+ if (current.getEndTimeMillis() < current.getBeginTimeMillis()) {
+ throw new IllegalStateException("Begin after end:\n"
+ + opsToDebugString(ops));
+ }
+ if (previous != null) {
+ if (previous.getEndTimeMillis() > current.getBeginTimeMillis()) {
+ throw new IllegalStateException("Intersecting ops:\n"
+ + opsToDebugString(ops));
+ }
+ if (previous.getBeginTimeMillis() > current.getBeginTimeMillis()) {
+ throw new IllegalStateException("Non increasing ops:\n"
+ + opsToDebugString(ops));
+ }
+ }
+ }
+ }
+
+ private long computeGlobalIntervalBeginMillis(int depth) {
+ long beginTimeMillis = 0;
+ for (int i = 0; i < depth + 1; i++) {
+ beginTimeMillis += Math.pow(mIntervalCompressionMultiplier, i);
+ }
+ return beginTimeMillis * mBaseSnapshotInterval;
+ }
+
+ private static @NonNull HistoricalOps spliceFromEnd(@NonNull HistoricalOps ops,
+ double spliceRatio) {
+ if (DEBUG) {
+ Slog.w(LOG_TAG, "Splicing from end:" + ops + " ratio:" + spliceRatio);
+ }
+ final HistoricalOps splice = ops.spliceFromEnd(spliceRatio);
+ if (DEBUG) {
+ Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
+ }
+ return splice;
+ }
+
+
+ private static @NonNull HistoricalOps spliceFromBeginning(@NonNull HistoricalOps ops,
+ double spliceRatio) {
+ if (DEBUG) {
+ Slog.w(LOG_TAG, "Splicing from beginning:" + ops + " ratio:" + spliceRatio);
+ }
+ final HistoricalOps splice = ops.spliceFromBeginning(spliceRatio);
+ if (DEBUG) {
+ Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
+ }
+ return splice;
+ }
+
+ private static void normalizeSnapshotForSlotDuration(@NonNull List<HistoricalOps> ops,
+ long slotDurationMillis) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Normalizing for slot duration: " + slotDurationMillis
+ + " ops:\n" + opsToDebugString(ops));
+ enforceOpsWellFormed(ops);
+ }
+ long slotBeginTimeMillis;
+ final int opCount = ops.size();
+ for (int processedIdx = opCount - 1; processedIdx >= 0; processedIdx--) {
+ final HistoricalOps processedOp = ops.get(processedIdx);
+ slotBeginTimeMillis = Math.max(processedOp.getEndTimeMillis()
+ - slotDurationMillis, 0);
+ for (int candidateIdx = processedIdx - 1; candidateIdx >= 0; candidateIdx--) {
+ final HistoricalOps candidateOp = ops.get(candidateIdx);
+ final long candidateSlotIntersectionMillis = candidateOp.getEndTimeMillis()
+ - Math.min(slotBeginTimeMillis, processedOp.getBeginTimeMillis());
+ if (candidateSlotIntersectionMillis <= 0) {
+ break;
+ }
+ final float candidateSplitRatio = candidateSlotIntersectionMillis
+ / (float) candidateOp.getDurationMillis();
+ if (Float.compare(candidateSplitRatio, 1.0f) >= 0) {
+ ops.remove(candidateIdx);
+ processedIdx--;
+ processedOp.merge(candidateOp);
+ } else {
+ final HistoricalOps endSplice = spliceFromEnd(candidateOp,
+ candidateSplitRatio);
+ if (endSplice != null) {
+ processedOp.merge(endSplice);
+ }
+ if (candidateOp.isEmpty()) {
+ ops.remove(candidateIdx);
+ processedIdx--;
+ }
+ }
+ }
+ }
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Normalized for slot duration: " + slotDurationMillis
+ + " ops:\n" + opsToDebugString(ops));
+ enforceOpsWellFormed(ops);
+ }
+ }
+
+ private static @NonNull String opsToDebugString(@NonNull List<HistoricalOps> ops) {
+ StringBuilder builder = new StringBuilder();
+ final int opCount = ops.size();
+ for (int i = 0; i < opCount; i++) {
+ builder.append(" ");
+ builder.append(ops.get(i));
+ if (i < opCount - 1) {
+ builder.append('\n');
+ }
+ }
+ return builder.toString();
+ }
+ }
+
+ private final class StringDumpVisitor implements AppOpsManager.HistoricalOpsVisitor {
+ private final long mNow = System.currentTimeMillis();
+
+ private final SimpleDateFormat mDateFormatter = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss.SSS");
+ private final Date mDate = new Date();
+
+ private final @NonNull String mOpsPrefix;
+ private final @NonNull String mUidPrefix;
+ private final @NonNull String mPackagePrefix;
+ private final @NonNull String mEntryPrefix;
+ private final @NonNull String mUidStatePrefix;
+ private final @NonNull PrintWriter mWriter;
+ private final int mFilterUid;
+ private final String mFilterPackage;
+ private final int mFilterOp;
+
+ StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer,
+ int filterUid, String filterPackage, int filterOp) {
+ mOpsPrefix = prefix + " ";
+ mUidPrefix = mOpsPrefix + " ";
+ mPackagePrefix = mUidPrefix + " ";
+ mEntryPrefix = mPackagePrefix + " ";
+ mUidStatePrefix = mEntryPrefix + " ";
+ mWriter = writer;
+ mFilterUid = filterUid;
+ mFilterPackage = filterPackage;
+ mFilterOp = filterOp;
+ }
+
+ @Override
+ public void visitHistoricalOps(HistoricalOps ops) {
+ mWriter.println();
+ mWriter.print(mOpsPrefix);
+ mWriter.println("snapshot:");
+ mWriter.print(mUidPrefix);
+ mWriter.print("begin = ");
+ mDate.setTime(ops.getBeginTimeMillis());
+ mWriter.print(mDateFormatter.format(mDate));
+ mWriter.print(" (");
+ TimeUtils.formatDuration(ops.getBeginTimeMillis() - mNow, mWriter);
+ mWriter.println(")");
+ mWriter.print(mUidPrefix);
+ mWriter.print("end = ");
+ mDate.setTime(ops.getEndTimeMillis());
+ mWriter.print(mDateFormatter.format(mDate));
+ mWriter.print(" (");
+ TimeUtils.formatDuration(ops.getEndTimeMillis() - mNow, mWriter);
+ mWriter.println(")");
+ }
+
+ @Override
+ public void visitHistoricalUidOps(HistoricalUidOps ops) {
+ if (mFilterUid != Process.INVALID_UID && mFilterUid != ops.getUid()) {
+ return;
+ }
+ mWriter.println();
+ mWriter.print(mUidPrefix);
+ mWriter.print("Uid ");
+ UserHandle.formatUid(mWriter, ops.getUid());
+ mWriter.println(":");
+ }
+
+ @Override
+ public void visitHistoricalPackageOps(HistoricalPackageOps ops) {
+ if (mFilterPackage != null && !mFilterPackage.equals(ops.getPackageName())) {
+ return;
+ }
+ mWriter.print(mPackagePrefix);
+ mWriter.print("Package ");
+ mWriter.print(ops.getPackageName());
+ mWriter.println(":");
+ }
+
+ @Override
+ public void visitHistoricalOp(HistoricalOp ops) {
+ if (mFilterOp != AppOpsManager.OP_NONE && mFilterOp != ops.getOpCode()) {
+ return;
+ }
+ mWriter.print(mEntryPrefix);
+ mWriter.print(AppOpsManager.opToName(ops.getOpCode()));
+ mWriter.println(":");
+ for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) {
+ boolean printedUidState = false;
+ final long accessCount = ops.getAccessCount(uidState);
+ if (accessCount > 0) {
+ if (!printedUidState) {
+ mWriter.print(mUidStatePrefix);
+ mWriter.print(AppOpsManager.uidStateToString(uidState));
+ mWriter.print("[");
+ printedUidState = true;
+ }
+ mWriter.print("access=");
+ mWriter.print(accessCount);
+ }
+ final long rejectCount = ops.getRejectCount(uidState);
+ if (rejectCount > 0) {
+ if (!printedUidState) {
+ mWriter.print(mUidStatePrefix);
+ mWriter.print(AppOpsManager.uidStateToString(uidState));
+ mWriter.print("[");
+ printedUidState = true;
+ } else {
+ mWriter.print(",");
+ }
+ mWriter.print("reject=");
+ mWriter.print(rejectCount);
+ }
+ final long accessDuration = ops.getAccessDuration(uidState);
+ if (accessDuration > 0) {
+ if (!printedUidState) {
+ mWriter.print(mUidStatePrefix);
+ mWriter.print(AppOpsManager.uidStateToString(uidState));
+ printedUidState = true;
+ } else {
+ mWriter.print(",");
+ }
+ mWriter.print("duration=");
+ mWriter.print(accessDuration);
+ }
+ if (printedUidState) {
+ mWriter.println("]");
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
new file mode 100644
index 000000000000..4901d3a8b06f
--- /dev/null
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsAppOpsTestCases",
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
index 73d3d9591529..b332c47ce281 100644
--- a/services/core/java/com/android/server/display/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -18,7 +18,9 @@ package com.android.server.display;
import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
+import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION;
+import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
@@ -31,6 +33,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.hardware.display.ColorDisplayManager;
import android.hardware.display.IColorDisplayManager;
@@ -39,6 +42,7 @@ import android.opengl.Matrix;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.provider.Settings.System;
@@ -61,6 +65,7 @@ import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeParseException;
+import java.util.Arrays;
/**
* Controls the display's color transforms.
@@ -83,6 +88,10 @@ public final class ColorDisplayService extends SystemService {
Matrix.setIdentityM(MATRIX_IDENTITY, 0);
}
+ private static final int MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE = 0;
+ private static final int MSG_APPLY_NIGHT_DISPLAY_ANIMATED = 1;
+ private static final int MSG_APPLY_GLOBAL_SATURATION = 2;
+
/**
* Evaluator used to animate color matrix transitions.
*/
@@ -161,6 +170,53 @@ public final class ColorDisplayService extends SystemService {
}
};
+ private final TintController mGlobalSaturationTintController = new TintController() {
+
+ private float[] mMatrixGlobalSaturation = new float[16];
+
+ @Override
+ public void setUp(Context context, boolean needsLinear) {
+ }
+
+ @Override
+ public float[] getMatrix() {
+ return Arrays.copyOf(mMatrixGlobalSaturation, mMatrixGlobalSaturation.length);
+ }
+
+ @Override
+ public void setMatrix(int saturationLevel) {
+ if (saturationLevel < 0) {
+ saturationLevel = 0;
+ } else if (saturationLevel > 100) {
+ saturationLevel = 100;
+ }
+ Slog.d(TAG, "Setting saturation level: " + saturationLevel);
+
+ if (saturationLevel == 100) {
+ Matrix.setIdentityM(mMatrixGlobalSaturation, 0);
+ } else {
+ float saturation = saturationLevel * 0.1f;
+ float desaturation = 1.0f - saturation;
+ float[] luminance = {0.231f * desaturation, 0.715f * desaturation,
+ 0.072f * desaturation};
+ mMatrixGlobalSaturation[0] = luminance[0] + saturation;
+ mMatrixGlobalSaturation[1] = luminance[0];
+ mMatrixGlobalSaturation[2] = luminance[0];
+ mMatrixGlobalSaturation[4] = luminance[1];
+ mMatrixGlobalSaturation[5] = luminance[1] + saturation;
+ mMatrixGlobalSaturation[6] = luminance[1];
+ mMatrixGlobalSaturation[8] = luminance[2];
+ mMatrixGlobalSaturation[9] = luminance[2];
+ mMatrixGlobalSaturation[10] = luminance[2] + saturation;
+ }
+ }
+
+ @Override
+ public int getLevel() {
+ return LEVEL_COLOR_MATRIX_SATURATION;
+ }
+ };
+
private final Handler mHandler;
private int mCurrentUser = UserHandle.USER_NULL;
@@ -178,7 +234,7 @@ public final class ColorDisplayService extends SystemService {
public ColorDisplayService(Context context) {
super(context);
- mHandler = new Handler(Looper.getMainLooper());
+ mHandler = new TintHandler(Looper.getMainLooper());
}
@Override
@@ -904,6 +960,23 @@ public final class ColorDisplayService extends SystemService {
void onDisplayWhiteBalanceStatusChanged(boolean enabled);
}
+ private final class TintHandler extends Handler {
+
+ TintHandler(Looper looper) {
+ super(looper, null, true /* async */);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_APPLY_GLOBAL_SATURATION:
+ mGlobalSaturationTintController.setMatrix(msg.arg1);
+ applyTint(mGlobalSaturationTintController, false);
+ break;
+ }
+ }
+ }
+
private final class BinderService extends IColorDisplayManager.Stub {
@Override
@@ -915,5 +988,27 @@ public final class ColorDisplayService extends SystemService {
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public boolean setSaturationLevel(int level) {
+ final boolean hasTransformsPermission = getContext()
+ .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ == PackageManager.PERMISSION_GRANTED;
+ final boolean hasLegacyPermission = getContext()
+ .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION)
+ == PackageManager.PERMISSION_GRANTED;
+ if (!hasTransformsPermission && !hasLegacyPermission) {
+ throw new SecurityException("Permission required to set display saturation level");
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final Message message = mHandler.obtainMessage(MSG_APPLY_GLOBAL_SATURATION);
+ message.arg1 = level;
+ mHandler.sendMessage(message);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return true;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b1ba05c035b3..4a17c6591449 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -724,27 +724,6 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void setSaturationLevelInternal(float level) {
- if (level < 0 || level > 1) {
- throw new IllegalArgumentException("Saturation level must be between 0 and 1");
- }
- float[] matrix = (level == 1.0f ? null : computeSaturationMatrix(level));
- DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
- dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION, matrix);
- }
-
- private static float[] computeSaturationMatrix(float saturation) {
- float desaturation = 1.0f - saturation;
- float[] luminance = {0.231f * desaturation, 0.715f * desaturation, 0.072f * desaturation};
- float[] matrix = {
- luminance[0] + saturation, luminance[0], luminance[0], 0,
- luminance[1], luminance[1] + saturation, luminance[1], 0,
- luminance[2], luminance[2], luminance[2] + saturation, 0,
- 0, 0, 0, 1
- };
- return matrix;
- }
-
private int createVirtualDisplayInternal(IVirtualDisplayCallback callback,
IMediaProjection projection, int callingUid, String packageName, String name, int width,
int height, int densityDpi, Surface surface, int flags, String uniqueId) {
@@ -1836,19 +1815,6 @@ public final class DisplayManagerService extends SystemService {
}
@Override // Binder call
- public void setSaturationLevel(float level) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.CONTROL_DISPLAY_SATURATION,
- "Permission required to set display saturation level");
- final long token = Binder.clearCallingIdentity();
- try {
- setSaturationLevelInternal(level);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override // Binder call
public int createVirtualDisplay(IVirtualDisplayCallback callback,
IMediaProjection projection, String packageName, String name,
int width, int height, int densityDpi, Surface surface, int flags,
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 1b0eb19df598..6f5a19612895 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -256,6 +256,41 @@ final class Constants {
static final int USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON = 1;
static final int NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON = 2;
+ // Port id to record local active port for Routing Control features
+ // They are used to map to corresponding Inputs
+ // Current interface is only implemented for specific device.
+ // Developers can add more port number and map them to corresponding inputs on demand.
+ @IntDef({
+ CEC_SWITCH_HOME,
+ CEC_SWITCH_HDMI1,
+ CEC_SWITCH_HDMI2,
+ CEC_SWITCH_HDMI3,
+ CEC_SWITCH_HDMI4,
+ CEC_SWITCH_HDMI5,
+ CEC_SWITCH_HDMI6,
+ CEC_SWITCH_HDMI7,
+ CEC_SWITCH_HDMI8,
+ CEC_SWITCH_ARC,
+ CEC_SWITCH_BLUETOOTH,
+ CEC_SWITCH_OPTICAL,
+ CEC_SWITCH_AUX
+ })
+ @interface LocalActivePort {}
+ static final int CEC_SWITCH_HOME = 0;
+ static final int CEC_SWITCH_HDMI1 = 1;
+ static final int CEC_SWITCH_HDMI2 = 2;
+ static final int CEC_SWITCH_HDMI3 = 3;
+ static final int CEC_SWITCH_HDMI4 = 4;
+ static final int CEC_SWITCH_HDMI5 = 5;
+ static final int CEC_SWITCH_HDMI6 = 6;
+ static final int CEC_SWITCH_HDMI7 = 7;
+ static final int CEC_SWITCH_HDMI8 = 8;
+ static final int CEC_SWITCH_ARC = 17;
+ static final int CEC_SWITCH_BLUETOOTH = 18;
+ static final int CEC_SWITCH_OPTICAL = 19;
+ static final int CEC_SWITCH_AUX = 20;
+ static final int CEC_SWITCH_PORT_MAX = 21;
+
static final String PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM =
"persist.sys.hdmi.addr.audiosystem";
static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback";
@@ -274,6 +309,13 @@ final class Constants {
static final String PROPERTY_SET_MENU_LANGUAGE = "ro.hdmi.set_menu_language";
/**
+ * Property to save the ARC port id on system audio device.
+ * <p>When ARC is initiated, this port will be used to turn on ARC.
+ */
+ static final String PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT =
+ "ro.hdmi.property_sytem_audio_device_arc_port";
+
+ /**
* Property to disable muting logic in System Audio Control handling. Default is true.
*
* <p>True means enabling muting logic.
@@ -291,6 +333,24 @@ final class Constants {
static final String PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS =
"ro.hdmi.property_hdmi_cec_never_claim_playback_logical_address";
+ /**
+ * A comma separated list of logical addresses that HdmiControlService
+ * will never assign local CEC devices to.
+ *
+ * <p> This is useful when HDMI CEC hardware module can't assign multiple logical addresses
+ * in the range same range of 0-7 or 8-15.
+ */
+ static final String PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES =
+ "ro.hdmi.property_hdmi_cec_never_assign_logical_addresses";
+
+ /**
+ * Property to indicate if the current device is a cec switch device.
+ *
+ * <p> Default is false.
+ */
+ static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH =
+ "ro.hdmi.property_is_device_hdmi_cec_switch";
+
// Set to false to allow playback device to go to suspend mode even
// when it's an active source. True by default.
static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake";
@@ -331,13 +391,6 @@ final class Constants {
"persist.sys.hdmi.property_sytem_audio_mode_audio_port";
/**
- * Property to save the ARC port id on system audio device.
- * <p>When ARC is initiated, this port will be used to turn on ARC.
- */
- static final String PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT =
- "persist.sys.hdmi.property_sytem_audio_device_arc_port";
-
- /**
* Property to indicate if a CEC audio device should forward volume keys when system audio mode
* is off.
*
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index de6cdd48fb09..e777ce8166ac 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -22,20 +22,25 @@ import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
+import android.os.SystemProperties;
import android.util.Slog;
import android.util.SparseArray;
+
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
+
+import libcore.util.EmptyArray;
+
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
-import java.util.function.Predicate;
import java.util.concurrent.ArrayBlockingQueue;
-import libcore.util.EmptyArray;
+import java.util.function.Predicate;
+
import sun.util.locale.LanguageTag;
/**
@@ -112,10 +117,18 @@ final class HdmiCecController {
private final NativeWrapper mNativeWrapperImpl;
+ /** List of logical addresses that should not be assigned to the current device.
+ *
+ * <p>Parsed from {@link Constants#PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES}
+ */
+ private final List<Integer> mNeverAssignLogicalAddresses;
+
// Private constructor. Use HdmiCecController.create().
private HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper) {
mService = service;
mNativeWrapperImpl = nativeWrapper;
+ mNeverAssignLogicalAddresses = mService.getIntList(SystemProperties.get(
+ Constants.PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES));
}
/**
@@ -208,7 +221,8 @@ final class HdmiCecController {
for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
if (curAddress != Constants.ADDR_UNREGISTERED
- && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) {
+ && deviceType == HdmiUtils.getTypeFromAddress(curAddress)
+ && !mNeverAssignLogicalAddresses.contains(curAddress)) {
boolean acked = false;
for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) {
if (sendPollMessage(curAddress, curAddress, 1)) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index c338e21105b8..528e0a4e4940 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -30,6 +30,7 @@ import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.hdmi.Constants.LocalActivePort;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
@@ -1051,7 +1052,7 @@ abstract class HdmiCecLocalDevice {
* <p>This check assumes target address is valid.
* @param targetPhysicalAddress is the physical address of the target device
* @return
- * <p>If the target device is under the current device, return the port number of current device
+ * If the target device is under the current device, return the port number of current device
* that the target device is connected to.
*
* <p>If the target device has the same physical address as the current device, return
@@ -1085,4 +1086,27 @@ abstract class HdmiCecLocalDevice {
}
return TARGET_NOT_UNDER_LOCAL_DEVICE;
}
+
+ /** Calculates the physical address for {@code activePortId}.
+ *
+ * <p>This method assumes current device physical address is valid.
+ * <p>If the current device is already the leaf of the whole CEC system
+ * and can't have devices under it, will return its own physical address.
+ *
+ * @param activePortId is the local active port Id
+ * @return the calculated physical address of the port
+ */
+ protected int getActivePathOnSwitchFromActivePortId(@LocalActivePort int activePortId) {
+ int myPhysicalAddress = mService.getPhysicalAddress();
+ int finalMask = activePortId << 8;
+ int mask;
+ for (mask = 0x0F00; mask > 0x000F; mask >>= 4) {
+ if ((myPhysicalAddress & mask) == 0) {
+ break;
+ } else {
+ finalMask >>= 4;
+ }
+ }
+ return finalMask | myPhysicalAddress;
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 20908b6d21e2..048a0e413a9b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -20,10 +20,12 @@ import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON
import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
import android.annotation.Nullable;
+import android.content.Intent;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioSystem;
+import android.media.tv.TvContract;
import android.os.SystemProperties;
import android.provider.Settings.Global;
@@ -32,6 +34,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.hdmi.Constants.AudioCodec;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+import java.util.HashMap;
+
/**
* Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
* system.
@@ -60,16 +64,16 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
// AVR as audio receiver.
@ServiceThreadOnly private boolean mArcEstablished = false;
- /**
- * Return value of {@link #getLocalPortFromPhysicalAddress(int)}
- */
- private static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1;
- private static final int TARGET_SAME_PHYSICAL_ADDRESS = 0;
+ // If the current device uses TvInput for ARC. We assume all other inputs also use TvInput
+ // when ARC is using TvInput.
+ private boolean mArcIntentUsed = SystemProperties
+ .get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT, "0").contains("tvinput");
- // Local active port number used for Routing Control.
- // Default 0 means HOME is the current active path. Temp solution only.
- // TODO(amyjojo): adding system constants for Atom inputs port and TIF mapping.
- private int mLocalActivePath = 0;
+ // Keeps the mapping (HDMI port ID to TV input URI) to keep track of the TV inputs ready to
+ // accept input switching request from HDMI devices. Requests for which the corresponding
+ // input ID is not yet registered by TV input framework need to be buffered for delayed
+ // processing.
+ private final HashMap<Integer, String> mTvInputs = new HashMap<>();
protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
@@ -81,6 +85,13 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true);
mAutoTvOff = mService.readBooleanSetting(
Global.HDMI_CONTROL_AUTO_TV_OFF_ENABLED, true);
+ // TODO(amyjojo): make the map ro property.
+ mTvInputs.put(Constants.CEC_SWITCH_HDMI1,
+ "com.droidlogic.tvinput/.services.Hdmi1InputService/HW5");
+ mTvInputs.put(Constants.CEC_SWITCH_HDMI2,
+ "com.droidlogic.tvinput/.services.Hdmi2InputService/HW6");
+ mTvInputs.put(Constants.CEC_SWITCH_HDMI3,
+ "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
}
@Override
@@ -595,4 +606,135 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
assertRunOnServiceThread();
mAutoDeviceOff = autoDeviceOff;
}
+
+ @Override
+ protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
+ int port = getLocalPortFromPhysicalAddress(physicalAddress);
+ // Wake up if the new Active Source is the current device or under it
+ // or if System Audio Control is enabled.
+ if ((isSystemAudioActivated() || port >= 0) && mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+
+ if (isSystemAudioActivated() && port < 0) {
+ // If system audio mode is on and the new active source is not under the current device,
+ // Will switch to ARC input.
+ // TODO(b/115637145): handle system aduio without ARC
+ routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
+ } else if (mIsSwitchDevice && port >= 0) {
+ // If current device is a switch and the new active source is under it,
+ // will switch to the corresponding active path.
+ routeToInputFromPortId(port);
+ }
+ }
+
+ protected void routeToInputFromPortId(int portId) {
+ if (mArcIntentUsed) {
+ routeToTvInputFromPortId(portId);
+ } else {
+ // TODO(): implement input switching for devices not using TvInput.
+ }
+ }
+
+ protected void routeToTvInputFromPortId(int portId) {
+ if (portId < 0 || portId >= Constants.CEC_SWITCH_PORT_MAX) {
+ HdmiLogger.debug("Invalid port number for Tv Input switching.");
+ return;
+ }
+ // TODO(amyjojo): handle if switching to the current input
+ if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
+ switchToHomeTvInput();
+ } else if (portId == Constants.CEC_SWITCH_ARC) {
+ switchToTvInput(SystemProperties.get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT));
+ return;
+ } else {
+ String uri = mTvInputs.get(portId);
+ if (uri != null) {
+ switchToTvInput(mTvInputs.get(portId));
+ } else {
+ HdmiLogger.debug("Port number does not match any Tv Input.");
+ return;
+ }
+ }
+
+ setLocalActivePort(portId);
+ }
+
+ // For device to switch to specific TvInput with corresponding URI.
+ private void switchToTvInput(String uri) {
+ mService.getContext().startActivity(new Intent(Intent.ACTION_VIEW,
+ TvContract.buildChannelUriForPassthroughInput(uri))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ // For device using TvInput to switch to Home.
+ private void switchToHomeTvInput() {
+ Intent activityIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP
+ | Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ mService.getContext().startActivity(activityIntent);
+ }
+
+ @Override
+ protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
+ int port = getLocalPortFromPhysicalAddress(physicalAddress);
+ // Routing change or information sent from switches under the current device can be ignored.
+ if (port > 0) {
+ return;
+ }
+ // When other switches route to some other devices not under the current device,
+ // check system audio mode status and do ARC switch if needed.
+ if (port < 0 && isSystemAudioActivated()) {
+ handleRoutingChangeAndInformationForSystemAudio();
+ return;
+ }
+ // When other switches route to the current device
+ // and the current device is also a switch.
+ if (port == 0) {
+ handleRoutingChangeAndInformationForSwitch(message);
+ }
+ }
+
+ // Handle the system audio(ARC) part of the logic on receiving routing change or information.
+ private void handleRoutingChangeAndInformationForSystemAudio() {
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ // TODO(b/115637145): handle system aduio without ARC
+ routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
+ }
+
+ // Handle the routing control part of the logic on receiving routing change or information.
+ private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) {
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ if (getLocalActivePort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
+ routeToInputFromPortId(Constants.CEC_SWITCH_HOME);
+ if (mService.playback() != null) {
+ mService.playback().setAndBroadcastActiveSource(
+ message, mService.getPhysicalAddress());
+ } else {
+ setAndBroadcastActiveSource(message, mService.getPhysicalAddress());
+ }
+ return;
+ }
+
+ int routingInformationPath =
+ getActivePathOnSwitchFromActivePortId(getLocalActivePort());
+ // If current device is already the leaf of the whole HDMI system, will do nothing.
+ if (routingInformationPath == mService.getPhysicalAddress()) {
+ HdmiLogger.debug("Current device can't assign valid physical address"
+ + "to devices under it any more. "
+ + "It's physical address is " + routingInformationPath);
+ return;
+ }
+ // Otherwise will switch to the current active port and broadcast routing information.
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingInformation(
+ mAddress, routingInformationPath));
+ routeToInputFromPortId(getLocalActivePort());
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index e9dd6822366e..379cc1610197 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -266,7 +266,8 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
}
- private void wakeUpIfActiveSource() {
+ @Override
+ protected void wakeUpIfActiveSource() {
if (!mIsActiveSource) {
return;
}
@@ -277,7 +278,8 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
}
}
- private void maySendActiveSource(int dest) {
+ @Override
+ protected void maySendActiveSource(int dest) {
if (mIsActiveSource) {
mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
mAddress, mService.getPhysicalAddress()));
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index fed66225f839..8d55299243b2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -19,8 +19,12 @@ package com.android.server.hdmi;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.hdmi.Constants.LocalActivePort;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import java.util.List;
@@ -35,6 +39,20 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
// Indicate if current device is Active Source or not
private boolean mIsActiveSource = false;
+ // Device has cec switch functionality or not.
+ // Default is false.
+ protected boolean mIsSwitchDevice = SystemProperties.getBoolean(
+ Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
+
+ // Local active port number used for Routing Control.
+ // This records the default active port or the previous valid active port.
+ // Default is HOME input.
+ // Note that we don't save active path here because for source device,
+ // new Active Source physical address might not match the local active path
+ @GuardedBy("mLock")
+ @LocalActivePort
+ private int mLocalActivePort = Constants.CEC_SWITCH_HOME;
+
protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) {
super(service, deviceType);
}
@@ -99,6 +117,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
setActiveSource(activeSource);
}
setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
+ switchInputOnReceivingNewActivePath(physicalAddress);
return true;
}
@@ -106,16 +125,130 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
@ServiceThreadOnly
protected boolean handleRequestActiveSource(HdmiCecMessage message) {
assertRunOnServiceThread();
- if (mIsActiveSource) {
- mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
- mAddress, mService.getPhysicalAddress()));
+ maySendActiveSource(message.getSource());
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleSetStreamPath(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+ // If current device is the target path, set to Active Source.
+ // If the path is under the current device, should switch
+ if (physicalAddress == mService.getPhysicalAddress() && mService.isPlaybackDevice()) {
+ setAndBroadcastActiveSource(message, physicalAddress);
+ }
+ switchInputOnReceivingNewActivePath(physicalAddress);
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleRoutingChange(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
+ // if the current device is a pure playback device
+ if (!mIsSwitchDevice
+ && newPath == mService.getPhysicalAddress()
+ && mService.isPlaybackDevice()) {
+ setAndBroadcastActiveSource(message, newPath);
}
+ handleRoutingChangeAndInformation(newPath, message);
return true;
}
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleRoutingInformation(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+ // if the current device is a pure playback device
+ if (!mIsSwitchDevice
+ && physicalAddress == mService.getPhysicalAddress()
+ && mService.isPlaybackDevice()) {
+ setAndBroadcastActiveSource(message, physicalAddress);
+ }
+ handleRoutingChangeAndInformation(physicalAddress, message);
+ return true;
+ }
+
+ // Method to switch Input with the new Active Path.
+ // All the devices with Switch functionality should implement this.
+ protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
+ // do nothing
+ }
+
+ // Source device with Switch functionality should implement this method.
+ // TODO(): decide which type will handle the routing when multi device type is supported
+ protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
+ // do nothing
+ }
+
+ // Active source claiming needs to be handled in the parent class
+ // since we decide who will be the active source when the device supports
+ // multiple device types in this method.
+ // This method should only be called when the device can be the active source.
+ protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress) {
+ // If the device has both playback and audio system logical addresses,
+ // playback will claim active source. Otherwise audio system will.
+ HdmiCecLocalDevice deviceToBeActiveSource = mService.playback();
+ if (deviceToBeActiveSource == null) {
+ deviceToBeActiveSource = mService.audioSystem();
+ }
+ if (this == deviceToBeActiveSource) {
+ ActiveSource activeSource = ActiveSource.of(mAddress, physicalAddress);
+ setIsActiveSource(true);
+ setActiveSource(activeSource);
+ wakeUpIfActiveSource();
+ maySendActiveSource(message.getSource());
+ }
+ }
+
@ServiceThreadOnly
void setIsActiveSource(boolean on) {
assertRunOnServiceThread();
mIsActiveSource = on;
}
+
+ @ServiceThreadOnly
+ // Check if current device is the Active Source
+ boolean isActiveSource() {
+ assertRunOnServiceThread();
+ return mIsActiveSource;
+ }
+
+ protected void wakeUpIfActiveSource() {
+ if (!mIsActiveSource) {
+ return;
+ }
+ // Wake up the device if the power is in standby mode
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ return;
+ }
+
+ protected void maySendActiveSource(int dest) {
+ if (mIsActiveSource) {
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
+ mAddress, mService.getPhysicalAddress()));
+ }
+ }
+
+ @VisibleForTesting
+ protected void setLocalActivePort(@LocalActivePort int portId) {
+ synchronized (mLock) {
+ mLocalActivePort = portId;
+ }
+ }
+
+ // To get the local active port to switch to
+ // when receivng routing change or information.
+ @LocalActivePort
+ protected int getLocalActivePort() {
+ synchronized (mLock) {
+ return mLocalActivePort;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 7e369598a9b1..903045d9b68f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -417,7 +417,7 @@ public class HdmiControlService extends SystemService {
mSettingsObserver = new SettingsObserver(mHandler);
}
- private static List<Integer> getIntList(String string) {
+ protected static List<Integer> getIntList(String string) {
ArrayList<Integer> list = new ArrayList<>();
TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
splitter.setString(string);
@@ -2114,11 +2114,15 @@ public class HdmiControlService extends SystemService {
return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
}
+ boolean isPlaybackDevice() {
+ return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
+ }
+
boolean isTvDeviceEnabled() {
return isTvDevice() && tv() != null;
}
- private HdmiCecLocalDevicePlayback playback() {
+ protected HdmiCecLocalDevicePlayback playback() {
return (HdmiCecLocalDevicePlayback)
mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
}
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 48c71d8b738a..46611dd1b66e 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -85,7 +85,11 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
private void broadcastActiveSource() {
sendCommand(HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(), getSourcePath()));
// Because only source device can create this action, it's safe to cast.
- source().setIsActiveSource(true);
+ HdmiCecLocalDeviceSource source = source();
+ source.setIsActiveSource(true);
+ source.setActiveSource(getSourceAddress(), getSourcePath());
+ // Set local active port to HOME when One Touch Play.
+ source.setLocalActivePort(Constants.CEC_SWITCH_HOME);
}
private void queryDevicePowerStatus() {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index e7c3c7bbe21b..45a9bc01223c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -213,8 +213,6 @@ public class InputManagerService extends IInputManager.Stub
private static native void nativeSetFocusedApplication(long ptr,
int displayId, InputApplicationHandle application);
private static native void nativeSetFocusedDisplay(long ptr, int displayId);
- private static native boolean nativeTransferTouchFocus(long ptr,
- InputChannel fromChannel, InputChannel toChannel);
private static native void nativeSetPointerSpeed(long ptr, int speed);
private static native void nativeSetShowTouches(long ptr, boolean enabled);
private static native void nativeSetInteractive(long ptr, boolean interactive);
@@ -1485,29 +1483,6 @@ public class InputManagerService extends IInputManager.Stub
nativeSetSystemUiVisibility(mPtr, visibility);
}
- /**
- * Atomically transfers touch focus from one window to another as identified by
- * their input channels. It is possible for multiple windows to have
- * touch focus if they support split touch dispatch
- * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
- * method only transfers touch focus of the specified window without affecting
- * other windows that may also have touch focus at the same time.
- * @param fromChannel The channel of a window that currently has touch focus.
- * @param toChannel The channel of the window that should receive touch focus in
- * place of the first.
- * @return True if the transfer was successful. False if the window with the
- * specified channel did not actually have touch focus at the time of the request.
- */
- public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
- if (fromChannel == null) {
- throw new IllegalArgumentException("fromChannel must not be null.");
- }
- if (toChannel == null) {
- throw new IllegalArgumentException("toChannel must not be null.");
- }
- return nativeTransferTouchFocus(mPtr, fromChannel, toChannel);
- }
-
@Override // Binder call
public void tryPointerSpeed(int speed) {
if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED,
@@ -1786,14 +1761,13 @@ public class InputManagerService extends IInputManager.Stub
// Native callback
private void notifyFocusChanged(IBinder token) {
- if (mFocusedWindow != token) {
- if (mFocusedWindowHasCapture) {
- setPointerCapture(false);
- }
- if (token instanceof IWindow) {
- mFocusedWindow = (IWindow) token;
- }
+ if (mFocusedWindow.asBinder() == token) {
+ Log.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow=" + mFocusedWindow);
+ return;
}
+
+ setPointerCapture(false);
+ mFocusedWindow = IWindow.Stub.asInterface(token);
}
// Native callback.
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index 3b11525e7cda..8facce112b52 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -42,6 +42,7 @@ import android.content.pm.ServiceInfo;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.Log;
import android.util.LogPrinter;
import android.util.Pair;
@@ -60,6 +61,7 @@ import java.util.Set;
/** Resolves all Android component types [activities, services, providers and receivers]. */
public class ComponentResolver {
+ private static final boolean DEBUG = false;
private static final String TAG = "PackageManager";
private static final boolean DEBUG_FILTERS = false;
private static final boolean DEBUG_SHOW_INFO = false;
@@ -1198,22 +1200,48 @@ public class ComponentResolver {
return packageName.equals(info.activity.owner.packageName);
}
+ private void log(String reason, ActivityIntentInfo info, int match,
+ int userId) {
+ Slog.w(TAG, reason
+ + "; match: "
+ + DebugUtils.flagsToString(IntentFilter.class, "MATCH_", match)
+ + "; userId: " + userId
+ + "; intent info: " + info);
+ }
+
@Override
protected ResolveInfo newResult(PackageParser.ActivityIntentInfo info,
int match, int userId) {
- if (!sUserManager.exists(userId)) return null;
+ if (!sUserManager.exists(userId)) {
+ if (DEBUG) {
+ log("User doesn't exist", info, match, userId);
+ }
+ return null;
+ }
if (!sPackageManagerInternal.isEnabledAndMatches(info.activity.info, mFlags, userId)) {
+ if (DEBUG) {
+ log("!PackageManagerInternal.isEnabledAndMatches; mFlags="
+ + DebugUtils.flagsToString(PackageManager.class, "MATCH_", mFlags),
+ info, match, userId);
+ }
return null;
}
final PackageParser.Activity activity = info.activity;
PackageSetting ps = (PackageSetting) activity.owner.mExtras;
if (ps == null) {
+ if (DEBUG) {
+ log("info.activity.owner.mExtras == null", info, match, userId);
+ }
return null;
}
final PackageUserState userState = ps.readUserState(userId);
ActivityInfo ai =
PackageParser.generateActivityInfo(activity, mFlags, userState, userId);
if (ai == null) {
+ if (DEBUG) {
+ log("Failed to create ActivityInfo based on " + info.activity, info, match,
+ userId);
+ }
return null;
}
final boolean matchExplicitlyVisibleOnly =
@@ -1227,15 +1255,31 @@ public class ComponentResolver {
final boolean matchInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
// throw out filters that aren't visible to ephemeral apps
if (matchVisibleToInstantApp && !(componentVisible || userState.instantApp)) {
+ if (DEBUG) {
+ log("Filter(s) not visible to ephemeral apps"
+ + "; matchVisibleToInstantApp=" + matchVisibleToInstantApp
+ + "; matchInstantApp=" + matchInstantApp
+ + "; info.isVisibleToInstantApp()=" + info.isVisibleToInstantApp()
+ + "; matchExplicitlyVisibleOnly=" + matchExplicitlyVisibleOnly
+ + "; info.isExplicitlyVisibleToInstantApp()="
+ + info.isExplicitlyVisibleToInstantApp(),
+ info, match, userId);
+ }
return null;
}
// throw out instant app filters if we're not explicitly requesting them
if (!matchInstantApp && userState.instantApp) {
+ if (DEBUG) {
+ log("Instant app filter is not explicitly requested", info, match, userId);
+ }
return null;
}
// throw out instant app filters if updates are available; will trigger
// instant app resolution
if (userState.instantApp && ps.isUpdateAvailable()) {
+ if (DEBUG) {
+ log("Instant app update is available", info, match, userId);
+ }
return null;
}
final ResolveInfo res = new ResolveInfo();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 33019627efda..2dc543712f0c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -13562,7 +13562,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
Signature[] callerSignature;
- Object obj = mSettings.getSettingLPr(callingUid);
+ final int appId = UserHandle.getAppId(callingUid);
+ final Object obj = mSettings.getSettingLPr(appId);
if (obj != null) {
if (obj instanceof SharedUserSetting) {
callerSignature =
diff --git a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
new file mode 100644
index 000000000000..45c975b26956
--- /dev/null
+++ b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.policy.role;
+
+import android.annotation.NonNull;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.os.Debug;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.telephony.SmsApplication;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.role.RoleManagerService;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Logic to retrieve the various legacy(pre-Q) equivalents of role holders.
+ *
+ * Unlike {@link RoleManagerService} this is meant to be pretty high-level to allow for depending
+ * on all kinds of various systems that are historically involved in legacy role resolution,
+ * e.g. {@link SmsApplication}
+ *
+ * @see RoleManagerService#migrateRoleIfNecessary
+ */
+public class LegacyRoleResolutionPolicy implements RoleManagerService.RoleHoldersResolver {
+
+ private static final boolean DEBUG = false;
+ private static final String LOG_TAG = "LegacyRoleResolutionPol";
+
+ @NonNull
+ private final Context mContext;
+
+ public LegacyRoleResolutionPolicy(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public List<String> getRoleHolders(String roleName, int userId) {
+ switch (roleName) {
+ case RoleManager.ROLE_SMS: {
+ // Moved over from SmsApplication#getApplication
+ String result = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+
+ if (result == null) {
+ Collection<SmsApplication.SmsApplicationData> applications =
+ SmsApplication.getApplicationCollectionAsUser(mContext, userId);
+ SmsApplication.SmsApplicationData applicationData;
+ String defaultPackage = mContext.getResources()
+ .getString(com.android.internal.R.string.default_sms_application);
+ applicationData =
+ SmsApplication.getApplicationForPackage(applications, defaultPackage);
+
+ if (applicationData == null) {
+ // Are there any applications?
+ if (applications.size() != 0) {
+ applicationData =
+ (SmsApplication.SmsApplicationData) applications.toArray()[0];
+ }
+ }
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Found default sms app: " + applicationData
+ + " among: " + applications + " from " + Debug.getCallers(4));
+ }
+ SmsApplication.SmsApplicationData app = applicationData;
+ result = app == null ? null : app.mPackageName;
+ }
+
+ return CollectionUtils.singletonOrEmpty(result);
+ }
+ default: {
+ Slog.e(LOG_TAG, "Don't know how to find legacy role holders for " + roleName);
+ return Collections.emptyList();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index 244ccb69e958..5cbe74c5986c 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -1,6 +1,4 @@
michaelwr@google.com
santoscordon@google.com
-per-file BatterySaverPolicy.java=omakoto@google.com
-per-file ShutdownThread.java=fkupolov@google.com
-per-file ThermalManagerService.java=wvw@google.com \ No newline at end of file
+per-file ThermalManagerService.java=wvw@google.com
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 565bb706a5d8..a02787308246 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -96,6 +96,7 @@ import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.batterysaver.BatterySaverController;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
import com.android.server.power.batterysaver.BatterySaverStateMachine;
import com.android.server.power.batterysaver.BatterySavingStats;
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 6400c88b320f..1d74350de978 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -35,13 +35,13 @@ import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
-import com.android.server.power.BatterySaverPolicy;
-import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
import com.android.server.power.PowerManagerService;
+import com.android.server.power.batterysaver.BatterySaverPolicy.BatterySaverPolicyListener;
import com.android.server.power.batterysaver.BatterySavingStats.BatterySaverState;
import com.android.server.power.batterysaver.BatterySavingStats.DozeState;
import com.android.server.power.batterysaver.BatterySavingStats.InteractiveState;
@@ -158,10 +158,9 @@ public class BatterySaverController implements BatterySaverPolicyListener {
mBatterySavingStats = batterySavingStats;
// Initialize plugins.
- final ArrayList<Plugin> plugins = new ArrayList<>();
- plugins.add(new BatterySaverLocationPlugin(mContext));
-
- mPlugins = plugins.toArray(new Plugin[plugins.size()]);
+ mPlugins = new Plugin[] {
+ new BatterySaverLocationPlugin(mContext)
+ };
}
/**
@@ -217,7 +216,7 @@ public class BatterySaverController implements BatterySaverPolicyListener {
super(looper);
}
- public void postStateChanged(boolean sendBroadcast, int reason) {
+ void postStateChanged(boolean sendBroadcast, int reason) {
obtainMessage(MSG_STATE_CHANGED, sendBroadcast ?
ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, reason).sendToTarget();
}
@@ -244,9 +243,8 @@ public class BatterySaverController implements BatterySaverPolicyListener {
}
}
- /**
- * Called by {@link PowerManagerService} to update the battery saver state.
- */
+ /** Enable or disable full battery saver. */
+ @VisibleForTesting
public void enableBatterySaver(boolean enable, int reason) {
synchronized (mLock) {
if (mEnabled == enable) {
@@ -311,7 +309,7 @@ public class BatterySaverController implements BatterySaverPolicyListener {
reason);
mPreviouslyEnabled = mEnabled;
- listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
+ listeners = mListeners.toArray(new LowPowerModeListener[0]);
enabled = mEnabled;
mIsInteractive = isInteractive;
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index cedb54801dc8..48a041eaab28 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server.power;
+package com.android.server.power.batterysaver;
import android.content.ContentResolver;
import android.content.Context;
@@ -36,21 +36,19 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ConcurrentUtils;
-import com.android.server.power.batterysaver.BatterySavingStats;
-import com.android.server.power.batterysaver.CpuFrequencies;
+import com.android.server.power.PowerManagerService;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
- * Class to decide whether to turn on battery saver mode for specific service
+ * Class to decide whether to turn on battery saver mode for specific services.
*
* IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy.
* Do not call out with the lock held, such as AccessibilityManager. (Settings provider is okay.)
*
- * Test:
- atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+ * Test: atest com.android.server.power.batterysaver.BatterySaverPolicyTest.java
*/
public class BatterySaverPolicy extends ContentObserver {
private static final String TAG = "BatterySaverPolicy";
@@ -200,8 +198,10 @@ public class BatterySaverPolicy extends ContentObserver {
onChange(true, null);
}
+ @VisibleForTesting
public void addListener(BatterySaverPolicyListener listener) {
synchronized (mLock) {
+ // TODO: set this in the constructor instead
mListeners.add(listener);
}
}
@@ -244,7 +244,7 @@ public class BatterySaverPolicy extends ContentObserver {
// Update.
updateConstantsLocked(setting, deviceSpecificSetting);
- listeners = mListeners.toArray(new BatterySaverPolicyListener[mListeners.size()]);
+ listeners = mListeners.toArray(new BatterySaverPolicyListener[0]);
}
// Notify the listeners.
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index a60f16d7ca27..b7f28da499e9 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -37,7 +37,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
-import com.android.server.power.BatterySaverPolicy;
import com.android.server.power.BatterySaverStateMachineProto;
import java.io.PrintWriter;
@@ -229,8 +228,8 @@ public class BatterySaverStateMachine {
}
/**
- * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable},
- * it'll be first removed before a new one is posted.
+ * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable} is
+ * already registered, it'll be first removed before being re-posted.
*/
@VisibleForTesting
void runOnBgThreadLazy(Runnable r, int delayMillis) {
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
index 821320559017..79b44ebd8645 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
@@ -29,7 +29,6 @@ import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
-import com.android.server.power.BatterySaverPolicy;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index f37ca12bbd7f..b4883371b468 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -35,6 +35,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
+import android.os.Binder;
import android.os.Handler;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -92,6 +93,15 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
@NonNull
private final Object mLock = new Object();
+ @NonNull
+ private final RoleHoldersResolver mLegacyRoleResolver;
+
+ /** @see #getRoleHolders(String, int) */
+ public interface RoleHoldersResolver {
+ /** @return a list of packages that hold a given role for a given user */
+ List<String> getRoleHolders(String roleName, int userId);
+ }
+
/**
* Maps user id to its state.
*/
@@ -118,9 +128,12 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
@NonNull
private final Handler mListenerHandler = FgThread.getHandler();
- public RoleManagerService(@NonNull Context context) {
+ public RoleManagerService(@NonNull Context context,
+ @NonNull RoleHoldersResolver legacyRoleResolver) {
super(context);
+ mLegacyRoleResolver = legacyRoleResolver;
+
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mAppOpsManager = context.getSystemService(AppOpsManager.class);
@@ -175,10 +188,17 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
private void performInitialGrantsIfNecessary(@UserIdInt int userId) {
RoleUserState userState;
userState = getOrCreateUserState(userId);
+
String packagesHash = computeComponentStateHash(userId);
String oldPackagesHash = userState.getPackagesHash();
boolean needGrant = !Objects.equals(packagesHash, oldPackagesHash);
if (needGrant) {
+
+ //TODO gradually add more role migrations statements here for remaining roles
+ // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders
+ // for a given role before adding a migration statement for it here
+ migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId);
+
// Some vital packages state has changed since last role grant
// Run grants again
Slog.i(LOG_TAG, "Granting default permissions...");
@@ -205,6 +225,20 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
}
}
+ private void migrateRoleIfNecessary(String role, @UserIdInt int userId) {
+ // Any role for which we have a record are already migrated
+ RoleUserState userState = getOrCreateUserState(userId);
+ if (!userState.isRoleAvailable(role)) {
+ userState.addRoleName(role);
+ List<String> roleHolders = mLegacyRoleResolver.getRoleHolders(role, userId);
+ Slog.i(LOG_TAG, "Migrating " + role + ", legacy holders: " + roleHolders);
+ int size = roleHolders.size();
+ for (int i = 0; i < size; i++) {
+ userState.addRoleHolder(role, roleHolders.get(i));
+ }
+ }
+ }
+
@Nullable
private static String computeComponentStateHash(@UserIdInt int userId) {
PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
@@ -372,6 +406,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
@Nullable
private ArraySet<String> getRoleHoldersInternal(@NonNull String roleName,
@UserIdInt int userId) {
+ migrateRoleIfNecessary(roleName, userId);
RoleUserState userState = getOrCreateUserState(userId);
return userState.getRoleHolders(roleName);
}
@@ -530,6 +565,17 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
}
@Override
+ public String getDefaultSmsPackage(int userId) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return CollectionUtils.firstOrNull(
+ getRoleHoldersAsUser(RoleManager.ROLE_SMS, userId));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), LOG_TAG, fout)) {
diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java
index 630a39caeb08..69e144951154 100644
--- a/services/core/java/com/android/server/role/RoleUserState.java
+++ b/services/core/java/com/android/server/role/RoleUserState.java
@@ -205,6 +205,28 @@ public class RoleUserState {
}
/**
+ * Adds the given role, effectively marking it as {@link #isRoleAvailable available}
+ *
+ * @param roleName the name of the role
+ *
+ * @return whether any changes were made
+ */
+ public boolean addRoleName(@NonNull String roleName) {
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+
+ if (!mRoles.containsKey(roleName)) {
+ mRoles.put(roleName, new ArraySet<>());
+ Slog.i(LOG_TAG, "Added new role: " + roleName);
+ scheduleWriteFileLocked();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
* Set the names of all available roles.
*
* @param roleNames the names of all the available roles
@@ -231,13 +253,7 @@ public class RoleUserState {
int roleNamesSize = roleNames.size();
for (int i = 0; i < roleNamesSize; i++) {
- String roleName = roleNames.get(i);
-
- if (!mRoles.containsKey(roleName)) {
- mRoles.put(roleName, new ArraySet<>());
- Slog.i(LOG_TAG, "Added new role: " + roleName);
- changed = true;
- }
+ changed |= addRoleName(roleNames.get(i));
}
if (changed) {
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 2cab63aa680a..ef771406805b 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -510,7 +510,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi
Slog.d(LOG_TAG, "Binding to " + serviceIntent.getComponent());
willBind = mContext.bindServiceAsUser(
serviceIntent, mConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_RESTRICT_ASSOCIATIONS,
UserHandle.of(mUserId));
mBinding = willBind;
} finally {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6f8f85f1af26..f8f0d1cfcac8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -390,6 +390,11 @@ final class ActivityRecord extends ConfigurationContainer {
private boolean mTurnScreenOn;
/**
+ * Current sequencing integer of the configuration, for skipping old activity configurations.
+ */
+ private int mConfigurationSeq;
+
+ /**
* Temp configs used in {@link #ensureActivityConfiguration(int, boolean)}
*/
private final Configuration mTmpConfig = new Configuration();
@@ -2568,6 +2573,45 @@ final class ActivityRecord extends ConfigurationContainer {
onRequestedOverrideConfigurationChanged(mTmpConfig);
}
+ @Override
+ void resolveOverrideConfiguration(Configuration newParentConfiguration) {
+ super.resolveOverrideConfiguration(newParentConfiguration);
+
+ // Assign configuration sequence number into hierarchy because there is a different way than
+ // ensureActivityConfiguration() in this class that uses configuration in WindowState during
+ // layout traversals.
+ mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
+ getResolvedOverrideConfiguration().seq = mConfigurationSeq;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newParentConfig) {
+ super.onConfigurationChanged(newParentConfig);
+
+ // Configuration's equality doesn't consider seq so if only seq number changes in resolved
+ // override configuration. Therefore ConfigurationContainer doesn't change merged override
+ // configuration, but it's used to push configuration changes so explicitly update that.
+ if (getMergedOverrideConfiguration().seq != getResolvedOverrideConfiguration().seq) {
+ onMergedOverrideConfigurationChanged();
+ }
+
+ // TODO(b/80414790): Remove code below after unification.
+ // Same as above it doesn't notify configuration listeners, and consequently AppWindowToken
+ // can't get updated seq number. However WindowState's merged override configuration needs
+ // to have this seq number because that's also used for activity config pushes during layout
+ // traversal. Therefore explicitly update them here.
+ if (mAppWindowToken == null) {
+ return;
+ }
+ final Configuration appWindowTokenRequestedOverrideConfig =
+ mAppWindowToken.getRequestedOverrideConfiguration();
+ if (appWindowTokenRequestedOverrideConfig.seq != getResolvedOverrideConfiguration().seq) {
+ appWindowTokenRequestedOverrideConfig.seq =
+ getResolvedOverrideConfiguration().seq;
+ mAppWindowToken.onMergedOverrideConfigurationChanged();
+ }
+ }
+
/** Returns true if the configuration is compatible with this activity. */
boolean isConfigurationCompatible(Configuration config) {
final int orientation = mAppWindowToken != null
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 0f286ce30ccd..d8644df3684c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.AppProtoEnums;
import android.app.IActivityManager;
import android.app.IApplicationThread;
@@ -479,4 +480,10 @@ public abstract class ActivityTaskManagerInternal {
public abstract void setProfilerInfo(ProfilerInfo profilerInfo);
public abstract ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry();
+
+ /**
+ * Gets bitmap snapshot of the provided task id.
+ */
+ public abstract ActivityManager.TaskSnapshot getTaskSnapshot(int taskId,
+ boolean reducedResolution);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 42121ca08696..e5bf21a205e3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -245,7 +245,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -7012,5 +7012,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return mStackSupervisor.getActivityMetricsLogger().getLaunchObserverRegistry();
}
}
+
+ @Override
+ public ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
+ synchronized (mGlobalLock) {
+ return ActivityTaskManagerService.this.getTaskSnapshot(taskId, reducedResolution);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowTraceBuffer.java b/services/core/java/com/android/server/wm/WindowTraceBuffer.java
new file mode 100644
index 000000000000..936ee85697b8
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTraceBuffer.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm;
+
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+
+import android.os.Trace;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Buffer used for window tracing.
+ */
+abstract class WindowTraceBuffer {
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ final Object mBufferSizeLock = new Object();
+ final BlockingQueue<byte[]> mBuffer;
+ int mBufferSize;
+ private final int mBufferCapacity;
+ private final File mTraceFile;
+
+ WindowTraceBuffer(int size, File traceFile) throws IOException {
+ mBufferCapacity = size;
+ mTraceFile = traceFile;
+ mBuffer = new LinkedBlockingQueue<>();
+
+ initTraceFile();
+ }
+
+ int getAvailableSpace() {
+ return mBufferCapacity - mBufferSize;
+ }
+
+ /**
+ * Inserts the specified element into this buffer.
+ *
+ * This method is synchronized with {@code #take()} and {@code #clear()}
+ * for consistency.
+ *
+ * @param proto the element to add
+ * @return {@code true} if the inserted item was inserted into the buffer
+ * @throws IllegalStateException if the element cannot be added because it is larger
+ * than the buffer size.
+ */
+ boolean add(ProtoOutputStream proto) throws InterruptedException {
+ byte[] protoBytes = proto.getBytes();
+ int protoLength = protoBytes.length;
+ if (protoLength > mBufferCapacity) {
+ throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
+ + mBufferCapacity + " Object size: " + protoLength);
+ }
+ synchronized (mBufferSizeLock) {
+ boolean canAdd = canAdd(protoBytes);
+ if (canAdd) {
+ mBuffer.offer(protoBytes);
+ mBufferSize += protoLength;
+ }
+ return canAdd;
+ }
+ }
+
+ void writeNextBufferElementToFile() throws IOException {
+ byte[] proto;
+ try {
+ proto = take();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return;
+ }
+
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
+ try (OutputStream os = new FileOutputStream(mTraceFile, true)) {
+ os.write(proto);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ /**
+ * Retrieves and removes the head of this queue, waiting if necessary
+ * until an element becomes available.
+ *
+ * This method is synchronized with {@code #add(ProtoOutputStream)} and {@code #clear()}
+ * for consistency.
+ *
+ * @return the head of this buffer, or {@code null} if this buffer is empty
+ */
+ private byte[] take() throws InterruptedException {
+ byte[] item = mBuffer.take();
+ synchronized (mBufferSizeLock) {
+ mBufferSize -= item.length;
+ return item;
+ }
+ }
+
+ private void initTraceFile() throws IOException {
+ mTraceFile.delete();
+ try (OutputStream os = new FileOutputStream(mTraceFile)) {
+ mTraceFile.setReadable(true, false);
+ ProtoOutputStream proto = new ProtoOutputStream(os);
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ proto.flush();
+ }
+ }
+
+ /**
+ * Checks if the element can be added to the buffer. The element is already certain to be
+ * smaller than the overall buffer size.
+ *
+ * @param protoBytes byte array representation of the Proto object to add
+ * @return <tt>true<</tt> if the element can be added to the buffer or not
+ */
+ abstract boolean canAdd(byte[] protoBytes) throws InterruptedException;
+
+ /**
+ * Flush all buffer content to the disk.
+ *
+ * @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile}
+ */
+ abstract void writeToDisk() throws IOException, InterruptedException;
+
+ /**
+ * Builder for a {@code WindowTraceBuffer} which creates a {@link WindowTraceQueueBuffer}
+ */
+ static class Builder {
+ private File mTraceFile;
+ private int mBufferCapacity;
+
+
+ Builder setTraceFile(File traceFile) {
+ mTraceFile = traceFile;
+ return this;
+ }
+
+ Builder setBufferCapacity(int size) {
+ mBufferCapacity = size;
+ return this;
+ }
+
+ File getFile() {
+ return mTraceFile;
+ }
+
+ WindowTraceBuffer build() throws IOException {
+ if (mBufferCapacity <= 0) {
+ throw new IllegalStateException("Buffer capacity must be greater than 0.");
+ }
+
+ if (mTraceFile == null) {
+ throw new IllegalArgumentException("A valid trace file must be specified.");
+ }
+
+ return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java b/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java
new file mode 100644
index 000000000000..b7fc7ac8cb5e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm;
+
+import static android.os.Build.IS_USER;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A buffer structure backed by a {@link java.util.concurrent.BlockingQueue} to store the first
+ * {@code #size size} bytes of window trace elements.
+ * Once the buffer is full it will no longer accepts new elements.
+ */
+class WindowTraceQueueBuffer extends WindowTraceBuffer {
+ private Thread mWriterThread;
+ private boolean mCancel;
+
+ @VisibleForTesting
+ WindowTraceQueueBuffer(int size, File traceFile, boolean startWriterThread) throws IOException {
+ super(size, traceFile);
+ if (startWriterThread) {
+ initializeWriterThread();
+ }
+ }
+
+ WindowTraceQueueBuffer(int size, File traceFile) throws IOException {
+ this(size, traceFile, !IS_USER);
+ }
+
+ private void initializeWriterThread() {
+ mCancel = false;
+ mWriterThread = new Thread(() -> {
+ try {
+ loop();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to execute trace write loop thread", e);
+ }
+ }, "window_tracing");
+ mWriterThread.start();
+ }
+
+ private void loop() throws IOException {
+ while (!mCancel) {
+ writeNextBufferElementToFile();
+ }
+ }
+
+ private void restartWriterThread() throws InterruptedException {
+ if (mWriterThread != null) {
+ mCancel = true;
+ mWriterThread.interrupt();
+ mWriterThread.join();
+ initializeWriterThread();
+ }
+ }
+
+ @Override
+ boolean canAdd(byte[] protoBytes) {
+ long availableSpace = getAvailableSpace();
+ return availableSpace >= protoBytes.length;
+ }
+
+ @Override
+ void writeToDisk() throws InterruptedException {
+ while (!mBuffer.isEmpty()) {
+ mBufferSizeLock.wait();
+ mBufferSizeLock.notify();
+ }
+ restartWriterThread();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 8fa56bb065c6..63539c4f9fd9 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -17,31 +17,23 @@
package com.android.server.wm;
import static android.os.Build.IS_USER;
+
import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
import static com.android.server.wm.WindowManagerTraceProto.WHERE;
import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.Trace;
-import android.annotation.Nullable;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.io.PrintWriter;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
/**
* A class that allows window manager to dump its state continuously to a trace file, such that a
@@ -49,35 +41,42 @@ import java.util.concurrent.BlockingQueue;
*/
class WindowTracing {
+ /**
+ * Maximum buffer size, currently defined as 512 KB
+ * Size was experimentally defined to fit between 100 to 150 elements.
+ */
+ private static final int WINDOW_TRACE_BUFFER_SIZE = 512 * 1024;
private static final String TAG = "WindowTracing";
- private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
private final Object mLock = new Object();
- private final File mTraceFile;
- private final BlockingQueue<ProtoOutputStream> mWriteQueue = new ArrayBlockingQueue<>(200);
+ private final WindowTraceBuffer.Builder mBufferBuilder;
+
+ private WindowTraceBuffer mTraceBuffer;
private boolean mEnabled;
private volatile boolean mEnabledLockFree;
WindowTracing(File file) {
- mTraceFile = file;
+ mBufferBuilder = new WindowTraceBuffer.Builder()
+ .setTraceFile(file)
+ .setBufferCapacity(WINDOW_TRACE_BUFFER_SIZE);
}
void startTrace(@Nullable PrintWriter pw) throws IOException {
- if (IS_USER){
+ if (IS_USER) {
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
synchronized (mLock) {
- logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
- mWriteQueue.clear();
- mTraceFile.delete();
- try (OutputStream os = new FileOutputStream(mTraceFile)) {
- mTraceFile.setReadable(true, false);
- ProtoOutputStream proto = new ProtoOutputStream(os);
- proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
- proto.flush();
+ logAndPrintln(pw, "Start tracing to " + mBufferBuilder.getFile() + ".");
+ if (mTraceBuffer != null) {
+ try {
+ mTraceBuffer.writeToDisk();
+ } catch (InterruptedException e) {
+ logAndPrintln(pw, "Error: Unable to flush the previous buffer.");
+ }
}
+ mTraceBuffer = mBufferBuilder.build();
mEnabled = mEnabledLockFree = true;
}
}
@@ -91,67 +90,42 @@ class WindowTracing {
}
void stopTrace(@Nullable PrintWriter pw) {
- if (IS_USER){
+ if (IS_USER) {
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
synchronized (mLock) {
- logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+ logAndPrintln(pw, "Stop tracing to " + mBufferBuilder.getFile()
+ + ". Waiting for traces to flush.");
mEnabled = mEnabledLockFree = false;
- while (!mWriteQueue.isEmpty()) {
+
+ synchronized (mLock) {
if (mEnabled) {
logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
throw new IllegalStateException("tracing enabled while waiting for flush.");
}
try {
- mLock.wait();
- mLock.notify();
+ mTraceBuffer.writeToDisk();
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to write buffer to file", e);
} catch (InterruptedException e) {
- Thread.currentThread().interrupt();
+ Log.e(TAG, "Unable to interrupt window tracing file write thread", e);
}
}
- logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+ logAndPrintln(pw, "Trace written to " + mBufferBuilder.getFile() + ".");
}
}
- void appendTraceEntry(ProtoOutputStream proto) {
+ private void appendTraceEntry(ProtoOutputStream proto) {
if (!mEnabledLockFree) {
return;
}
- if (!mWriteQueue.offer(proto)) {
- Log.e(TAG, "Dropping window trace entry, queue full");
- }
- }
-
- void loop() {
- for (;;) {
- loopOnce();
- }
- }
-
- @VisibleForTesting
- void loopOnce() {
- ProtoOutputStream proto;
try {
- proto = mWriteQueue.take();
+ mTraceBuffer.add(proto);
} catch (InterruptedException e) {
+ Log.e(TAG, "Unable to add element to trace", e);
Thread.currentThread().interrupt();
- return;
- }
-
- synchronized (mLock) {
- try {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
- try (OutputStream os = new FileOutputStream(mTraceFile, true /* append */)) {
- os.write(proto.getBytes());
- }
- } catch (IOException e) {
- Log.e(TAG, "Failed to write file " + mTraceFile, e);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
- mLock.notify();
}
}
@@ -161,11 +135,7 @@ class WindowTracing {
static WindowTracing createDefaultAndStartLooper(Context context) {
File file = new File("/data/misc/wmtrace/wm_trace.pb");
- WindowTracing windowTracing = new WindowTracing(file);
- if (!IS_USER){
- new Thread(windowTracing::loop, "window_tracing").start();
- }
- return windowTracing;
+ return new WindowTracing(file);
}
int onShellCommand(ShellCommand shell, String cmd) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 0929e20c9187..6f105ec90836 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1500,27 +1500,6 @@ static void nativeSetSystemUiVisibility(JNIEnv* /* env */,
im->setSystemUiVisibility(visibility);
}
-static jboolean nativeTransferTouchFocus(JNIEnv* env,
- jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
- sp<InputChannel> fromChannel =
- android_view_InputChannel_getInputChannel(env, fromChannelObj);
- sp<InputChannel> toChannel =
- android_view_InputChannel_getInputChannel(env, toChannelObj);
-
- if (fromChannel == nullptr || toChannel == nullptr) {
- return JNI_FALSE;
- }
-
- if (im->getInputManager()->getDispatcher()->
- transferTouchFocus(fromChannel, toChannel)) {
- return JNI_TRUE;
- } else {
- return JNI_FALSE;
- }
-}
-
static void nativeSetPointerSpeed(JNIEnv* /* env */,
jclass /* clazz */, jlong ptr, jint speed) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1708,8 +1687,6 @@ static const JNINativeMethod gInputManagerMethods[] = {
(void*) nativeSetInputDispatchMode },
{ "nativeSetSystemUiVisibility", "(JI)V",
(void*) nativeSetSystemUiVisibility },
- { "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
- (void*) nativeTransferTouchFocus },
{ "nativeSetPointerSpeed", "(JI)V",
(void*) nativeSetPointerSpeed },
{ "nativeSetShowTouches", "(JZ)V",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0e05b5a8e044..76ae5ccd4eeb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6103,7 +6103,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
delegates = getDelegatePackagesInternalLocked(scope, userId);
}
- if (delegates.size() != 1) {
+ if (delegates.size() == 0) {
+ return null;
+ } else if (delegates.size() > 1) {
Slog.wtf(LOG_TAG, "More than one delegate holds " + scope);
return null;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 8235ca3484b6..fef2db921dcf 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -120,6 +120,7 @@ import com.android.server.pm.PackageManagerService;
import com.android.server.pm.ShortcutService;
import com.android.server.pm.UserManagerService;
import com.android.server.policy.PhoneWindowManager;
+import com.android.server.policy.role.LegacyRoleResolutionPolicy;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
import com.android.server.power.ThermalManagerService;
@@ -262,6 +263,10 @@ public final class SystemServer {
"com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
private static final String ADB_SERVICE_CLASS =
"com.android.server.adb.AdbService$Lifecycle";
+ private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
+ "com.android.server.appprediction.AppPredictionManagerService";
+ private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
+ "com.android.server.contentsuggestions.ContentSuggestionsManagerService";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@@ -1172,6 +1177,16 @@ public final class SystemServer {
startContentCaptureService(context);
+ // App prediction manager service
+ traceBeginAndSlog("StartAppPredictionService");
+ mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS);
+ traceEnd();
+
+ // Content suggestions manager service
+ traceBeginAndSlog("StartContentSuggestionsService");
+ mSystemServiceManager.startService(CONTENT_SUGGESTIONS_SERVICE_CLASS);
+ traceEnd();
+
// NOTE: ClipboardService indirectly depends on IntelligenceService
traceBeginAndSlog("StartClipboardService");
mSystemServiceManager.startService(ClipboardService.class);
@@ -1977,7 +1992,8 @@ public final class SystemServer {
// Grants default permissions and defines roles
traceBeginAndSlog("StartRoleManagerService");
- mSystemServiceManager.startService(RoleManagerService.class);
+ mSystemServiceManager.startService(new RoleManagerService(
+ mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext)));
traceEnd();
// No dependency on Webview preparation in system server. But this should
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 148faada6381..6a153d5346ed 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -60,6 +60,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
@@ -81,6 +82,7 @@ import org.mockito.quality.Strictness;
import java.util.ArrayList;
+@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AlarmManagerServiceTest {
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 6a10ff487d2d..2cc338cb8674 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -70,7 +70,7 @@ import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
import com.android.server.am.ProcessList.IsolatedUidRange;
import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
import com.android.server.wm.ActivityTaskManagerService;
diff --git a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
index 05cb48b7d64d..8109b1c56207 100644
--- a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
@@ -25,7 +25,7 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
index 9626990efdcd..cbdc6c3faadd 100644
--- a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
@@ -34,7 +34,7 @@ import android.test.mock.MockContentResolver;
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
import org.junit.AfterClass;
import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index 7e0dfcfd73f0..65c5781a9114 100644
--- a/services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.appops;
+package com.android.server.appop;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
index edd89f9e61d1..edd89f9e61d1 100644
--- a/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 4ae9bea7617c..36f84d053817 100644
--- a/services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.appop;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
@@ -36,6 +36,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.appop.AppOpsService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index aac96a1dd847..eb0c6279ea99 100644
--- a/services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.appop;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 7049b215083a..c442ae845d4b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -17,6 +17,7 @@ package com.android.server.hdmi;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -53,6 +54,7 @@ public class HdmiCecLocalDeviceAudioSystemTest {
private HdmiControlService mHdmiControlService;
private HdmiCecController mHdmiCecController;
private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
+ private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
private FakeNativeWrapper mNativeWrapper;
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
@@ -133,7 +135,9 @@ public class HdmiCecLocalDeviceAudioSystemTest {
mMyLooper = mTestLooper.getLooper();
mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
+ mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
mHdmiCecLocalDeviceAudioSystem.init();
+ mHdmiCecLocalDevicePlayback.init();
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController =
@@ -142,6 +146,7 @@ public class HdmiCecLocalDeviceAudioSystemTest {
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
+ mLocalDevices.add(mHdmiCecLocalDevicePlayback);
mHdmiControlService.initPortInfo();
// No TV device interacts with AVR so system audio control won't be turned on here
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -557,4 +562,63 @@ public class HdmiCecLocalDeviceAudioSystemTest {
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
+
+ @Test
+ public void handleActiveSource_activeSourceFromTV_swithToArc() {
+ mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+
+ ActiveSource expectedActiveSource = ActiveSource.of(ADDR_TV, 0x0000);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
+ .isEqualTo(expectedActiveSource);
+ }
+
+ @Test
+ public void handleRoutingChange_currentActivePortIsHome() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x3000, mAvrPhysicalAddress);
+
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_AUDIO_SYSTEM, mAvrPhysicalAddress);
+ ActiveSource expectedActiveSource = ActiveSource.of(ADDR_AUDIO_SYSTEM, mAvrPhysicalAddress);
+ int expectedLocalActivePort = Constants.CEC_SWITCH_HOME;
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
+ .isEqualTo(expectedActiveSource);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePort())
+ .isEqualTo(expectedLocalActivePort);
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
+
+ @Test
+ public void handleRoutingInformation_currentActivePortIsHDMI1() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x2000);
+ mHdmiCecLocalDeviceAudioSystem.setLocalActivePort(Constants.CEC_SWITCH_HDMI1);
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildRoutingInformation(ADDR_AUDIO_SYSTEM, 0x2100);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
+
+ @Test
+ public void handleRoutingChange_homeIsActive_playbackSendActiveSource() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x2000);
+
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x2000);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 561c61fb979f..0dff03f2f53e 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -47,6 +47,7 @@ import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.PowerManagerService.Injector;
import com.android.server.power.PowerManagerService.NativeWrapper;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
import com.android.server.power.batterysaver.BatterySavingStats;
import org.junit.Rule;
diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index acf511e38738..73eefcf618b0 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server.power;
+package com.android.server.power.batterysaver;
import static com.google.common.truth.Truth.assertThat;
@@ -31,13 +31,12 @@ import android.util.ArrayMap;
import com.android.frameworks.servicestests.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
-import com.android.server.power.batterysaver.BatterySavingStats;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
- * Tests for {@link com.android.server.power.BatterySaverPolicy}
+ * Tests for {@link com.android.server.power.batterysaver.BatterySaverPolicy}
*/
public class BatterySaverPolicyTest extends AndroidTestCase {
private static final boolean BATTERY_SAVER_ON = true;
@@ -62,7 +61,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
private static final String BATTERY_SAVER_INCORRECT_CONSTANTS = "vi*,!=,,true";
private class BatterySaverPolicyForTest extends BatterySaverPolicy {
- public BatterySaverPolicyForTest(Object lock, Context context,
+ BatterySaverPolicyForTest(Object lock, Context context,
BatterySavingStats batterySavingStats) {
super(lock, context, batterySavingStats);
}
@@ -269,15 +268,15 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
mBatterySaverPolicy.onChange();
assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}");
assertThat(mBatterySaverPolicy.getFileValues(false).toString())
- .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, " +
- "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}");
+ .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, "
+ + "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}");
mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_3;
mBatterySaverPolicy.onChange();
assertThat(mBatterySaverPolicy.getFileValues(true).toString())
- .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, " +
- "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}");
+ .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, "
+ + "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}");
assertThat(mBatterySaverPolicy.getFileValues(false).toString())
.isEqualTo("{/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=222}");
@@ -287,9 +286,9 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
mBatterySaverPolicy.onChange();
assertThat(mBatterySaverPolicy.getFileValues(true).toString())
- .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, " +
- "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, " +
- "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}");
+ .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, "
+ + "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, "
+ + "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}");
assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}");
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index b6f181758d3a..8f9d2badce30 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -20,6 +20,9 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
@@ -38,6 +41,7 @@ import static org.junit.Assert.assertTrue;
import android.app.ActivityOptions;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.PauseActivityItem;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.util.MutableBoolean;
@@ -210,4 +214,41 @@ public class ActivityRecordTests extends ActivityTestsBase {
assertNull(mActivity.pendingOptions);
assertNotNull(activity2.pendingOptions);
}
+
+ @Test
+ public void testNewOverrideConfigurationIncrementsSeq() {
+ final Configuration newConfig = new Configuration();
+
+ final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
+ mActivity.onRequestedOverrideConfigurationChanged(newConfig);
+ assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
+ }
+
+ @Test
+ public void testNewParentConfigurationIncrementsSeq() {
+ final Configuration newConfig = new Configuration(
+ mTask.getRequestedOverrideConfiguration());
+ newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
+ ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
+
+ final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
+ mTask.onRequestedOverrideConfigurationChanged(newConfig);
+ assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
+ }
+
+ @Test
+ public void testNotifiesSeqIncrementToAppToken() {
+ final Configuration appWindowTokenRequestedOrientation = mock(Configuration.class);
+ mActivity.mAppWindowToken = mock(AppWindowToken.class);
+ doReturn(appWindowTokenRequestedOrientation).when(mActivity.mAppWindowToken)
+ .getRequestedOverrideConfiguration();
+
+ final Configuration newConfig = new Configuration();
+ newConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
+
+ final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
+ mActivity.onRequestedOverrideConfigurationChanged(newConfig);
+ assertEquals(prevSeq + 1, appWindowTokenRequestedOrientation.seq);
+ verify(mActivity.mAppWindowToken).onMergedOverrideConfigurationChanged();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index bd29d2a3a884..5589ca1be815 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -66,7 +66,7 @@ import android.view.Display;
import android.view.DisplayInfo;
import com.android.internal.app.IVoiceInteractor;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.ServiceThread;
import com.android.server.am.ActivityManagerService;
@@ -264,6 +264,8 @@ class ActivityTestsBase {
.setOrientation(anyInt(), any(), any());
doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt());
doNothing().when(activity).removeWindowContainer();
+ doReturn(mock(Configuration.class)).when(activity.mAppWindowToken)
+ .getRequestedOverrideConfiguration();
if (mTaskRecord != null) {
mTaskRecord.addActivityToTop(activity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index f1c6eab2143d..bb3ab358e71a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -20,11 +20,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -125,7 +123,6 @@ public class DragDropControllerTests extends WindowTestsBase {
mDisplayContent = spy(mDisplayContent);
mWindow = createDropTargetWindow("Drag test window", 0);
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
- when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
synchronized (mWm.mGlobalLock) {
mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
@@ -176,7 +173,6 @@ public class DragDropControllerTests extends WindowTestsBase {
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- assertTrue(mWm.mInputManager.transferTouchFocus(null, null));
mToken = mTarget.performDrag(
new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
data);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index c343fe7d0675..8c6ac23c9202 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -18,7 +18,6 @@ package com.android.server.wm;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -58,10 +57,6 @@ public class TaskPositioningControllerTests extends WindowTestsBase {
assertNotNull(mWm.mTaskPositioningController);
mTarget = mWm.mTaskPositioningController;
- when(mWm.mInputManager.transferTouchFocus(
- any(InputChannel.class),
- any(InputChannel.class))).thenReturn(true);
-
mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
mWindow.mInputChannel = new InputChannel();
synchronized (mWm.mGlobalLock) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java
new file mode 100644
index 000000000000..8d834974148c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+
+/**
+ * Test class for {@link WindowTraceBuffer} and {@link WindowTraceQueueBuffer}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:WindowTraceBufferTest
+ */
+@SmallTest
+@Presubmit
+public class WindowTraceBufferTest {
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private File mFile;
+
+ @Before
+ public void setUp() throws Exception {
+ final Context testContext = getInstrumentation().getContext();
+ mFile = testContext.getFileStreamPath("tracing_test.dat");
+ mFile.delete();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mFile.delete();
+ }
+
+ @Test
+ public void testTraceQueueBuffer_addItem() throws Exception {
+ ProtoOutputStream toWrite1 = getDummy(1);
+ ProtoOutputStream toWrite2 = getDummy(2);
+ ProtoOutputStream toWrite3 = getDummy(3);
+ byte[] toWrite1Bytes = toWrite1.getBytes();
+ byte[] toWrite2Bytes = toWrite2.getBytes();
+ byte[] toWrite3Bytes = toWrite3.getBytes();
+
+ final int objectSize = toWrite1.getBytes().length;
+ final int bufferCapacity = objectSize * 2;
+
+ final WindowTraceBuffer buffer = buildQueueBuffer(bufferCapacity);
+
+ buffer.add(toWrite1);
+ assertTrue("First element should be in the list",
+ buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes)));
+
+ buffer.add(toWrite2);
+ assertTrue("First element should be in the list",
+ buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes)));
+ assertTrue("Second element should be in the list",
+ buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite2Bytes)));
+
+ buffer.add(toWrite3);
+
+ assertTrue("Third element should not be in the list",
+ buffer.mBuffer.stream().noneMatch(p -> Arrays.equals(p, toWrite3Bytes)));
+
+ assertEquals("Buffer should have 2 elements", buffer.mBuffer.size(), 2);
+ assertEquals(String.format("Buffer is full, used space should be %d", bufferCapacity),
+ buffer.mBufferSize, bufferCapacity);
+ assertEquals("Buffer is full, available space should be 0",
+ buffer.getAvailableSpace(), 0);
+ }
+
+ private ProtoOutputStream getDummy(int value) {
+ ProtoOutputStream toWrite = new ProtoOutputStream();
+ toWrite.write(MAGIC_NUMBER, value);
+ toWrite.flush();
+
+ return toWrite;
+ }
+
+ private WindowTraceBuffer buildQueueBuffer(int size) throws IOException {
+ return new WindowTraceQueueBuffer(size, mFile, false);
+ }
+}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 9fc1b6f76e77..bf9bf9a1ba8f 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -170,7 +170,8 @@ public class ServiceState implements Parcelable {
RIL_RADIO_TECHNOLOGY_GSM,
RIL_RADIO_TECHNOLOGY_TD_SCDMA,
RIL_RADIO_TECHNOLOGY_IWLAN,
- RIL_RADIO_TECHNOLOGY_LTE_CA})
+ RIL_RADIO_TECHNOLOGY_LTE_CA,
+ RIL_RADIO_TECHNOLOGY_NR})
public @interface RilRadioTechnology {}
/**
* Available radio technologies for GSM, UMTS and CDMA.
diff --git a/telephony/java/com/android/internal/telephony/SmsApplication.java b/telephony/java/com/android/internal/telephony/SmsApplication.java
index 9874f809c0b4..a508b068065f 100644
--- a/telephony/java/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/java/com/android/internal/telephony/SmsApplication.java
@@ -18,6 +18,8 @@ package com.android.internal.telephony;
import android.Manifest.permission;
import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.app.role.RoleManagerCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -29,13 +31,12 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Binder;
import android.os.Debug;
import android.os.Process;
import android.os.UserHandle;
-import android.provider.Settings;
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
import android.telephony.Rlog;
@@ -50,6 +51,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Class for managing the primary application that we will deliver SMS/MMS messages to
@@ -67,6 +72,7 @@ public final class SmsApplication {
private static final String SCHEME_SMSTO = "smsto";
private static final String SCHEME_MMS = "mms";
private static final String SCHEME_MMSTO = "mmsto";
+ private static final boolean DEBUG = false;
private static final boolean DEBUG_MULTIUSER = false;
private static final int[] DEFAULT_APP_EXCLUSIVE_APPOPS = {
@@ -240,7 +246,11 @@ public final class SmsApplication {
// Get the list of apps registered for SMS
Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
- List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
+ if (DEBUG) {
+ intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
+ }
+ List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>();
@@ -266,7 +276,8 @@ public final class SmsApplication {
// Update any existing entries with mms receiver class
intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
intent.setDataAndType(null, "application/vnd.wap.mms-message");
- List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
+ List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
for (ResolveInfo resolveInfo : mmsReceivers) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
@@ -286,7 +297,8 @@ public final class SmsApplication {
// Update any existing entries with respond via message intent class.
intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE,
Uri.fromParts(SCHEME_SMSTO, "", null));
- List<ResolveInfo> respondServices = packageManager.queryIntentServicesAsUser(intent, 0,
+ List<ResolveInfo> respondServices = packageManager.queryIntentServicesAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
for (ResolveInfo resolveInfo : respondServices) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
@@ -306,7 +318,8 @@ public final class SmsApplication {
// Update any existing entries with supports send to.
intent = new Intent(Intent.ACTION_SENDTO,
Uri.fromParts(SCHEME_SMSTO, "", null));
- List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, 0,
+ List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
for (ResolveInfo resolveInfo : sendToActivities) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
@@ -323,7 +336,9 @@ public final class SmsApplication {
// Update any existing entries with the default sms changed handler.
intent = new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
List<ResolveInfo> smsAppChangedReceivers =
- packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ packageManager.queryBroadcastReceiversAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" +
smsAppChangedReceivers);
@@ -348,7 +363,9 @@ public final class SmsApplication {
// Update any existing entries with the external provider changed handler.
intent = new Intent(Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE);
List<ResolveInfo> providerChangedReceivers =
- packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ packageManager.queryBroadcastReceiversAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" +
providerChangedReceivers);
@@ -373,7 +390,9 @@ public final class SmsApplication {
// Update any existing entries with the sim full handler.
intent = new Intent(Intents.SIM_FULL_ACTION);
List<ResolveInfo> simFullReceivers =
- packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ packageManager.queryBroadcastReceiversAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers="
+ simFullReceivers);
@@ -406,7 +425,8 @@ public final class SmsApplication {
if (smsApplicationData != null) {
if (!smsApplicationData.isComplete()) {
Log.w(LOG_TAG, "Package " + packageName
- + " lacks required manifest declarations to be a default sms app");
+ + " lacks required manifest declarations to be a default sms app: "
+ + smsApplicationData);
receivers.remove(packageName);
}
}
@@ -418,7 +438,7 @@ public final class SmsApplication {
* Checks to see if we have a valid installed SMS application for the specified package name
* @return Data for the specified package name or null if there isn't one
*/
- private static SmsApplicationData getApplicationForPackage(
+ public static SmsApplicationData getApplicationForPackage(
Collection<SmsApplicationData> applications, String packageName) {
if (packageName == null) {
return null;
@@ -456,8 +476,7 @@ public final class SmsApplication {
Log.i(LOG_TAG, "getApplication userId=" + userId);
}
// Determine which application receives the broadcast
- String defaultApplication = Settings.Secure.getStringForUser(context.getContentResolver(),
- Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+ String defaultApplication = getDefaultSmsPackage(context, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplication defaultApp=" + defaultApplication);
}
@@ -469,27 +488,6 @@ public final class SmsApplication {
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplication appData=" + applicationData);
}
- // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do
- // this if the caller asked us to.
- if (updateIfNeeded && applicationData == null) {
- // Try to find the default SMS package for this device
- Resources r = context.getResources();
- String defaultPackage =
- r.getString(com.android.internal.R.string.default_sms_application);
- applicationData = getApplicationForPackage(applications, defaultPackage);
-
- if (applicationData == null) {
- // Are there any applications?
- if (applications.size() != 0) {
- applicationData = (SmsApplicationData)applications.toArray()[0];
- }
- }
-
- // If we found a new default app, update the setting
- if (applicationData != null) {
- setDefaultApplicationInternal(applicationData.mPackageName, context, userId);
- }
- }
// If we found a package, make sure AppOps permissions are set up correctly
if (applicationData != null) {
@@ -513,7 +511,7 @@ public final class SmsApplication {
// current SMS app will already be the preferred activity - but checking whether or
// not this is true is just as expensive as reconfiguring the preferred activity so
// we just reconfigure every time.
- updateDefaultSmsApp(context, userId, applicationData);
+ defaultSmsAppChanged(context);
}
}
if (DEBUG_MULTIUSER) {
@@ -522,16 +520,17 @@ public final class SmsApplication {
return applicationData;
}
- private static void updateDefaultSmsApp(Context context, int userId,
- SmsApplicationData applicationData) {
+ private static String getDefaultSmsPackage(Context context, int userId) {
+ return context.getSystemService(RoleManager.class).getDefaultSmsPackage(userId);
+ }
+
+ /**
+ * Grants various permissions and appops on sms app change
+ */
+ private static void defaultSmsAppChanged(Context context) {
PackageManager packageManager = context.getPackageManager();
AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
- // Configure this as the preferred activity for SENDTO sms/mms intents
- configurePreferredActivity(packageManager, new ComponentName(
- applicationData.mPackageName, applicationData.mSendToClass),
- userId);
-
// Assign permission to special system apps
assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
PHONE_PACKAGE_NAME);
@@ -603,8 +602,7 @@ public final class SmsApplication {
final UserHandle userHandle = UserHandle.of(userId);
// Get old package name
- String oldPackageName = Settings.Secure.getStringForUser(context.getContentResolver(),
- Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+ String oldPackageName = getDefaultSmsPackage(context, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldPackageName +
@@ -636,16 +634,29 @@ public final class SmsApplication {
}
}
- // Update the secure setting.
- Settings.Secure.putStringForUser(context.getContentResolver(),
- Settings.Secure.SMS_DEFAULT_APPLICATION, applicationData.mPackageName,
- userId);
-
- // Allow relevant appops for the newly configured default SMS app.
- setExclusiveAppops(applicationData.mPackageName, appOps, applicationData.mUid,
- AppOpsManager.MODE_ALLOWED);
+ // Update the setting.
+ CompletableFuture<Void> res = new CompletableFuture<>();
+ context.getSystemService(RoleManager.class).addRoleHolderAsUser(
+ RoleManager.ROLE_SMS, applicationData.mPackageName, UserHandle.of(userId),
+ AsyncTask.THREAD_POOL_EXECUTOR, new RoleManagerCallback() {
+ @Override
+ public void onSuccess() {
+ res.complete(null);
+ }
+
+ @Override
+ public void onFailure() {
+ res.completeExceptionally(new RuntimeException());
+ }
+ });
+ try {
+ res.get(5, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Log.e(LOG_TAG, "Exception while adding sms role holder " + applicationData, e);
+ return;
+ }
- updateDefaultSmsApp(context, userId, applicationData);
+ defaultSmsAppChanged(context);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "setDefaultApplicationInternal oldAppData=" + oldAppData);
diff --git a/tests/PackageWatchdog/Android.mk b/tests/PackageWatchdog/Android.mk
new file mode 100644
index 000000000000..b53f27d28740
--- /dev/null
+++ b/tests/PackageWatchdog/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+# PackageWatchdogTest
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_PACKAGE_NAME := PackageWatchdogTest
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ junit \
+ frameworks-base-testutils \
+ android-support-test \
+ services
+
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner
+
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/PackageWatchdog/AndroidManifest.xml b/tests/PackageWatchdog/AndroidManifest.xml
new file mode 100644
index 000000000000..fa89528403c3
--- /dev/null
+++ b/tests/PackageWatchdog/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.packagewatchdog" >
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.tests.packagewatchdog"
+ android:label="PackageWatchdog Test"/>
+</manifest>
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
new file mode 100644
index 000000000000..ec07037b3a8f
--- /dev/null
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+import static com.android.server.PackageWatchdog.TRIGGER_FAILURE_COUNT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.test.TestLooper;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+// TODO(zezeozue): Write test without using PackageWatchdog#getPackages. Just rely on
+// behavior of observers receiving crash notifications or not to determine if it's registered
+/**
+ * Test PackageWatchdog.
+ */
+public class PackageWatchdogTest {
+ private static final String APP_A = "com.package.a";
+ private static final String APP_B = "com.package.b";
+ private static final String OBSERVER_NAME_1 = "observer1";
+ private static final String OBSERVER_NAME_2 = "observer2";
+ private static final String OBSERVER_NAME_3 = "observer3";
+ private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
+ private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
+ private TestLooper mTestLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestLooper = new TestLooper();
+ mTestLooper.startAutoDispatch();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ new File(InstrumentationRegistry.getContext().getFilesDir(),
+ "package-watchdog.xml").delete();
+ }
+
+ /**
+ * Test registration, unregistration, package expiry and duration reduction
+ */
+ @Test
+ public void testRegistration() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+ TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
+
+ // Start observing for observer1 which will be unregistered
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ // Start observing for observer2 which will expire
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+ // Start observing for observer3 which will have expiry duration reduced
+ watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION);
+
+ // Verify packages observed at start
+ // 1
+ assertEquals(1, watchdog.getPackages(observer1).size());
+ assertTrue(watchdog.getPackages(observer1).contains(APP_A));
+ // 2
+ assertEquals(2, watchdog.getPackages(observer2).size());
+ assertTrue(watchdog.getPackages(observer2).contains(APP_A));
+ assertTrue(watchdog.getPackages(observer2).contains(APP_B));
+ // 3
+ assertEquals(1, watchdog.getPackages(observer3).size());
+ assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+
+ // Then unregister observer1
+ watchdog.unregisterHealthObserver(observer1);
+
+ // Verify observer2 and observer3 left
+ // 1
+ assertNull(watchdog.getPackages(observer1));
+ // 2
+ assertEquals(2, watchdog.getPackages(observer2).size());
+ assertTrue(watchdog.getPackages(observer2).contains(APP_A));
+ assertTrue(watchdog.getPackages(observer2).contains(APP_B));
+ // 3
+ assertEquals(1, watchdog.getPackages(observer3).size());
+ assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+
+ // Then advance time a little and run messages in Handlers so observer2 expires
+ Thread.sleep(SHORT_DURATION);
+ mTestLooper.dispatchAll();
+
+ // Verify observer3 left with reduced expiry duration
+ // 1
+ assertNull(watchdog.getPackages(observer1));
+ // 2
+ assertNull(watchdog.getPackages(observer2));
+ // 3
+ assertEquals(1, watchdog.getPackages(observer3).size());
+ assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+
+ // Then advance time some more and run messages in Handlers so observer3 expires
+ Thread.sleep(LONG_DURATION);
+ mTestLooper.dispatchAll();
+
+ // Verify observer3 expired
+ // 1
+ assertNull(watchdog.getPackages(observer1));
+ // 2
+ assertNull(watchdog.getPackages(observer2));
+ // 3
+ assertNull(watchdog.getPackages(observer3));
+ }
+
+ /**
+ * Test package observers are persisted and loaded on startup
+ */
+ @Test
+ public void testPersistence() throws Exception {
+ PackageWatchdog watchdog1 = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+ watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+
+ // Verify 2 observers are registered and saved internally
+ // 1
+ assertEquals(1, watchdog1.getPackages(observer1).size());
+ assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
+ // 2
+ assertEquals(2, watchdog1.getPackages(observer2).size());
+ assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
+ assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
+
+
+ // Then advance time and run IO Handler so file is saved
+ mTestLooper.dispatchAll();
+
+ // Then start a new watchdog
+ PackageWatchdog watchdog2 = createWatchdog();
+
+ // Verify the new watchdog loads observers on startup but nothing registered
+ assertEquals(0, watchdog2.getPackages(observer1).size());
+ assertEquals(0, watchdog2.getPackages(observer2).size());
+ // Verify random observer not saved returns null
+ assertNull(watchdog2.getPackages(new TestObserver(OBSERVER_NAME_3)));
+
+ // Then regiser observer1
+ watchdog2.registerHealthObserver(observer1);
+ watchdog2.registerHealthObserver(observer2);
+
+ // Verify 2 observers are registered after reload
+ // 1
+ assertEquals(1, watchdog1.getPackages(observer1).size());
+ assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
+ // 2
+ assertEquals(2, watchdog1.getPackages(observer2).size());
+ assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
+ assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
+ }
+
+ /**
+ * Test package failure under threshold does not notify observers
+ */
+ @Test
+ public void testNoPackageFailureBeforeThreshold() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+
+ // Then fail APP_A below the threshold
+ for (int i = 0; i < TRIGGER_FAILURE_COUNT - 1; i++) {
+ watchdog.onPackageFailure(new String[]{APP_A});
+ }
+
+ // Verify that observers are not notified
+ assertEquals(0, observer1.mFailedPackages.size());
+ assertEquals(0, observer2.mFailedPackages.size());
+ }
+
+ /**
+ * Test package failure and notifies all observer since none handles the failure
+ */
+ @Test
+ public void testPackageFailureNotifyAll() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+ // Start observing for observer1 and observer2 without handling failures
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+
+ // Then fail APP_A and APP_B above the threshold
+ for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
+ watchdog.onPackageFailure(new String[]{APP_A, APP_B});
+ }
+
+ // Verify all observers are notifed of all package failures
+ List<String> observer1Packages = observer1.mFailedPackages;
+ List<String> observer2Packages = observer2.mFailedPackages;
+ assertEquals(2, observer1Packages.size());
+ assertEquals(1, observer2Packages.size());
+ assertEquals(APP_A, observer1Packages.get(0));
+ assertEquals(APP_B, observer1Packages.get(1));
+ assertEquals(APP_A, observer2Packages.get(0));
+ }
+
+ /**
+ * Test package failure and notifies only one observer because it handles the failure
+ */
+ @Test
+ public void testPackageFailureNotifyOne() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, true /* shouldHandle */);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, true /* shouldHandle */);
+
+ // Start observing for observer1 and observer2 with failure handling
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+
+ // Then fail APP_A above the threshold
+ for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
+ watchdog.onPackageFailure(new String[]{APP_A});
+ }
+
+ // Verify only one observer is notifed
+ assertEquals(1, observer1.mFailedPackages.size());
+ assertEquals(APP_A, observer1.mFailedPackages.get(0));
+ assertEquals(0, observer2.mFailedPackages.size());
+ }
+
+ private PackageWatchdog createWatchdog() {
+ return new PackageWatchdog(InstrumentationRegistry.getContext(),
+ mTestLooper.getLooper());
+ }
+
+ private static class TestObserver implements PackageHealthObserver {
+ private final String mName;
+ private boolean mShouldHandle;
+ final List<String> mFailedPackages = new ArrayList<>();
+
+ TestObserver(String name) {
+ mName = name;
+ }
+
+ TestObserver(String name, boolean shouldHandle) {
+ mName = name;
+ mShouldHandle = shouldHandle;
+ }
+
+ public boolean onHealthCheckFailed(String packageName) {
+ mFailedPackages.add(packageName);
+ return mShouldHandle;
+ }
+
+ public String getName() {
+ return mName;
+ }
+ }
+}
diff --git a/tools/bit/command.h b/tools/bit/command.h
index fb44900b0806..dd7103e10fe7 100644
--- a/tools/bit/command.h
+++ b/tools/bit/command.h
@@ -25,7 +25,7 @@ using namespace std;
struct Command
{
- Command(const string& prog);
+ explicit Command(const string& prog);
~Command();
void AddArg(const string& arg);
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index 9968bda6458f..4491a8567441 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -67,7 +67,7 @@ cpp_type_name(java_type_t type)
case JAVA_TYPE_STRING:
return "char const*";
case JAVA_TYPE_BYTE_ARRAY:
- return "char const*";
+ return "const BytesField&";
default:
return "UNKNOWN";
}
@@ -270,10 +270,6 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms,
chainField.name.c_str(), chainField.name.c_str());
}
}
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", %s arg%d, size_t arg%d_length",
- cpp_type_name(*arg), argIndex, argIndex);
-
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
fprintf(out, ", const std::map<int, int32_t>& arg%d_1, "
"const std::map<int, int64_t>& arg%d_2, "
@@ -355,7 +351,8 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms,
fprintf(out, " event.end();\n\n");
} else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
fprintf(out,
- " event.AppendCharArray(arg%d, arg%d_length);\n",
+ " event.AppendCharArray(arg%d.arg, "
+ "arg%d.arg_length);\n",
argIndex, argIndex);
} else {
if (*arg == JAVA_TYPE_STRING) {
@@ -397,10 +394,6 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms,
chainField.name.c_str(), chainField.name.c_str());
}
}
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", %s arg%d, size_t arg%d_length",
- cpp_type_name(*arg), argIndex, argIndex);
-
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
fprintf(out,
", const std::map<int, int32_t>& arg%d_1, "
@@ -434,8 +427,6 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms,
chainField.name.c_str(), chainField.name.c_str());
}
}
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", arg%d, arg%d_length", argIndex, argIndex);
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
fprintf(out, ", arg%d_1, arg%d_2, arg%d_3, arg%d_4", argIndex,
argIndex, argIndex, argIndex);
@@ -494,7 +485,14 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms,
fprintf(out, " arg%d = \"\";\n", argIndex);
fprintf(out, " }\n");
}
- fprintf(out, " event << arg%d;\n", argIndex);
+ if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+ fprintf(out,
+ " event.AppendCharArray(arg%d.arg, "
+ "arg%d.arg_length);",
+ argIndex, argIndex);
+ } else {
+ fprintf(out, " event << arg%d;\n", argIndex);
+ }
if (argIndex == 2) {
fprintf(out, " event.end();\n\n");
fprintf(out, " event.end();\n\n");
@@ -577,7 +575,9 @@ void build_non_chained_decl_map(const Atoms& atoms,
static void write_cpp_usage(
FILE* out, const string& method_name, const string& atom_code_name,
const AtomDecl& atom, const AtomDecl &attributionDecl) {
- fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str());
+ fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(),
+ atom_code_name.c_str());
+
for (vector<AtomField>::const_iterator field = atom.fields.begin();
field != atom.fields.end(); field++) {
if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
@@ -601,11 +601,6 @@ static void write_cpp_usage(
field->name.c_str(),
field->name.c_str(),
field->name.c_str());
- } else if (field->javaType == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", %s %s, size_t %s_length",
- cpp_type_name(field->javaType), field->name.c_str(),
- field->name.c_str());
-
} else {
fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
}
@@ -639,9 +634,6 @@ static void write_cpp_method_header(
"const std::map<int, char const*>& arg%d_3, "
"const std::map<int, float>& arg%d_4",
argIndex, argIndex, argIndex, argIndex);
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", %s arg%d, size_t arg%d_length",
- cpp_type_name(*arg), argIndex, argIndex);
} else {
fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
}
@@ -708,6 +700,15 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio
fprintf(out, "};\n");
fprintf(out, "\n");
+ fprintf(out, "struct BytesField {\n");
+ fprintf(out,
+ " BytesField(char const* array, size_t len) : arg(array), "
+ "arg_length(len) {}\n");
+ fprintf(out, " char const* arg;\n");
+ fprintf(out, " size_t arg_length;\n");
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
fprintf(out, "struct StateAtomFieldOptions {\n");
fprintf(out, " std::vector<int> primaryFields;\n");
fprintf(out, " int exclusiveField;\n");
@@ -1183,6 +1184,11 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp
fprintf(out, " str%d = NULL;\n", argIndex);
fprintf(out, " }\n");
+ fprintf(out,
+ " android::util::BytesField bytesField%d(str%d, "
+ "str%d_length);",
+ argIndex, argIndex, argIndex);
+
} else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
hadStringOrChain = true;
for (auto chainField : attributionDecl.fields) {
@@ -1240,7 +1246,8 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp
// stats_write call
argIndex = 1;
- fprintf(out, "\n int ret = android::util::%s(code", cpp_method_name.c_str());
+ fprintf(out, "\n int ret = android::util::%s(code",
+ cpp_method_name.c_str());
for (vector<java_type_t>::const_iterator arg = signature->begin();
arg != signature->end(); arg++) {
if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
@@ -1255,16 +1262,12 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp
}
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
fprintf(out, ", int32_t_map, int64_t_map, string_map, float_map");
+ } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+ fprintf(out, ", bytesField%d", argIndex);
} else {
- const char* argName = (*arg == JAVA_TYPE_STRING ||
- *arg == JAVA_TYPE_BYTE_ARRAY)
- ? "str"
- : "arg";
+ const char* argName =
+ (*arg == JAVA_TYPE_STRING) ? "str" : "arg";
fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex);
-
- if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", %s%d_length", argName, argIndex);
- }
}
argIndex++;
}
diff --git a/tools/streaming_proto/Errors.h b/tools/streaming_proto/Errors.h
index f14bbfd55b5f..bddd9819e8f5 100644
--- a/tools/streaming_proto/Errors.h
+++ b/tools/streaming_proto/Errors.h
@@ -11,7 +11,7 @@ using namespace std;
struct Error
{
Error();
- explicit Error(const Error& that);
+ Error(const Error& that);
Error(const string& filename, int lineno, const char* message);
string filename;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 7f56a010c4d0..517bf3b47a7a 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -189,6 +189,7 @@ public class WifiManager {
*/
public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5;
+ /** @hide */
@IntDef(prefix = { "STATUS_NETWORK_SUGGESTIONS_" }, value = {
STATUS_NETWORK_SUGGESTIONS_SUCCESS,
STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL,
@@ -1652,7 +1653,7 @@ public class WifiManager {
* suggestion back using this API.</li>
*
* @param networkSuggestions List of network suggestions provided by the app.
- * @return Status code corresponding to the values in {@link NetworkSuggestionsStatusCode}.
+ * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values.
* {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app.
* @throws {@link SecurityException} if the caller is missing required permissions.
*/
@@ -1673,8 +1674,7 @@ public class WifiManager {
*
* @param networkSuggestions List of network suggestions to be removed. Pass an empty list
* to remove all the previous suggestions provided by the app.
- * @return Status code corresponding to the values in
- * {@link NetworkSuggestionsStatusCode}.
+ * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values.
* Any matching suggestions are removed from the device and will not be considered for any
* further connection attempts.
*/