summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt52
-rw-r--r--api/system-current.txt52
-rw-r--r--api/test-current.txt51
-rw-r--r--cmds/pm/Android.mk10
-rwxr-xr-xcmds/pm/pm8
-rw-r--r--cmds/pm/src/com/android/commands/pm/Pm.java822
-rw-r--r--cmds/statsd/Android.mk10
-rw-r--r--cmds/statsd/src/StatsService.cpp23
-rw-r--r--cmds/statsd/src/StatsService.h5
-rw-r--r--cmds/statsd/src/atoms.proto29
-rw-r--r--cmds/statsd/src/config/ConfigManager.cpp30
-rw-r--r--cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp99
-rw-r--r--cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h33
-rw-r--r--cmds/statsd/src/metrics/CountMetricProducer.cpp2
-rw-r--r--cmds/statsd/src/metrics/DurationMetricProducer.cpp11
-rw-r--r--cmds/statsd/src/metrics/GaugeMetricProducer.cpp2
-rw-r--r--cmds/statsd/src/metrics/ValueMetricProducer.cpp2
-rw-r--r--cmds/statsd/src/metrics/metrics_manager_util.cpp22
-rw-r--r--cmds/statsd/src/stats_log.proto2
-rw-r--r--cmds/statsd/src/statsd_config.proto2
-rw-r--r--cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp1
-rw-r--r--cmds/statsd/tools/Android.mk20
-rw-r--r--cmds/statsd/tools/dogfood/Android.mk34
-rw-r--r--cmds/statsd/tools/dogfood/AndroidManifest.xml41
-rw-r--r--cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.pngbin0 -> 7783 bytes
-rw-r--r--cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.pngbin0 -> 3760 bytes
-rw-r--r--cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.pngbin0 -> 12356 bytes
-rw-r--r--cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.pngbin0 -> 24780 bytes
-rw-r--r--cmds/statsd/tools/dogfood/res/layout/activity_main.xml133
-rw-r--r--cmds/statsd/tools/dogfood/res/raw/statsd_baseline_configbin0 -> 2358 bytes
-rw-r--r--cmds/statsd/tools/dogfood/res/values/strings.xml52
-rw-r--r--cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java245
-rw-r--r--cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java259
-rw-r--r--core/java/Android.bp23
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java5
-rw-r--r--core/java/android/app/ActivityManager.java8
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/NotificationManager.java66
-rw-r--r--core/java/android/app/job/JobInfo.java35
-rw-r--r--core/java/android/app/slice/Slice.java149
-rw-r--r--core/java/android/app/slice/SliceItem.java187
-rw-r--r--core/java/android/app/slice/SliceQuery.java55
-rw-r--r--core/java/android/content/CursorLoader.java2
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java28
-rw-r--r--core/java/android/content/pm/PackageInfo.java28
-rw-r--r--core/java/android/content/pm/PackageParser.java19
-rw-r--r--core/java/android/database/sqlite/SQLiteConnectionPool.java10
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java12
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java5
-rw-r--r--core/java/android/hardware/usb/IUsbManager.aidl4
-rw-r--r--core/java/android/hardware/usb/UsbManager.java11
-rw-r--r--core/java/android/net/IpSecAlgorithm.java62
-rw-r--r--core/java/android/net/IpSecConfig.java10
-rw-r--r--core/java/android/net/IpSecManager.java272
-rw-r--r--core/java/android/net/IpSecTransform.java177
-rw-r--r--core/java/android/os/BatteryStats.java48
-rw-r--r--core/java/android/os/Parcel.java2
-rw-r--r--core/java/android/os/PowerManager.java6
-rw-r--r--core/java/android/os/ShellCallback.java3
-rw-r--r--core/java/android/os/ShellCommand.java12
-rw-r--r--core/java/android/view/WindowManagerInternal.java5
-rw-r--r--core/java/android/widget/RemoteViews.java25
-rw-r--r--core/java/com/android/internal/app/procstats/ProcessState.java1
-rw-r--r--core/java/com/android/internal/content/NativeLibraryHelper.java13
-rw-r--r--core/java/com/android/internal/content/PackageHelper.java9
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java36
-rw-r--r--core/jni/com_android_internal_content_NativeLibraryHelper.cpp20
-rw-r--r--core/proto/android/os/batterystats.proto8
-rw-r--r--core/res/AndroidManifest.xml9
-rw-r--r--core/res/res/layout/unsupported_compile_sdk_dialog_content.xml31
-rw-r--r--core/res/res/values-watch/config.xml5
-rw-r--r--core/res/res/values/attrs_manifest.xml19
-rw-r--r--core/res/res/values/config.xml10
-rw-r--r--core/res/res/values/public.xml4
-rw-r--r--core/res/res/values/strings.xml7
-rw-r--r--core/res/res/values/symbols.xml7
-rw-r--r--data/keyboards/OWNERS4
-rw-r--r--libs/androidfw/AssetManager.cpp32
-rw-r--r--libs/androidfw/include/androidfw/AssetManager.h8
-rw-r--r--media/java/android/media/MediaFormat.java8
-rw-r--r--media/java/android/media/MediaMuxer.java16
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java14
-rw-r--r--services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java31
-rw-r--r--services/backup/java/com/android/server/backup/TransportManager.java80
-rw-r--r--services/backup/java/com/android/server/backup/internal/BackupHandler.java33
-rw-r--r--services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java34
-rw-r--r--services/backup/java/com/android/server/backup/internal/PerformBackupTask.java60
-rw-r--r--services/backup/java/com/android/server/backup/params/BackupParams.java16
-rw-r--r--services/backup/java/com/android/server/backup/transport/TransportClient.java468
-rw-r--r--services/backup/java/com/android/server/backup/transport/TransportClientManager.java83
-rw-r--r--services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java37
-rw-r--r--services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java32
-rw-r--r--services/backup/java/com/android/server/backup/transport/TransportUtils.java60
-rw-r--r--services/core/java/com/android/server/LocationManagerService.java2
-rw-r--r--services/core/java/com/android/server/OWNERS7
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java109
-rw-r--r--services/core/java/com/android/server/am/ActivityRecord.java10
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java4
-rw-r--r--services/core/java/com/android/server/am/ActivityStarter.java8
-rw-r--r--services/core/java/com/android/server/am/AppWarnings.java498
-rw-r--r--services/core/java/com/android/server/am/CompatModePackages.java28
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java8
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java15
-rw-r--r--services/core/java/com/android/server/am/TaskRecord.java26
-rw-r--r--services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java83
-rw-r--r--services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java12
-rw-r--r--services/core/java/com/android/server/display/BrightnessIdleJob.java81
-rw-r--r--services/core/java/com/android/server/display/BrightnessTracker.java50
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java8
-rw-r--r--services/core/java/com/android/server/display/OWNERS1
-rw-r--r--services/core/java/com/android/server/input/OWNERS2
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java16
-rw-r--r--services/core/java/com/android/server/job/JobStore.java83
-rw-r--r--services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java125
-rw-r--r--services/core/java/com/android/server/lights/OWNERS1
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java92
-rw-r--r--services/core/java/com/android/server/notification/RankingConfig.java2
-rw-r--r--services/core/java/com/android/server/notification/RankingHelper.java7
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java8
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java72
-rw-r--r--services/core/java/com/android/server/power/BatterySaverPolicy.java101
-rw-r--r--services/core/java/com/android/server/power/OWNERS3
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java15
-rw-r--r--services/core/java/com/android/server/power/batterysaver/BatterySaverController.java9
-rw-r--r--services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java101
-rw-r--r--services/core/java/com/android/server/power/batterysaver/FileUpdater.java235
-rw-r--r--services/core/java/com/android/server/power/batterysaver/OWNERS1
-rw-r--r--services/core/java/com/android/server/utils/AppInstallerUtil.java71
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java20
-rw-r--r--services/robotests/src/com/android/server/backup/transport/TransportClientTest.java240
-rw-r--r--services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java119
-rw-r--r--services/tests/notification/src/com/android/server/notification/RankingHelperTest.java13
-rw-r--r--services/tests/servicestests/res/values/strings.xml4
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java58
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java69
-rw-r--r--services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java337
-rw-r--r--services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java13
-rw-r--r--services/usb/java/com/android/server/usb/UsbHostManager.java5
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java13
-rw-r--r--services/usb/java/com/android/server/usb/UsbUserSettingsManager.java86
-rw-r--r--tools/aapt2/java/ProguardRules.cpp20
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl36
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java32
-rw-r--r--wifi/tests/src/android/net/wifi/WifiManagerTest.java10
155 files changed, 5820 insertions, 1714 deletions
diff --git a/api/current.txt b/api/current.txt
index f8b9f41e634f..ec13b5560d37 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2777,6 +2777,7 @@ package android.accessibilityservice {
field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+ field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -5616,6 +5617,7 @@ package android.app {
method public final int getCurrentInterruptionFilter();
method public int getImportance();
method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+ method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String);
method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
@@ -5628,8 +5630,12 @@ package android.app {
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+ field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+ field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID";
field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
field public static final int IMPORTANCE_HIGH = 4; // 0x4
field public static final int IMPORTANCE_LOW = 2; // 0x2
@@ -6885,6 +6891,7 @@ package android.app.job {
method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+ method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -6988,31 +6995,33 @@ package android.app.slice {
field public static final java.lang.String HINT_LARGE = "large";
field public static final java.lang.String HINT_LIST = "list";
field public static final java.lang.String HINT_LIST_ITEM = "list_item";
- field public static final java.lang.String HINT_MESSAGE = "message";
field public static final java.lang.String HINT_NO_TINT = "no_tint";
field public static final java.lang.String HINT_PARTIAL = "partial";
field public static final java.lang.String HINT_SELECTED = "selected";
- field public static final java.lang.String HINT_SOURCE = "source";
field public static final java.lang.String HINT_TITLE = "title";
+ field public static final java.lang.String SUBTYPE_MESSAGE = "message";
+ field public static final java.lang.String SUBTYPE_SOURCE = "source";
}
public static class Slice.Builder {
ctor public Slice.Builder(android.net.Uri);
ctor public Slice.Builder(android.app.slice.Slice.Builder);
method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice);
- method public android.app.slice.Slice.Builder addColor(int, java.lang.String...);
- method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String);
+ method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List<java.lang.String>);
method public android.app.slice.Slice.Builder addHints(java.lang.String...);
method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...);
- method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...);
+ method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.lang.String...);
method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice);
- method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...);
- method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...);
- method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice, java.lang.String);
+ method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.util.List<java.lang.String>);
method public android.app.slice.Slice build();
method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec);
}
@@ -7021,23 +7030,24 @@ package android.app.slice {
method public int describeContents();
method public android.app.PendingIntent getAction();
method public int getColor();
+ method public java.lang.String getFormat();
method public java.util.List<java.lang.String> getHints();
method public android.graphics.drawable.Icon getIcon();
method public android.app.RemoteInput getRemoteInput();
method public android.app.slice.Slice getSlice();
+ method public java.lang.String getSubType();
method public java.lang.CharSequence getText();
method public long getTimestamp();
- method public int getType();
method public boolean hasHint(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
- field public static final int TYPE_ACTION = 4; // 0x4
- field public static final int TYPE_COLOR = 6; // 0x6
- field public static final int TYPE_IMAGE = 3; // 0x3
- field public static final int TYPE_REMOTE_INPUT = 9; // 0x9
- field public static final int TYPE_SLICE = 1; // 0x1
- field public static final int TYPE_TEXT = 2; // 0x2
- field public static final int TYPE_TIMESTAMP = 8; // 0x8
+ field public static final java.lang.String FORMAT_ACTION = "action";
+ field public static final java.lang.String FORMAT_COLOR = "color";
+ field public static final java.lang.String FORMAT_IMAGE = "image";
+ field public static final java.lang.String FORMAT_REMOTE_INPUT = "input";
+ field public static final java.lang.String FORMAT_SLICE = "slice";
+ field public static final java.lang.String FORMAT_TEXT = "text";
+ field public static final java.lang.String FORMAT_TIMESTAMP = "timestamp";
}
public abstract class SliceProvider extends android.content.ContentProvider {
@@ -23043,6 +23053,7 @@ package android.media {
public static final class MediaMuxer.OutputFormat {
field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2
+ field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3
field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
}
@@ -25852,7 +25863,6 @@ package android.net {
method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
diff --git a/api/system-current.txt b/api/system-current.txt
index 00681a09e7d8..ce3d27795d22 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2919,6 +2919,7 @@ package android.accessibilityservice {
field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+ field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -5825,6 +5826,7 @@ package android.app {
method public final int getCurrentInterruptionFilter();
method public int getImportance();
method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+ method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String);
method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
@@ -5837,8 +5839,12 @@ package android.app {
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+ field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+ field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID";
field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
field public static final int IMPORTANCE_HIGH = 4; // 0x4
field public static final int IMPORTANCE_LOW = 2; // 0x2
@@ -7328,6 +7334,7 @@ package android.app.job {
method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+ method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -7432,31 +7439,33 @@ package android.app.slice {
field public static final java.lang.String HINT_LARGE = "large";
field public static final java.lang.String HINT_LIST = "list";
field public static final java.lang.String HINT_LIST_ITEM = "list_item";
- field public static final java.lang.String HINT_MESSAGE = "message";
field public static final java.lang.String HINT_NO_TINT = "no_tint";
field public static final java.lang.String HINT_PARTIAL = "partial";
field public static final java.lang.String HINT_SELECTED = "selected";
- field public static final java.lang.String HINT_SOURCE = "source";
field public static final java.lang.String HINT_TITLE = "title";
+ field public static final java.lang.String SUBTYPE_MESSAGE = "message";
+ field public static final java.lang.String SUBTYPE_SOURCE = "source";
}
public static class Slice.Builder {
ctor public Slice.Builder(android.net.Uri);
ctor public Slice.Builder(android.app.slice.Slice.Builder);
method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice);
- method public android.app.slice.Slice.Builder addColor(int, java.lang.String...);
- method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String);
+ method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List<java.lang.String>);
method public android.app.slice.Slice.Builder addHints(java.lang.String...);
method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...);
- method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...);
+ method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.lang.String...);
method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice);
- method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...);
- method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...);
- method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice, java.lang.String);
+ method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.util.List<java.lang.String>);
method public android.app.slice.Slice build();
method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec);
}
@@ -7465,23 +7474,24 @@ package android.app.slice {
method public int describeContents();
method public android.app.PendingIntent getAction();
method public int getColor();
+ method public java.lang.String getFormat();
method public java.util.List<java.lang.String> getHints();
method public android.graphics.drawable.Icon getIcon();
method public android.app.RemoteInput getRemoteInput();
method public android.app.slice.Slice getSlice();
+ method public java.lang.String getSubType();
method public java.lang.CharSequence getText();
method public long getTimestamp();
- method public int getType();
method public boolean hasHint(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
- field public static final int TYPE_ACTION = 4; // 0x4
- field public static final int TYPE_COLOR = 6; // 0x6
- field public static final int TYPE_IMAGE = 3; // 0x3
- field public static final int TYPE_REMOTE_INPUT = 9; // 0x9
- field public static final int TYPE_SLICE = 1; // 0x1
- field public static final int TYPE_TEXT = 2; // 0x2
- field public static final int TYPE_TIMESTAMP = 8; // 0x8
+ field public static final java.lang.String FORMAT_ACTION = "action";
+ field public static final java.lang.String FORMAT_COLOR = "color";
+ field public static final java.lang.String FORMAT_IMAGE = "image";
+ field public static final java.lang.String FORMAT_REMOTE_INPUT = "input";
+ field public static final java.lang.String FORMAT_SLICE = "slice";
+ field public static final java.lang.String FORMAT_TEXT = "text";
+ field public static final java.lang.String FORMAT_TIMESTAMP = "timestamp";
}
public abstract class SliceProvider extends android.content.ContentProvider {
@@ -24937,6 +24947,7 @@ package android.media {
public static final class MediaMuxer.OutputFormat {
field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2
+ field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3
field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
}
@@ -28098,7 +28109,6 @@ package android.net {
method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
diff --git a/api/test-current.txt b/api/test-current.txt
index bc82fbe8cdc9..8a718f588d70 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2777,6 +2777,7 @@ package android.accessibilityservice {
field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+ field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -5647,6 +5648,7 @@ package android.app {
method public android.content.ComponentName getEffectsSuppressor();
method public int getImportance();
method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+ method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String);
method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
@@ -5659,8 +5661,12 @@ package android.app {
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+ field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+ field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID";
field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
field public static final int IMPORTANCE_HIGH = 4; // 0x4
field public static final int IMPORTANCE_LOW = 2; // 0x2
@@ -6959,6 +6965,7 @@ package android.app.job {
method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+ method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -7062,31 +7069,33 @@ package android.app.slice {
field public static final java.lang.String HINT_LARGE = "large";
field public static final java.lang.String HINT_LIST = "list";
field public static final java.lang.String HINT_LIST_ITEM = "list_item";
- field public static final java.lang.String HINT_MESSAGE = "message";
field public static final java.lang.String HINT_NO_TINT = "no_tint";
field public static final java.lang.String HINT_PARTIAL = "partial";
field public static final java.lang.String HINT_SELECTED = "selected";
- field public static final java.lang.String HINT_SOURCE = "source";
field public static final java.lang.String HINT_TITLE = "title";
+ field public static final java.lang.String SUBTYPE_MESSAGE = "message";
+ field public static final java.lang.String SUBTYPE_SOURCE = "source";
}
public static class Slice.Builder {
ctor public Slice.Builder(android.net.Uri);
ctor public Slice.Builder(android.app.slice.Slice.Builder);
method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice);
- method public android.app.slice.Slice.Builder addColor(int, java.lang.String...);
- method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String);
+ method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List<java.lang.String>);
method public android.app.slice.Slice.Builder addHints(java.lang.String...);
method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...);
- method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...);
+ method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.lang.String...);
method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice);
- method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...);
- method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...);
- method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice, java.lang.String);
+ method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.util.List<java.lang.String>);
+ method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.lang.String...);
+ method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.util.List<java.lang.String>);
method public android.app.slice.Slice build();
method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec);
}
@@ -7095,23 +7104,24 @@ package android.app.slice {
method public int describeContents();
method public android.app.PendingIntent getAction();
method public int getColor();
+ method public java.lang.String getFormat();
method public java.util.List<java.lang.String> getHints();
method public android.graphics.drawable.Icon getIcon();
method public android.app.RemoteInput getRemoteInput();
method public android.app.slice.Slice getSlice();
+ method public java.lang.String getSubType();
method public java.lang.CharSequence getText();
method public long getTimestamp();
- method public int getType();
method public boolean hasHint(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
- field public static final int TYPE_ACTION = 4; // 0x4
- field public static final int TYPE_COLOR = 6; // 0x6
- field public static final int TYPE_IMAGE = 3; // 0x3
- field public static final int TYPE_REMOTE_INPUT = 9; // 0x9
- field public static final int TYPE_SLICE = 1; // 0x1
- field public static final int TYPE_TEXT = 2; // 0x2
- field public static final int TYPE_TIMESTAMP = 8; // 0x8
+ field public static final java.lang.String FORMAT_ACTION = "action";
+ field public static final java.lang.String FORMAT_COLOR = "color";
+ field public static final java.lang.String FORMAT_IMAGE = "image";
+ field public static final java.lang.String FORMAT_REMOTE_INPUT = "input";
+ field public static final java.lang.String FORMAT_SLICE = "slice";
+ field public static final java.lang.String FORMAT_TEXT = "text";
+ field public static final java.lang.String FORMAT_TIMESTAMP = "timestamp";
}
public abstract class SliceProvider extends android.content.ContentProvider {
@@ -23246,6 +23256,7 @@ package android.media {
public static final class MediaMuxer.OutputFormat {
field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2
+ field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3
field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
}
diff --git a/cmds/pm/Android.mk b/cmds/pm/Android.mk
index 6a03defc9e2c..960c8052f935 100644
--- a/cmds/pm/Android.mk
+++ b/cmds/pm/Android.mk
@@ -3,14 +3,8 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_MODULE := pmlib
-LOCAL_MODULE_STEM := pm
-include $(BUILD_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
LOCAL_MODULE := pm
-LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_SRC_FILES := pm
-LOCAL_REQUIRED_MODULES := pmlib
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)
diff --git a/cmds/pm/pm b/cmds/pm/pm
index 53f85b2f9c51..4d1f94554a78 100755
--- a/cmds/pm/pm
+++ b/cmds/pm/pm
@@ -1,8 +1,2 @@
#!/system/bin/sh
-# Script to start "pm" on the device, which has a very rudimentary
-# shell.
-#
-base=/system
-export CLASSPATH=$base/framework/pm.jar
-exec app_process $base/bin com.android.commands.pm.Pm "$@"
-
+cmd package "$@"
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
deleted file mode 100644
index 9490880afe0f..000000000000
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ /dev/null
@@ -1,822 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.commands.pm;
-
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-
-import android.accounts.IAccountManager;
-import android.app.ActivityManager;
-import android.app.PackageInstallObserver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDataObserver;
-import android.content.pm.IPackageInstaller;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageInstaller.SessionParams;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
-import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.UserInfo;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.IUserManager;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.SELinux;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.content.PackageHelper;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.SizedInputStream;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.TimeUnit;
-
-public final class Pm {
- private static final String TAG = "Pm";
- private static final String STDIN_PATH = "-";
-
- IPackageManager mPm;
- IPackageInstaller mInstaller;
- IUserManager mUm;
- IAccountManager mAm;
-
- private String[] mArgs;
- private int mNextArg;
- private String mCurArgData;
-
- private static final String PM_NOT_RUNNING_ERR =
- "Error: Could not access the Package Manager. Is the system running?";
-
- public static void main(String[] args) {
- int exitCode = 1;
- try {
- exitCode = new Pm().run(args);
- } catch (Exception e) {
- Log.e(TAG, "Error", e);
- System.err.println("Error: " + e);
- if (e instanceof RemoteException) {
- System.err.println(PM_NOT_RUNNING_ERR);
- }
- }
- System.exit(exitCode);
- }
-
- public int run(String[] args) throws RemoteException {
- if (args.length < 1) {
- return runShellCommand("package", mArgs);
- }
- mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
- mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
- mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-
- if (mPm == null) {
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- mInstaller = mPm.getPackageInstaller();
-
- mArgs = args;
- String op = args[0];
- mNextArg = 1;
-
- if ("install".equals(op)) {
- return runInstall();
- }
-
- if ("install-create".equals(op)) {
- return runInstallCreate();
- }
-
- if ("install-write".equals(op)) {
- return runInstallWrite();
- }
-
- if ("install-commit".equals(op)) {
- return runInstallCommit();
- }
-
- if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
- return runInstallAbandon();
- }
-
- return runShellCommand("package", mArgs);
- }
-
- static final class MyShellCallback extends ShellCallback {
- @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
- String mode) {
- File file = new File(path);
- final ParcelFileDescriptor fd;
- try {
- fd = ParcelFileDescriptor.open(file,
- ParcelFileDescriptor.MODE_CREATE |
- ParcelFileDescriptor.MODE_TRUNCATE |
- ParcelFileDescriptor.MODE_WRITE_ONLY);
- } catch (FileNotFoundException e) {
- String msg = "Unable to open file " + path + ": " + e;
- System.err.println(msg);
- throw new IllegalArgumentException(msg);
- }
- if (seLinuxContext != null) {
- final String tcon = SELinux.getFileContext(file.getAbsolutePath());
- if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
- try {
- fd.close();
- } catch (IOException e) {
- }
- String msg = "System server has no access to file context " + tcon;
- System.err.println(msg + " (from path " + file.getAbsolutePath()
- + ", context " + seLinuxContext + ")");
- throw new IllegalArgumentException(msg);
- }
- }
- return fd;
- }
- }
-
- private int runShellCommand(String serviceName, String[] args) {
- final HandlerThread handlerThread = new HandlerThread("results");
- handlerThread.start();
- try {
- ServiceManager.getService(serviceName).shellCommand(
- FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- args, new MyShellCallback(),
- new ResultReceiver(new Handler(handlerThread.getLooper())));
- return 0;
- } catch (RemoteException e) {
- e.printStackTrace();
- } finally {
- handlerThread.quitSafely();
- }
- return -1;
- }
-
- private static class LocalIntentReceiver {
- private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
-
- private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
- @Override
- public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
- IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
- try {
- mResult.offer(intent, 5, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- };
-
- public IntentSender getIntentSender() {
- return new IntentSender((IIntentSender) mLocalSender);
- }
-
- public Intent getResult() {
- try {
- return mResult.take();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- private int translateUserId(int userId, String logContext) {
- return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, true, true, logContext, "pm command");
- }
-
- private static String checkAbiArgument(String abi) {
- if (TextUtils.isEmpty(abi)) {
- throw new IllegalArgumentException("Missing ABI argument");
- }
- if ("-".equals(abi)) {
- return abi;
- }
- final String[] supportedAbis = Build.SUPPORTED_ABIS;
- for (String supportedAbi : supportedAbis) {
- if (supportedAbi.equals(abi)) {
- return abi;
- }
- }
- throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
- }
-
- /*
- * Keep this around to support existing users of the "pm install" command that may not be
- * able to be updated [or, at least informed the API has changed] such as ddmlib.
- *
- * Moving the implementation of "pm install" to "cmd package install" changes the executing
- * context. Instead of being a stand alone process, "cmd package install" runs in the
- * system_server process. Due to SELinux rules, system_server cannot access many directories;
- * one of which being the package install staging directory [/data/local/tmp].
- *
- * The use of "adb install" or "cmd package install" over "pm install" is highly encouraged.
- */
- private int runInstall() throws RemoteException {
- long startedTime = SystemClock.elapsedRealtime();
- final InstallParams params = makeInstallParams();
- final String inPath = nextArg();
- if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
- File file = new File(inPath);
- if (file.isFile()) {
- try {
- ApkLite baseApk = PackageParser.parseApkLite(file, 0);
- PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
- null, null);
- params.sessionParams.setSize(
- PackageHelper.calculateInstalledSize(pkgLite,
- params.sessionParams.abiOverride));
- } catch (PackageParserException | IOException e) {
- System.err.println("Error: Failed to parse APK file: " + e);
- return 1;
- }
- } else {
- System.err.println("Error: Can't open non-file: " + inPath);
- return 1;
- }
- }
-
- final int sessionId = doCreateSession(params.sessionParams,
- params.installerPackageName, params.userId);
-
- try {
- if (inPath == null && params.sessionParams.sizeBytes == -1) {
- System.err.println("Error: must either specify a package size or an APK file");
- return 1;
- }
- if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
- false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/);
- if (status.second != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime()
- - startedTime) + " ms");
- System.out.println("Success");
- return 0;
- } finally {
- try {
- mInstaller.abandonSession(sessionId);
- } catch (Exception ignore) {
- }
- }
- }
-
- private int runInstallAbandon() throws RemoteException {
- final int sessionId = Integer.parseInt(nextArg());
- return doAbandonSession(sessionId, true /*logSuccess*/);
- }
-
- private int runInstallCommit() throws RemoteException {
- final int sessionId = Integer.parseInt(nextArg());
- return doCommitSession(sessionId, true /*logSuccess*/).second;
- }
-
- private int runInstallCreate() throws RemoteException {
- final InstallParams installParams = makeInstallParams();
- final int sessionId = doCreateSession(installParams.sessionParams,
- installParams.installerPackageName, installParams.userId);
-
- // NOTE: adb depends on parsing this string
- System.out.println("Success: created install session [" + sessionId + "]");
- return PackageInstaller.STATUS_SUCCESS;
- }
-
- private int runInstallWrite() throws RemoteException {
- long sizeBytes = -1;
-
- String opt;
- while ((opt = nextOption()) != null) {
- if (opt.equals("-S")) {
- sizeBytes = Long.parseLong(nextArg());
- } else {
- throw new IllegalArgumentException("Unknown option: " + opt);
- }
- }
-
- final int sessionId = Integer.parseInt(nextArg());
- final String splitName = nextArg();
- final String path = nextArg();
- return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
- }
-
- private static class InstallParams {
- SessionParams sessionParams;
- String installerPackageName;
- int userId = UserHandle.USER_ALL;
- }
-
- private InstallParams makeInstallParams() {
- final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
- final InstallParams params = new InstallParams();
- params.sessionParams = sessionParams;
- String opt;
- while ((opt = nextOption()) != null) {
- switch (opt) {
- case "-l":
- sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
- break;
- case "-r":
- sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
- break;
- case "-i":
- params.installerPackageName = nextArg();
- if (params.installerPackageName == null) {
- throw new IllegalArgumentException("Missing installer package");
- }
- break;
- case "-t":
- sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
- break;
- case "-s":
- sessionParams.installFlags |= PackageManager.INSTALL_EXTERNAL;
- break;
- case "-f":
- sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL;
- break;
- case "-d":
- sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
- break;
- case "-g":
- sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
- break;
- case "--dont-kill":
- sessionParams.installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
- break;
- case "--originating-uri":
- sessionParams.originatingUri = Uri.parse(nextOptionData());
- break;
- case "--referrer":
- sessionParams.referrerUri = Uri.parse(nextOptionData());
- break;
- case "-p":
- sessionParams.mode = SessionParams.MODE_INHERIT_EXISTING;
- sessionParams.appPackageName = nextOptionData();
- if (sessionParams.appPackageName == null) {
- throw new IllegalArgumentException("Missing inherit package name");
- }
- break;
- case "--pkg":
- sessionParams.appPackageName = nextOptionData();
- if (sessionParams.appPackageName == null) {
- throw new IllegalArgumentException("Missing package name");
- }
- break;
- case "-S":
- final long sizeBytes = Long.parseLong(nextOptionData());
- if (sizeBytes <= 0) {
- throw new IllegalArgumentException("Size must be positive");
- }
- sessionParams.setSize(sizeBytes);
- break;
- case "--abi":
- sessionParams.abiOverride = checkAbiArgument(nextOptionData());
- break;
- case "--ephemeral":
- case "--instant":
- sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
- break;
- case "--full":
- sessionParams.setInstallAsInstantApp(false /*isInstantApp*/);
- break;
- case "--user":
- params.userId = UserHandle.parseUserArg(nextOptionData());
- break;
- case "--install-location":
- sessionParams.installLocation = Integer.parseInt(nextOptionData());
- break;
- case "--force-uuid":
- sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
- sessionParams.volumeUuid = nextOptionData();
- if ("internal".equals(sessionParams.volumeUuid)) {
- sessionParams.volumeUuid = null;
- }
- break;
- case "--force-sdk":
- sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
- break;
- default:
- throw new IllegalArgumentException("Unknown option " + opt);
- }
- }
- return params;
- }
-
- private int doCreateSession(SessionParams params, String installerPackageName, int userId)
- throws RemoteException {
- userId = translateUserId(userId, "runInstallCreate");
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
- params.installFlags |= PackageManager.INSTALL_ALL_USERS;
- }
-
- final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
- return sessionId;
- }
-
- private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName,
- boolean logSuccess) throws RemoteException {
- if (STDIN_PATH.equals(inPath)) {
- inPath = null;
- } else if (inPath != null) {
- final File file = new File(inPath);
- if (file.isFile()) {
- sizeBytes = file.length();
- }
- }
-
- final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-
- PackageInstaller.Session session = null;
- InputStream in = null;
- OutputStream out = null;
- try {
- session = new PackageInstaller.Session(
- mInstaller.openSession(sessionId));
-
- if (inPath != null) {
- in = new FileInputStream(inPath);
- } else {
- in = new SizedInputStream(System.in, sizeBytes);
- }
- out = session.openWrite(splitName, 0, sizeBytes);
-
- int total = 0;
- byte[] buffer = new byte[1024 * 1024];
- int c;
- while ((c = in.read(buffer)) != -1) {
- total += c;
- out.write(buffer, 0, c);
-
- if (info.sizeBytes > 0) {
- final float fraction = ((float) c / (float) info.sizeBytes);
- session.addProgress(fraction);
- }
- }
- session.fsync(out);
-
- if (logSuccess) {
- System.out.println("Success: streamed " + total + " bytes");
- }
- return PackageInstaller.STATUS_SUCCESS;
- } catch (IOException e) {
- System.err.println("Error: failed to write; " + e.getMessage());
- return PackageInstaller.STATUS_FAILURE;
- } finally {
- IoUtils.closeQuietly(out);
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(session);
- }
- }
-
- private Pair<String, Integer> doCommitSession(int sessionId, boolean logSuccess)
- throws RemoteException {
- PackageInstaller.Session session = null;
- try {
- session = new PackageInstaller.Session(
- mInstaller.openSession(sessionId));
-
- final LocalIntentReceiver receiver = new LocalIntentReceiver();
- session.commit(receiver.getIntentSender());
-
- final Intent result = receiver.getResult();
- final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
- if (status == PackageInstaller.STATUS_SUCCESS) {
- if (logSuccess) {
- System.out.println("Success");
- }
- } else {
- System.err.println("Failure ["
- + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
- }
- return new Pair<>(result.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME), status);
- } finally {
- IoUtils.closeQuietly(session);
- }
- }
-
- private int doAbandonSession(int sessionId, boolean logSuccess) throws RemoteException {
- PackageInstaller.Session session = null;
- try {
- session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
- session.abandon();
- if (logSuccess) {
- System.out.println("Success");
- }
- return PackageInstaller.STATUS_SUCCESS;
- } finally {
- IoUtils.closeQuietly(session);
- }
- }
-
- class LocalPackageInstallObserver extends PackageInstallObserver {
- boolean finished;
- int result;
- String extraPermission;
- String extraPackage;
-
- @Override
- public void onPackageInstalled(String name, int status, String msg, Bundle extras) {
- synchronized (this) {
- finished = true;
- result = status;
- if (status == PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION) {
- extraPermission = extras.getString(
- PackageManager.EXTRA_FAILURE_EXISTING_PERMISSION);
- extraPackage = extras.getString(
- PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
- }
- notifyAll();
- }
- }
- }
-
- private static boolean isNumber(String s) {
- try {
- Integer.parseInt(s);
- } catch (NumberFormatException nfe) {
- return false;
- }
- return true;
- }
-
- static class ClearCacheObserver extends IPackageDataObserver.Stub {
- boolean finished;
- boolean result;
-
- @Override
- public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
- synchronized (this) {
- finished = true;
- result = succeeded;
- notifyAll();
- }
- }
-
- }
-
- static class ClearDataObserver extends IPackageDataObserver.Stub {
- boolean finished;
- boolean result;
-
- @Override
- public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
- synchronized (this) {
- finished = true;
- result = succeeded;
- notifyAll();
- }
- }
- }
-
- /**
- * Displays the package file for a package.
- * @param pckg
- */
- private int displayPackageFilePath(String pckg, int userId) {
- try {
- PackageInfo info = mPm.getPackageInfo(pckg, 0, userId);
- if (info != null && info.applicationInfo != null) {
- System.out.print("package:");
- System.out.println(info.applicationInfo.sourceDir);
- if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
- for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
- System.out.print("package:");
- System.out.println(splitSourceDir);
- }
- }
- return 0;
- }
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- }
- return 1;
- }
-
- private String nextOption() {
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mNextArg];
- if (!arg.startsWith("-")) {
- return null;
- }
- mNextArg++;
- if (arg.equals("--")) {
- return null;
- }
- if (arg.length() > 1 && arg.charAt(1) != '-') {
- if (arg.length() > 2) {
- mCurArgData = arg.substring(2);
- return arg.substring(0, 2);
- } else {
- mCurArgData = null;
- return arg;
- }
- }
- mCurArgData = null;
- return arg;
- }
-
- private String nextOptionData() {
- if (mCurArgData != null) {
- return mCurArgData;
- }
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String data = mArgs[mNextArg];
- mNextArg++;
- return data;
- }
-
- private String nextArg() {
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mNextArg];
- mNextArg++;
- return arg;
- }
-
- private static int showUsage() {
- System.err.println("usage: pm path [--user USER_ID] PACKAGE");
- System.err.println(" pm dump PACKAGE");
- System.err.println(" pm install [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
- System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
- System.err.println(" [--originating-uri URI] [---referrer URI]");
- System.err.println(" [--abi ABI_NAME] [--force-sdk]");
- System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]");
- System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
- System.err.println(" pm install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
- System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
- System.err.println(" [--originating-uri URI] [---referrer URI]");
- System.err.println(" [--abi ABI_NAME] [--force-sdk]");
- System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]");
- System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
- System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
- System.err.println(" pm install-commit SESSION_ID");
- System.err.println(" pm install-abandon SESSION_ID");
- System.err.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE");
- System.err.println(" pm set-installer PACKAGE INSTALLER");
- System.err.println(" pm move-package PACKAGE [internal|UUID]");
- System.err.println(" pm move-primary-storage [internal|UUID]");
- System.err.println(" pm clear [--user USER_ID] PACKAGE");
- System.err.println(" pm enable [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm default-state [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm set-user-restriction [--user USER_ID] RESTRICTION VALUE");
- System.err.println(" pm hide [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm grant [--user USER_ID] PACKAGE PERMISSION");
- System.err.println(" pm revoke [--user USER_ID] PACKAGE PERMISSION");
- System.err.println(" pm reset-permissions");
- System.err.println(" pm set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}");
- System.err.println(" pm get-app-link [--user USER_ID] PACKAGE");
- System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]");
- System.err.println(" pm get-install-location");
- System.err.println(" pm set-permission-enforced PERMISSION [true|false]");
- System.err.println(" pm trim-caches DESIRED_FREE_SPACE [internal|UUID]");
- System.err.println(" pm create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral] [--guest] USER_NAME");
- System.err.println(" pm remove-user USER_ID");
- System.err.println(" pm get-max-users");
- System.err.println("");
- System.err.println("NOTE: 'pm list' commands have moved! Run 'adb shell cmd package'");
- System.err.println(" to display the new commands.");
- System.err.println("");
- System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
- System.err.println("");
- System.err.println("pm dump: print system state associated with the given PACKAGE.");
- System.err.println("");
- System.err.println("pm install: install a single legacy package");
- System.err.println("pm install-create: create an install session");
- System.err.println(" -l: forward lock application");
- System.err.println(" -r: allow replacement of existing application");
- System.err.println(" -t: allow test packages");
- System.err.println(" -i: specify package name of installer owning the app");
- System.err.println(" -s: install application on sdcard");
- System.err.println(" -f: install application on internal flash");
- System.err.println(" -d: allow version code downgrade (debuggable packages only)");
- System.err.println(" -p: partial application install (new split on top of existing pkg)");
- System.err.println(" -g: grant all runtime permissions");
- System.err.println(" -S: size in bytes of entire session");
- System.err.println(" --dont-kill: installing a new feature split, don't kill running app");
- System.err.println(" --originating-uri: set URI where app was downloaded from");
- System.err.println(" --referrer: set URI that instigated the install of the app");
- System.err.println(" --pkg: specify expected package name of app being installed");
- System.err.println(" --abi: override the default ABI of the platform");
- System.err.println(" --instantapp: cause the app to be installed as an ephemeral install app");
- System.err.println(" --full: cause the app to be installed as a non-ephemeral full app");
- System.err.println(" --install-location: force the install location:");
- System.err.println(" 0=auto, 1=internal only, 2=prefer external");
- System.err.println(" --force-uuid: force install on to disk volume with given UUID");
- System.err.println(" --force-sdk: allow install even when existing app targets platform");
- System.err.println(" codename but new one targets a final API level");
- System.err.println("");
- System.err.println("pm install-write: write a package into existing session; path may");
- System.err.println(" be '-' to read from stdin");
- System.err.println(" -S: size in bytes of package, required for stdin");
- System.err.println("");
- System.err.println("pm install-commit: perform install of fully staged session");
- System.err.println("pm install-abandon: abandon session");
- System.err.println("");
- System.err.println("pm set-installer: set installer package name");
- System.err.println("");
- System.err.println("pm uninstall: removes a package from the system. Options:");
- System.err.println(" -k: keep the data and cache directories around after package removal.");
- System.err.println("");
- System.err.println("pm clear: deletes all data associated with a package.");
- System.err.println("");
- System.err.println("pm enable, disable, disable-user, disable-until-used, default-state:");
- System.err.println(" these commands change the enabled state of a given package or");
- System.err.println(" component (written as \"package/class\").");
- System.err.println("");
- System.err.println("pm grant, revoke: these commands either grant or revoke permissions");
- System.err.println(" to apps. The permissions must be declared as used in the app's");
- System.err.println(" manifest, be runtime permissions (protection level dangerous),");
- System.err.println(" and the app targeting SDK greater than Lollipop MR1.");
- System.err.println("");
- System.err.println("pm reset-permissions: revert all runtime permissions to their default state.");
- System.err.println("");
- System.err.println("pm get-install-location: returns the current install location.");
- System.err.println(" 0 [auto]: Let system decide the best location");
- System.err.println(" 1 [internal]: Install on internal device storage");
- System.err.println(" 2 [external]: Install on external media");
- System.err.println("");
- System.err.println("pm set-install-location: changes the default install location.");
- System.err.println(" NOTE: this is only intended for debugging; using this can cause");
- System.err.println(" applications to break and other undersireable behavior.");
- System.err.println(" 0 [auto]: Let system decide the best location");
- System.err.println(" 1 [internal]: Install on internal device storage");
- System.err.println(" 2 [external]: Install on external media");
- System.err.println("");
- System.err.println("pm trim-caches: trim cache files to reach the given free space.");
- System.err.println("");
- System.err.println("pm create-user: create a new user with the given USER_NAME,");
- System.err.println(" printing the new user identifier of the user.");
- System.err.println("");
- System.err.println("pm remove-user: remove the user with the given USER_IDENTIFIER,");
- System.err.println(" deleting all data associated with that user");
- System.err.println("");
- return 1;
- }
-}
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 1b2f9da22ec1..fd9465d68b37 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -55,7 +55,8 @@ statsd_common_src := \
src/storage/DropboxWriter.cpp \
src/StatsLogProcessor.cpp \
src/StatsService.cpp \
- src/stats_util.cpp
+ src/stats_util.cpp \
+ src/guardrail/MemoryLeakTrackUtil.cpp
statsd_common_c_includes := \
$(LOCAL_PATH)/src \
@@ -82,7 +83,8 @@ statsd_common_shared_libraries := \
libhidltransport \
libhwbinder \
android.hardware.power@1.0 \
- android.hardware.power@1.1
+ android.hardware.power@1.1 \
+ libmemunreachable
# =========
# statsd
@@ -178,3 +180,7 @@ statsd_common_aidl_includes:=
statsd_common_c_includes:=
include $(BUILD_NATIVE_TEST)
+
+##############################
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 1a056df8e367..65b3da1cd258 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -17,10 +17,11 @@
#define DEBUG true
#include "Log.h"
-#include "android-base/stringprintf.h"
#include "StatsService.h"
+#include "android-base/stringprintf.h"
#include "config/ConfigKey.h"
#include "config/ConfigManager.h"
+#include "guardrail/MemoryLeakTrackUtil.h"
#include "storage/DropboxReader.h"
#include <android-base/file.h>
@@ -226,6 +227,10 @@ status_t StatsService::command(FILE* in, FILE* out, FILE* err, Vector<String8>&
if (!args[0].compare(String8("clear-config"))) {
return cmd_remove_config_files(out);
}
+
+ if (!args[0].compare(String8("meminfo"))) {
+ return cmd_dump_memory_info(out);
+ }
}
print_cmd_help(out);
@@ -238,6 +243,15 @@ void StatsService::print_cmd_help(FILE* out) {
"[timestamp_nsec_optional]\n");
fprintf(out, "\n");
fprintf(out, "\n");
+ fprintf(out, "usage: adb shell cmd stats meminfo\n");
+ fprintf(out, "\n");
+ fprintf(out, " Prints the malloc debug information. You need to run the following first: \n");
+ fprintf(out, " # adb shell stop\n");
+ fprintf(out, " # adb shell setprop libc.debug.malloc.program statsd \n");
+ fprintf(out, " # adb shell setprop libc.debug.malloc.options backtrace \n");
+ fprintf(out, " # adb shell start\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
fprintf(out, "usage: adb shell cmd stats print-uid-map \n");
fprintf(out, "\n");
fprintf(out, " Prints the UID, app name, version mapping.\n");
@@ -507,6 +521,13 @@ status_t StatsService::cmd_remove_config_files(FILE* out) {
return NO_ERROR;
}
+status_t StatsService::cmd_dump_memory_info(FILE* out) {
+ std::string s = dumpMemInfo(100);
+ fprintf(out, "Memory Info\n");
+ fprintf(out, "%s", s.c_str());
+ return NO_ERROR;
+}
+
Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
const vector<String16>& app) {
if (DEBUG) ALOGD("StatsService::informAllUidData was called");
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 4d768f630362..47f0bb601fef 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -160,6 +160,11 @@ private:
*/
status_t cmd_remove_config_files(FILE* out);
+ /*
+ * Dump memory usage by statsd.
+ */
+ status_t cmd_dump_memory_info(FILE* out);
+
/**
* Update a configuration.
*/
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 57a92b61a5c9..20e7c600938e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -74,6 +74,7 @@ message Atom {
ActivityForegroundStateChanged activity_foreground_state_changed = 42;
IsolatedUidChanged isolated_uid_changed = 43;
PacketWakeupOccurred packet_wakeup_occurred = 44;
+ DropboxErrorChanged dropbox_error_changed = 45;
// TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
}
@@ -716,6 +717,34 @@ message ActivityForegroundStateChanged {
}
/**
+ * Logs when an error is written to dropbox.
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ */
+message DropboxErrorChanged {
+ // The uid if available. -1 means not available.
+ optional int32 uid = 1;
+
+ // Tag used when recording this error to dropbox. Contains data_ or system_ prefix.
+ optional string tag = 2;
+
+ // The name of the process.
+ optional string process_name = 3;
+
+ // The pid if available. -1 means not available.
+ optional int32 pid = 4;
+
+ // 1 indicates is instant app. -1 indicates Not applicable.
+ optional int32 is_instant_app = 5;
+
+ // The activity name if available.
+ optional string activity_name = 6;
+
+ // 1 indicates in foreground. -1 indicates not available.
+ optional int32 is_foreground = 7;
+}
+
+/**
* Pulls bytes transferred via wifi (Sum of foreground and background usage).
*
* Pulled from:
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 21256097a766..0c9252e2095f 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -232,7 +232,7 @@ void ConfigManager::update_saved_configs(const ConfigKey& key, const StatsdConfi
static StatsdConfig build_fake_config() {
// HACK: Hard code a test metric for counting screen on events...
StatsdConfig config;
- config.set_name("12345");
+ config.set_name("CONFIG_12345");
int WAKE_LOCK_TAG_ID = 1111; // put a fake id here to make testing easier.
int WAKE_LOCK_UID_KEY_ID = 1;
@@ -263,20 +263,21 @@ static StatsdConfig build_fake_config() {
// Count Screen ON events.
CountMetric* metric = config.add_count_metric();
- metric->set_name("1");
+ metric->set_name("METRIC_1");
metric->set_what("SCREEN_TURNED_ON");
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
// Anomaly threshold for screen-on count.
Alert* alert = config.add_alert();
- alert->set_name("1");
+ alert->set_name("ALERT_1");
+ alert->set_metric_name("METRIC_1");
alert->set_number_of_buckets(6);
alert->set_trigger_if_sum_gt(10);
alert->set_refractory_period_secs(30);
// Count process state changes, slice by uid.
metric = config.add_count_metric();
- metric->set_name("2");
+ metric->set_name("METRIC_2");
metric->set_what("PROCESS_STATE_CHANGE");
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
KeyMatcher* keyMatcher = metric->add_dimension();
@@ -284,14 +285,15 @@ static StatsdConfig build_fake_config() {
// Anomaly threshold for background count.
alert = config.add_alert();
- alert->set_name("2");
+ alert->set_name("ALERT_2");
+ alert->set_metric_name("METRIC_2");
alert->set_number_of_buckets(4);
alert->set_trigger_if_sum_gt(30);
alert->set_refractory_period_secs(20);
// Count process state changes, slice by uid, while SCREEN_IS_OFF
metric = config.add_count_metric();
- metric->set_name("3");
+ metric->set_name("METRIC_3");
metric->set_what("PROCESS_STATE_CHANGE");
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
keyMatcher = metric->add_dimension();
@@ -300,7 +302,7 @@ static StatsdConfig build_fake_config() {
// Count wake lock, slice by uid, while SCREEN_IS_ON and app in background
metric = config.add_count_metric();
- metric->set_name("4");
+ metric->set_name("METRIC_4");
metric->set_what("APP_GET_WL");
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
keyMatcher = metric->add_dimension();
@@ -313,7 +315,7 @@ static StatsdConfig build_fake_config() {
// Duration of an app holding any wl, while screen on and app in background, slice by uid
DurationMetric* durationMetric = config.add_duration_metric();
- durationMetric->set_name("5");
+ durationMetric->set_name("METRIC_5");
durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
keyMatcher = durationMetric->add_dimension();
@@ -327,7 +329,7 @@ static StatsdConfig build_fake_config() {
// max Duration of an app holding any wl, while screen on and app in background, slice by uid
durationMetric = config.add_duration_metric();
- durationMetric->set_name("6");
+ durationMetric->set_name("METRIC_6");
durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
keyMatcher = durationMetric->add_dimension();
@@ -341,7 +343,7 @@ static StatsdConfig build_fake_config() {
// Duration of an app holding any wl, while screen on and app in background
durationMetric = config.add_duration_metric();
- durationMetric->set_name("7");
+ durationMetric->set_name("METRIC_7");
durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
@@ -353,14 +355,14 @@ static StatsdConfig build_fake_config() {
// Duration of screen on time.
durationMetric = config.add_duration_metric();
- durationMetric->set_name("8");
+ durationMetric->set_name("METRIC_8");
durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
durationMetric->set_what("SCREEN_IS_ON");
// Value metric to count KERNEL_WAKELOCK when screen turned on
ValueMetric* valueMetric = config.add_value_metric();
- valueMetric->set_name("6");
+ valueMetric->set_name("METRIC_6");
valueMetric->set_what("KERNEL_WAKELOCK");
valueMetric->set_value_field(1);
valueMetric->set_condition("SCREEN_IS_ON");
@@ -371,12 +373,12 @@ static StatsdConfig build_fake_config() {
// Add an EventMetric to log process state change events.
EventMetric* eventMetric = config.add_event_metric();
- eventMetric->set_name("9");
+ eventMetric->set_name("METRIC_9");
eventMetric->set_what("SCREEN_TURNED_ON");
// Add an GaugeMetric.
GaugeMetric* gaugeMetric = config.add_gauge_metric();
- gaugeMetric->set_name("10");
+ gaugeMetric->set_name("METRIC_10");
gaugeMetric->set_what("DEVICE_TEMPERATURE");
gaugeMetric->set_gauge_field(DEVICE_TEMPERATURE_KEY);
gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
diff --git a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp
new file mode 100644
index 000000000000..e1947c4a5fe8
--- /dev/null
+++ b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include <sstream>
+#include "MemoryLeakTrackUtil.h"
+
+/*
+ * The code here originally resided in MediaPlayerService.cpp
+ */
+
+// Figure out the abi based on defined macros.
+#if defined(__arm__)
+#define ABI_STRING "arm"
+#elif defined(__aarch64__)
+#define ABI_STRING "arm64"
+#elif defined(__mips__) && !defined(__LP64__)
+#define ABI_STRING "mips"
+#elif defined(__mips__) && defined(__LP64__)
+#define ABI_STRING "mips64"
+#elif defined(__i386__)
+#define ABI_STRING "x86"
+#elif defined(__x86_64__)
+#define ABI_STRING "x86_64"
+#else
+#error "Unsupported ABI"
+#endif
+
+extern std::string backtrace_string(const uintptr_t* frames, size_t frame_count);
+
+namespace android {
+namespace os {
+namespace statsd {
+
+extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize, size_t* infoSize,
+ size_t* totalMemory, size_t* backtraceSize);
+
+extern "C" void free_malloc_leak_info(uint8_t* info);
+
+std::string dumpMemInfo(size_t limit) {
+ uint8_t* info;
+ size_t overallSize;
+ size_t infoSize;
+ size_t totalMemory;
+ size_t backtraceSize;
+ get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory, &backtraceSize);
+
+ size_t count;
+ if (info == nullptr || overallSize == 0 || infoSize == 0 ||
+ (count = overallSize / infoSize) == 0) {
+ ALOGD("no malloc info, libc.debug.malloc.program property should be set");
+ return std::string();
+ }
+
+ std::ostringstream oss;
+ oss << totalMemory << " bytes in " << count << " allocations\n";
+ oss << " ABI: '" ABI_STRING "'"
+ << "\n\n";
+ if (count > limit) count = limit;
+
+ // The memory is sorted based on total size which is useful for finding
+ // worst memory offenders. For diffs, sometimes it is preferable to sort
+ // based on the backtrace.
+ for (size_t i = 0; i < count; i++) {
+ struct AllocEntry {
+ size_t size; // bit 31 is set if this is zygote allocated memory
+ size_t allocations;
+ uintptr_t backtrace[];
+ };
+
+ const AllocEntry* const e = (AllocEntry*)(info + i * infoSize);
+
+ oss << (e->size * e->allocations) << " bytes ( " << e->size << " bytes * " << e->allocations
+ << " allocations )\n";
+ oss << backtrace_string(e->backtrace, backtraceSize) << "\n";
+ }
+ oss << "\n";
+ free_malloc_leak_info(info);
+ return oss.str();
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android \ No newline at end of file
diff --git a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h
new file mode 100644
index 000000000000..444ed92cc9bb
--- /dev/null
+++ b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <iostream>
+
+namespace android {
+namespace os {
+namespace statsd {
+/*
+ * Dump the heap memory of the calling process, sorted by total size
+ * (allocation size * number of allocations).
+ *
+ * limit is the number of unique allocations to return.
+ */
+extern std::string dumpMemInfo(size_t limit);
+
+} // namespace statsd
+} // namespace os
+} // namespace android \ No newline at end of file
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index d47bd4fe9e44..a5ce38915574 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -133,7 +133,7 @@ std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() {
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
- mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+ mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
} else if (kv.has_value_int()) {
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
} else if (kv.has_value_bool()) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index b0a97b143400..a32e0cb8b274 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -163,6 +163,14 @@ std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() {
ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
continue;
}
+
+ // If there is no duration bucket info for this key, don't include it in the report.
+ // For example, duration started, but condition is never turned to true.
+ // TODO: Only add the key to the map when we add duration buckets info for it.
+ if (pair.second.size() == 0) {
+ continue;
+ }
+
long long wrapperToken =
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
@@ -172,7 +180,7 @@ std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() {
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
- mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+ mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
} else if (kv.has_value_int()) {
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
} else if (kv.has_value_bool()) {
@@ -203,7 +211,6 @@ std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() {
mProto->end(mProtoToken);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
(long long)mCurrentBucketStartTimeNs);
-
std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
startNewProtoOutputStream(endTime);
// TODO: Properly clear the old buckets.
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 42ac1a2bbd7b..d7b72965ae1e 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -135,7 +135,7 @@ std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() {
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
- mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+ mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
} else if (kv.has_value_int()) {
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
} else if (kv.has_value_bool()) {
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 9cbe6f6eac15..0dbdd2981307 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -153,7 +153,7 @@ std::unique_ptr<std::vector<uint8_t>> ValueMetricProducer::onDumpReport() {
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
- mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+ mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
} else if (kv.has_value_int()) {
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
} else if (kv.has_value_bool()) {
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 2344cb4170a3..466026350ddd 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -43,12 +43,12 @@ bool handleMetricWithLogTrackers(const string what, const int metricIndex,
int& logTrackerIndex) {
auto logTrackerIt = logTrackerMap.find(what);
if (logTrackerIt == logTrackerMap.end()) {
- ALOGW("cannot find the LogEntryMatcher %s in config", what.c_str());
+ ALOGW("cannot find the LogEntryMatcher \"%s\" in config", what.c_str());
return false;
}
if (usedForDimension && allLogEntryMatchers[logTrackerIt->second]->getTagIds().size() > 1) {
- ALOGE("LogEntryMatcher %s has more than one tag ids. When a metric has dimension, the "
- "\"what\" can only about one atom type.",
+ ALOGE("LogEntryMatcher \"%s\" has more than one tag ids. When a metric has dimension, "
+ "the \"what\" can only about one atom type.",
what.c_str());
return false;
}
@@ -67,14 +67,14 @@ bool handleMetricWithConditions(
unordered_map<int, std::vector<int>>& conditionToMetricMap) {
auto condition_it = conditionTrackerMap.find(condition);
if (condition_it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", condition.c_str());
+ ALOGW("cannot find Condition \"%s\" in the config", condition.c_str());
return false;
}
for (const auto& link : links) {
auto it = conditionTrackerMap.find(link.condition());
if (it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+ ALOGW("cannot find Condition \"%s\" in the config", link.condition().c_str());
return false;
}
allConditionTrackers[condition_it->second]->setSliced(true);
@@ -110,7 +110,7 @@ bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& log
new CombinationLogMatchingTracker(logMatcher.name(), index));
break;
default:
- ALOGE("Matcher %s malformed", logMatcher.name().c_str());
+ ALOGE("Matcher \"%s\" malformed", logMatcher.name().c_str());
return false;
// continue;
}
@@ -158,7 +158,7 @@ bool initConditions(const StatsdConfig& config, const unordered_map<string, int>
break;
}
default:
- ALOGE("Condition %s malformed", condition.name().c_str());
+ ALOGE("Condition \"%s\" malformed", condition.name().c_str());
return false;
}
if (conditionTrackerMap.find(condition.name()) != conditionTrackerMap.end()) {
@@ -204,7 +204,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l
for (int i = 0; i < config.count_metric_size(); i++) {
const CountMetric& metric = config.count_metric(i);
if (!metric.has_what()) {
- ALOGW("cannot find what in CountMetric %s", metric.name().c_str());
+ ALOGW("cannot find \"what\" in CountMetric \"%s\"", metric.name().c_str());
return false;
}
@@ -341,7 +341,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l
for (int i = 0; i < config.value_metric_size(); i++) {
const ValueMetric& metric = config.value_metric(i);
if (!metric.has_what()) {
- ALOGW("cannot find what in ValueMetric %s", metric.name().c_str());
+ ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str());
return false;
}
@@ -387,7 +387,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l
for (int i = 0; i < config.gauge_metric_size(); i++) {
const GaugeMetric& metric = config.gauge_metric(i);
if (!metric.has_what()) {
- ALOGW("cannot find what in ValueMetric %s", metric.name().c_str());
+ ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str());
return false;
}
@@ -438,7 +438,7 @@ bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& me
const Alert& alert = config.alert(i);
const auto& itr = metricProducerMap.find(alert.metric_name());
if (itr == metricProducerMap.end()) {
- ALOGW("alert has unknown metric name: %s %s", alert.name().c_str(),
+ ALOGW("alert \"%s\" has unknown metric name: \"%s\"", alert.name().c_str(),
alert.metric_name().c_str());
return false;
}
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 4f5df5512847..3fbcfeeee936 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -125,7 +125,7 @@ message UidMapping {
}
message StatsLogReport {
- optional int32 metric_id = 1;
+ optional string metric_name = 1;
optional int64 start_report_nanos = 2;
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index e75a37f98966..c8fa155de569 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -88,7 +88,7 @@ message SimpleCondition {
UNKNOWN = 0;
FALSE = 1;
}
- optional InitialValue initial_value = 5 [default = UNKNOWN];
+ optional InitialValue initial_value = 5 [default = FALSE];
repeated KeyMatcher dimension = 6;
}
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 80a0068690f7..11fb011fd74e 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -70,6 +70,7 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) {
simpleCondition.set_start("SCREEN_TURNED_ON");
simpleCondition.set_stop("SCREEN_TURNED_OFF");
simpleCondition.set_count_nesting(false);
+ simpleCondition.set_initial_value(SimpleCondition_InitialValue_UNKNOWN);
unordered_map<string, int> trackerNameIndexMap;
trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
diff --git a/cmds/statsd/tools/Android.mk b/cmds/statsd/tools/Android.mk
new file mode 100644
index 000000000000..faf2d2c7d4e7
--- /dev/null
+++ b/cmds/statsd/tools/Android.mk
@@ -0,0 +1,20 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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)
+include $(CLEAR_VARS)
+# Include the sub-makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH)) \ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk
new file mode 100644
index 000000000000..1bd5f30b2f5c
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SRC_FILES += ../../src/stats_log.proto \
+ ../../src/atoms_copy.proto
+
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../../src/
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := StatsdDogfood
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_DEX_PREOPT := false
+include $(BUILD_PACKAGE) \ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml
new file mode 100644
index 000000000000..cd76c9d379b5
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT 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.statsd.dogfood"
+ android:sharedUserId="android.uid.system"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.DUMP" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:launchMode="singleTop" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000000..55621cc1074f
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000000..11ec2068be19
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000000..7c02b784aa5d
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000000..915d91441349
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
new file mode 100644
index 000000000000..39978971d6b4
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/push_config"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/holo_green_light"
+ android:text="@string/push_config"/>
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/app_a_wake_lock_acquire1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_a_get_wl1"/>
+ <Button android:id="@+id/app_a_wake_lock_release1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_a_release_wl1"/>
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/app_a_wake_lock_acquire2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_a_get_wl2"/>
+ <Button android:id="@+id/app_a_wake_lock_release2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_a_release_wl2"/>
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/app_b_wake_lock_acquire1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_b_get_wl1"/>
+ <Button android:id="@+id/app_b_wake_lock_release1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_b_release_wl1"/>
+ </LinearLayout>
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/app_b_wake_lock_acquire2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_b_get_wl2"/>
+ <Button android:id="@+id/app_b_wake_lock_release2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_b_release_wl2"/>
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/plug"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/plug"/>
+
+ <Button android:id="@+id/unplug"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/unplug"/>
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/screen_on"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/screen_on"/>
+
+ <Button android:id="@+id/screen_off"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/screen_off"/>
+ </LinearLayout>
+
+ <Button android:id="@+id/dump"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/holo_purple"
+ android:text="@string/dump"/>
+
+ <TextView
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/report_header"/>
+
+ <TextView
+ android:id="@+id/report_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+</ScrollView> \ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
new file mode 100644
index 000000000000..d5b8fedfb9ec
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/values/strings.xml b/cmds/statsd/tools/dogfood/res/values/strings.xml
new file mode 100644
index 000000000000..7690df616ca5
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/values/strings.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+
+ <string name="app_name">Statsd Dogfood</string>
+
+ <string name="statsd_running">Statsd Running</string>
+ <string name="statsd_not_running">Statsd NOT Running</string>
+
+ <string name="push_config">Push baseline config</string>
+
+ <string name="app_a_foreground">App A foreground</string>
+ <string name="app_b_foreground">App B foreground</string>
+
+
+ <string name="app_a_get_wl1">App A get wl_1</string>
+ <string name="app_a_release_wl1">App A release wl_1</string>
+
+ <string name="app_a_get_wl2">App A get wl_2</string>
+ <string name="app_a_release_wl2">App A release wl_2</string>
+
+ <string name="app_b_get_wl1">App B get wl_1</string>
+ <string name="app_b_release_wl1">App B release wl_1</string>
+
+ <string name="app_b_get_wl2">App B get wl_2</string>
+ <string name="app_b_release_wl2">App B release wl_2</string>
+
+ <string name="plug">Plug</string>
+ <string name="unplug">Unplug</string>
+
+ <string name="screen_on">Screen On</string>
+ <string name="screen_off">Screen Off</string>
+
+ <string name="dump">DumpReport</string>
+ <string name="report_header">Report details</string>
+</resources>
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
new file mode 100644
index 000000000000..f6dea42d448d
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.statsd.dogfood;
+
+import android.text.format.DateFormat;
+
+import com.android.os.StatsLog;
+
+import java.util.List;
+
+public class DisplayProtoUtils {
+ public static void displayLogReport(StringBuilder sb, StatsLog.ConfigMetricsReport report) {
+ sb.append("ConfigKey: ");
+ if (report.hasConfigKey()) {
+ com.android.os.StatsLog.ConfigMetricsReport.ConfigKey key = report.getConfigKey();
+ sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName())
+ .append("\n");
+ }
+
+ sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n");
+ for (StatsLog.StatsLogReport log : report.getMetricsList()) {
+ sb.append("\n\n");
+ sb.append("metric id: ").append(log.getMetricName()).append("\n");
+ sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n");
+ sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n");
+
+ switch (log.getDataCase()) {
+ case DURATION_METRICS:
+ sb.append("Duration metric data\n");
+ displayDurationMetricData(sb, log);
+ break;
+ case EVENT_METRICS:
+ sb.append("Event metric data\n");
+ displayEventMetricData(sb, log);
+ break;
+ case COUNT_METRICS:
+ sb.append("Count metric data\n");
+ displayCountMetricData(sb, log);
+ break;
+ case GAUGE_METRICS:
+ sb.append("Gauge metric data\n");
+ displayGaugeMetricData(sb, log);
+ break;
+ case VALUE_METRICS:
+ sb.append("Value metric data\n");
+ displayValueMetricData(sb, log);
+ break;
+ case DATA_NOT_SET:
+ sb.append("No metric data\n");
+ break;
+ }
+ }
+ }
+
+ public static String getDateStr(long nanoSec) {
+ return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString();
+ }
+
+ private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) {
+ for (com.android.os.StatsLog.KeyValuePair kv : pairs) {
+ sb.append(kv.getKey()).append(":");
+ if (kv.hasValueBool()) {
+ sb.append(kv.getValueBool());
+ } else if (kv.hasValueFloat()) {
+ sb.append(kv.getValueFloat());
+ } else if (kv.hasValueInt()) {
+ sb.append(kv.getValueInt());
+ } else if (kv.hasValueStr()) {
+ sb.append(kv.getValueStr());
+ }
+ sb.append(" ");
+ }
+ }
+
+ public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+ StatsLog.StatsLogReport.DurationMetricDataWrapper durationMetricDataWrapper
+ = log.getDurationMetrics();
+ sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n");
+ for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) {
+ sb.append("dimension: ");
+ displayDimension(sb, duration.getDimensionList());
+ sb.append("\n");
+
+ for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) {
+ sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-")
+ .append(getDateStr(info.getEndBucketNanos())).append("] -> ")
+ .append(info.getDurationNanos()).append(" ns\n");
+ }
+ }
+ }
+
+ public static void displayEventMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+ sb.append("Contains ").append(log.getEventMetrics().getDataCount()).append(" events\n");
+ StatsLog.StatsLogReport.EventMetricDataWrapper eventMetricDataWrapper =
+ log.getEventMetrics();
+ for (StatsLog.EventMetricData event : eventMetricDataWrapper.getDataList()) {
+ sb.append(getDateStr(event.getTimestampNanos())).append(": ");
+ switch (event.getAtom().getPushedCase()) {
+ case SETTING_CHANGED:
+ sb.append("SETTING_CHANGED\n");
+ break;
+ case SYNC_STATE_CHANGED:
+ sb.append("SYNC_STATE_CHANGED\n");
+ break;
+ case AUDIO_STATE_CHANGED:
+ sb.append("AUDIO_STATE_CHANGED\n");
+ break;
+ case CAMERA_STATE_CHANGED:
+ sb.append("CAMERA_STATE_CHANGED\n");
+ break;
+ case ISOLATED_UID_CHANGED:
+ sb.append("ISOLATED_UID_CHANGED\n");
+ break;
+ case SCREEN_STATE_CHANGED:
+ sb.append("SCREEN_STATE_CHANGED\n");
+ break;
+ case SENSOR_STATE_CHANGED:
+ sb.append("SENSOR_STATE_CHANGED\n");
+ break;
+ case BATTERY_LEVEL_CHANGED:
+ sb.append("BATTERY_LEVEL_CHANGED\n");
+ break;
+ case PLUGGED_STATE_CHANGED:
+ sb.append("PLUGGED_STATE_CHANGED\n");
+ break;
+ case WAKEUP_ALARM_OCCURRED:
+ sb.append("WAKEUP_ALARM_OCCURRED\n");
+ break;
+ case BLE_SCAN_STATE_CHANGED:
+ sb.append("BLE_SCAN_STATE_CHANGED\n");
+ break;
+ case CHARGING_STATE_CHANGED:
+ sb.append("CHARGING_STATE_CHANGED\n");
+ break;
+ case GPS_SCAN_STATE_CHANGED:
+ sb.append("GPS_SCAN_STATE_CHANGED\n");
+ break;
+ case KERNEL_WAKEUP_REPORTED:
+ sb.append("KERNEL_WAKEUP_REPORTED\n");
+ break;
+ case WAKELOCK_STATE_CHANGED:
+ sb.append("WAKELOCK_STATE_CHANGED\n");
+ break;
+ case WIFI_LOCK_STATE_CHANGED:
+ sb.append("WIFI_LOCK_STATE_CHANGED\n");
+ break;
+ case WIFI_SCAN_STATE_CHANGED:
+ sb.append("WIFI_SCAN_STATE_CHANGED\n");
+ break;
+ case BLE_SCAN_RESULT_RECEIVED:
+ sb.append("BLE_SCAN_RESULT_RECEIVED\n");
+ break;
+ case DEVICE_ON_STATUS_CHANGED:
+ sb.append("DEVICE_ON_STATUS_CHANGED\n");
+ break;
+ case FLASHLIGHT_STATE_CHANGED:
+ sb.append("FLASHLIGHT_STATE_CHANGED\n");
+ break;
+ case SCREEN_BRIGHTNESS_CHANGED:
+ sb.append("SCREEN_BRIGHTNESS_CHANGED\n");
+ break;
+ case UID_PROCESS_STATE_CHANGED:
+ sb.append("UID_PROCESS_STATE_CHANGED\n");
+ break;
+ case UID_WAKELOCK_STATE_CHANGED:
+ sb.append("UID_WAKELOCK_STATE_CHANGED\n");
+ break;
+ case DEVICE_TEMPERATURE_REPORTED:
+ sb.append("DEVICE_TEMPERATURE_REPORTED\n");
+ break;
+ case SCHEDULED_JOB_STATE_CHANGED:
+ sb.append("SCHEDULED_JOB_STATE_CHANGED\n");
+ break;
+ case MEDIA_CODEC_ACTIVITY_CHANGED:
+ sb.append("MEDIA_CODEC_ACTIVITY_CHANGED\n");
+ break;
+ case WIFI_SIGNAL_STRENGTH_CHANGED:
+ sb.append("WIFI_SIGNAL_STRENGTH_CHANGED\n");
+ break;
+ case PHONE_SIGNAL_STRENGTH_CHANGED:
+ sb.append("PHONE_SIGNAL_STRENGTH_CHANGED\n");
+ break;
+ case DEVICE_IDLE_MODE_STATE_CHANGED:
+ sb.append("DEVICE_IDLE_MODE_STATE_CHANGED\n");
+ break;
+ case BATTERY_SAVER_MODE_STATE_CHANGED:
+ sb.append("BATTERY_SAVER_MODE_STATE_CHANGED\n");
+ break;
+ case PROCESS_LIFE_CYCLE_STATE_CHANGED:
+ sb.append("PROCESS_LIFE_CYCLE_STATE_CHANGED\n");
+ break;
+ case ACTIVITY_FOREGROUND_STATE_CHANGED:
+ sb.append("ACTIVITY_FOREGROUND_STATE_CHANGED\n");
+ break;
+ case BLE_UNOPTIMIZED_SCAN_STATE_CHANGED:
+ sb.append("BLE_UNOPTIMIZED_SCAN_STATE_CHANGED\n");
+ break;
+ case LONG_PARTIAL_WAKELOCK_STATE_CHANGED:
+ sb.append("LONG_PARTIAL_WAKELOCK_STATE_CHANGED\n");
+ break;
+ case PUSHED_NOT_SET:
+ sb.append("PUSHED_NOT_SET\n");
+ break;
+ }
+ }
+ }
+
+ public static void displayCountMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+ StatsLog.StatsLogReport.CountMetricDataWrapper countMetricDataWrapper
+ = log.getCountMetrics();
+ sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n");
+ for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) {
+ sb.append("dimension: ");
+ displayDimension(sb, count.getDimensionList());
+ sb.append("\n");
+
+ for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) {
+ sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-")
+ .append(getDateStr(info.getEndBucketNanos())).append("] -> ")
+ .append(info.getCount()).append("\n");
+ }
+ }
+ }
+
+ public static void displayGaugeMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+ sb.append("Display me!");
+ }
+
+ public static void displayValueMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+ sb.append("Display me!");
+ }
+}
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
new file mode 100644
index 000000000000..5e3160e11849
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.statsd.dogfood;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.StatsLog;
+import android.util.StatsManager;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.os.IStatsManager;
+import android.os.ServiceManager;
+
+import java.io.InputStream;
+
+import static com.android.statsd.dogfood.DisplayProtoUtils.displayLogReport;
+
+public class MainActivity extends Activity {
+ private final static String TAG = "StatsdDogfood";
+
+ final int[] mUids = {11111111, 2222222};
+ StatsManager mStatsManager;
+ TextView mReportText;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_main);
+
+ findViewById(R.id.app_a_wake_lock_acquire1).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(0, "wl_1");
+ }
+ });
+
+ findViewById(R.id.app_b_wake_lock_acquire1).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(1, "wl_1");
+ }
+ });
+
+ findViewById(R.id.app_a_wake_lock_acquire2).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(0, "wl_2");
+ }
+ });
+
+ findViewById(R.id.app_b_wake_lock_acquire2).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(1, "wl_2");
+ }
+ });
+
+ findViewById(R.id.app_a_wake_lock_release1).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(0, "wl_1");
+ }
+ });
+
+
+ findViewById(R.id.app_b_wake_lock_release1).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(1, "wl_1");
+ }
+ });
+
+ findViewById(R.id.app_a_wake_lock_release2).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(0, "wl_2");
+ }
+ });
+
+
+ findViewById(R.id.app_b_wake_lock_release2).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(1, "wl_2");
+ }
+ });
+
+
+ findViewById(R.id.plug).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 1);
+ }
+ });
+
+ findViewById(R.id.unplug).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 0);
+ }
+ });
+
+ findViewById(R.id.screen_on).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 2);
+ }
+ });
+
+ findViewById(R.id.screen_off).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 1);
+ }
+ });
+
+ mReportText = (TextView) findViewById(R.id.report_text);
+
+ findViewById(R.id.dump).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!statsdRunning()) {
+ return;
+ }
+ if (mStatsManager != null) {
+ byte[] data = mStatsManager.getData("fake");
+ if (data != null) {
+ displayData(data);
+ } else {
+ mReportText.setText("Failed!");
+ }
+ }
+ }
+ });
+
+ findViewById(R.id.push_config).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ try {
+ if (!statsdRunning()) {
+ return;
+ }
+ Resources res = getResources();
+ InputStream inputStream = res.openRawResource(R.raw.statsd_baseline_config);
+
+ byte[] config = new byte[inputStream.available()];
+ inputStream.read(config);
+ if (mStatsManager != null) {
+ if (mStatsManager.addConfiguration("fake",
+ config, getPackageName(), MainActivity.this.getClass().getName())) {
+ Toast.makeText(
+ MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(MainActivity.this, "Config push FAILED!",
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ } catch (Exception e) {
+ Toast.makeText(MainActivity.this, "failed to read config", Toast.LENGTH_LONG);
+ }
+ }
+ });
+ mStatsManager = (StatsManager) getSystemService("stats");
+ }
+
+ private boolean statsdRunning() {
+ if (IStatsManager.Stub.asInterface(ServiceManager.getService("stats")) == null) {
+ Log.d(TAG, "Statsd not running");
+ Toast.makeText(MainActivity.this, "Statsd NOT running!", Toast.LENGTH_LONG).show();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ Log.d(TAG, "new intent: " + intent.getIntExtra("pkg", 0));
+ int pkg = intent.getIntExtra("pkg", 0);
+ String name = intent.getStringExtra("name");
+ if (intent.hasExtra("acquire")) {
+ onWakeLockAcquire(pkg, name);
+ } else if (intent.hasExtra("release")) {
+ onWakeLockRelease(pkg, name);
+ }
+ }
+
+ private void displayData(byte[] data) {
+ com.android.os.StatsLog.ConfigMetricsReport report = null;
+ boolean good = false;
+ if (data != null) {
+ try {
+ report = com.android.os.StatsLog.ConfigMetricsReport.parseFrom(data);
+ good = true;
+ } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+ // display it in the text view.
+ }
+ }
+ int size = data == null ? 0 : data.length;
+ StringBuilder sb = new StringBuilder();
+ sb.append(good ? "Proto parsing OK!" : "Proto parsing Error!");
+ sb.append(" size:").append(size).append("\n");
+
+ if (good && report != null) {
+ displayLogReport(sb, report);
+ mReportText.setText(sb.toString());
+ }
+ }
+
+
+ private void onWakeLockAcquire(int id, String name) {
+ if (id > 1) {
+ Log.d(TAG, "invalid pkg id");
+ return;
+ }
+ StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, mUids[id], 0, name, 1);
+ StringBuilder sb = new StringBuilder();
+ sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
+ .append(", ").append(name).append(", 1);");
+ Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
+ }
+
+ private void onWakeLockRelease(int id, String name) {
+ if (id > 1) {
+ Log.d(TAG, "invalid pkg id");
+ return;
+ }
+ StatsLog.write(10, mUids[id], 0, name, 0);
+ StringBuilder sb = new StringBuilder();
+ sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
+ .append(", ").append(name).append(", 0);");
+ Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
+ }
+}
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 1503445a1b02..d8c79293f9ba 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -3,7 +3,26 @@ filegroup {
srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
}
-filegroup {
- name: "IKeystoreService.aidl",
+// only used by key_store_service
+cc_library_static {
+ name: "libkeystore_aidl",
srcs: ["android/security/IKeystoreService.aidl"],
+ aidl: {
+ export_aidl_headers: true,
+ include_dirs: ["frameworks/base/core/java/"],
+ },
+ header_libs: [
+ "libkeystore_headers",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libcutils",
+ "libhardware",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "liblog",
+ "libselinux",
+ "libutils",
+ ],
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index a558d6850af1..8824643db447 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -358,6 +358,11 @@ public abstract class AccessibilityService extends Service {
*/
public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7;
+ /**
+ * Action to lock the screen
+ */
+ public static final int GLOBAL_ACTION_LOCK_SCREEN = 8;
+
private static final String LOG_TAG = "AccessibilityService";
/**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 064e97828f06..02b7f8c59ea6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -519,11 +519,15 @@ public class ActivityManager {
* process that contains activities. */
public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 16;
+ /** @hide Process is being cached for later use and has an activity that corresponds
+ * to an existing recent task. */
+ public static final int PROCESS_STATE_CACHED_RECENT = 17;
+
/** @hide Process is being cached for later use and is empty. */
- public static final int PROCESS_STATE_CACHED_EMPTY = 17;
+ public static final int PROCESS_STATE_CACHED_EMPTY = 18;
/** @hide Process does not exist. */
- public static final int PROCESS_STATE_NONEXISTENT = 18;
+ public static final int PROCESS_STATE_NONEXISTENT = 19;
// NOTE: If PROCESS_STATEs are added or changed, then new fields must be added
// to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9e926bd63a6a..d1aacad30a64 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -72,6 +72,7 @@ interface INotificationManager
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
int getDeletedChannelCount(String pkg, int uid);
void deleteNotificationChannelGroup(String pkg, String channelGroupId);
+ NotificationChannelGroup getNotificationChannelGroup(String pkg, String channelGroupId);
ParceledListSlice getNotificationChannelGroups(String pkg);
boolean onlyHasDefaultChannel(String pkg, int uid);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f931589b50a2..659cf169e994 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -93,6 +93,58 @@ public class NotificationManager {
private static boolean localLOGV = false;
/**
+ * Intent that is broadcast when a {@link NotificationChannel} is blocked
+ * (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked
+ * (when {@link NotificationChannel#getImportance()} is anything other than
+ * {@link #IMPORTANCE_NONE}).
+ *
+ * This broadcast is only sent to the app that owns the channel that has changed.
+ *
+ * Input: nothing
+ * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED =
+ "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+
+ /**
+ * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the id of the
+ * object which has a new blocked state.
+ *
+ * The value will be the {@link NotificationChannel#getId()} of the channel for
+ * {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} and
+ * the {@link NotificationChannelGroup#getId()} of the group for
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED}.
+ */
+ public static final String EXTRA_BLOCK_STATE_CHANGED_ID =
+ "android.app.extra.BLOCK_STATE_CHANGED_ID";
+
+ /**
+ * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the new blocked
+ * state as a boolean.
+ *
+ * The value will be {@code true} if this channel or group is now blocked and {@code false} if
+ * this channel or group is now unblocked.
+ */
+ public static final String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+
+
+ /**
+ * Intent that is broadcast when a {@link NotificationChannelGroup} is
+ * {@link NotificationChannelGroup#isBlocked() blocked} or unblocked.
+ *
+ * This broadcast is only sent to the app that owns the channel group that has changed.
+ *
+ * Input: nothing
+ * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED =
+ "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
+
+ /**
* Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes.
* This broadcast is only sent to registered receivers.
*
@@ -504,6 +556,20 @@ public class NotificationManager {
}
/**
+ * Returns the notification channel group settings for a given channel group id.
+ *
+ * The channel group must belong to your package, or null will be returned.
+ */
+ public NotificationChannelGroup getNotificationChannelGroup(String channelGroupId) {
+ INotificationManager service = getService();
+ try {
+ return service.getNotificationChannelGroup(mContext.getPackageName(), channelGroupId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns all notification channel groups belonging to the calling app.
*/
public List<NotificationChannelGroup> getNotificationChannelGroups() {
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 530d84b420a3..7c40b4eaf375 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -244,6 +244,13 @@ public class JobInfo implements Parcelable {
public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0;
/**
+ * Allows this job to run despite doze restrictions as long as the app is in the foreground
+ * or on the temporary whitelist
+ * @hide
+ */
+ public static final int FLAG_IMPORTANT_WHILE_FOREGROUND = 1 << 1;
+
+ /**
* @hide
*/
public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
@@ -1333,6 +1340,30 @@ public class JobInfo implements Parcelable {
}
/**
+ * Setting this to true indicates that this job is important while the scheduling app
+ * is in the foreground or on the temporary whitelist for background restrictions.
+ * This means that the system will relax doze restrictions on this job during this time.
+ *
+ * Apps should use this flag only for short jobs that are essential for the app to function
+ * properly in the foreground.
+ *
+ * Note that once the scheduling app is no longer whitelisted from background restrictions
+ * and in the background, or the job failed due to unsatisfied constraints,
+ * this job should be expected to behave like other jobs without this flag.
+ *
+ * @param importantWhileForeground whether to relax doze restrictions for this job when the
+ * app is in the foreground. False by default.
+ */
+ public Builder setImportantWhileForeground(boolean importantWhileForeground) {
+ if (importantWhileForeground) {
+ mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND;
+ } else {
+ mFlags &= (~FLAG_IMPORTANT_WHILE_FOREGROUND);
+ }
+ return this;
+ }
+
+ /**
* Set whether or not to persist this job across device reboots.
*
* @param isPersisted True to indicate that the job will be written to
@@ -1395,6 +1426,10 @@ public class JobInfo implements Parcelable {
"persisted job");
}
}
+ if ((mFlags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && mHasEarlyConstraint) {
+ throw new IllegalArgumentException("An important while foreground job cannot "
+ + "have a time delay");
+ }
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
throw new IllegalArgumentException("An idle mode job will not respect any" +
" back-off policy, so calling setBackoffCriteria with" +
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index 58fb260814e5..c5f227215b17 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -33,7 +33,6 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.widget.RemoteViews;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
@@ -41,6 +40,7 @@ import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
/**
* A slice is a piece of app content and actions that can be surfaced outside of the app.
@@ -54,7 +54,7 @@ public final class Slice implements Parcelable {
* @hide
*/
@StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
- HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL})
+ HINT_NO_TINT, HINT_PARTIAL})
public @interface SliceHint{ }
/**
@@ -62,7 +62,7 @@ public final class Slice implements Parcelable {
* the content should be used in the shortcut representation of the slice (icon, label, action),
* normally this should be indicated by adding the hint on the action containing that content.
*
- * @see SliceItem#TYPE_ACTION
+ * @see SliceItem#FORMAT_ACTION
*/
public static final String HINT_TITLE = "title";
/**
@@ -90,20 +90,6 @@ public final class Slice implements Parcelable {
*/
public static final String HINT_SELECTED = "selected";
/**
- * Hint to indicate that this is a message as part of a communication
- * sequence in this slice.
- */
- public static final String HINT_MESSAGE = "message";
- /**
- * Hint to tag the source (i.e. sender) of a {@link #HINT_MESSAGE}.
- */
- public static final String HINT_SOURCE = "source";
- /**
- * Hint that list items within this slice or subslice would appear better
- * if organized horizontally.
- */
- public static final String HINT_HORIZONTAL = "horizontal";
- /**
* Hint to indicate that this content should not be tinted.
*/
public static final String HINT_NO_TINT = "no_tint";
@@ -123,23 +109,31 @@ public final class Slice implements Parcelable {
*/
public static final String HINT_TOGGLE = "toggle";
/**
+ * Hint that list items within this slice or subslice would appear better
+ * if organized horizontally.
+ */
+ public static final String HINT_HORIZONTAL = "horizontal";
+ /**
* Hint to indicate that this slice is incomplete and an update will be sent once
* loading is complete. Slices which contain HINT_PARTIAL will not be cached by the
* OS and should not be cached by apps.
*/
public static final String HINT_PARTIAL = "partial";
- // These two are coming over from prototyping, but we probably don't want in
- // public API, at least not right now.
- /**
- * @hide
- */
- public static final String HINT_ALT = "alt";
/**
* Key to retrieve an extra added to an intent when a control is changed.
* @hide
*/
public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
+ /**
+ * Subtype to indicate that this is a message as part of a communication
+ * sequence in this slice.
+ */
+ public static final String SUBTYPE_MESSAGE = "message";
+ /**
+ * Subtype to tag the source (i.e. sender) of a {@link #SUBTYPE_MESSAGE}.
+ */
+ public static final String SUBTYPE_SOURCE = "source";
private final SliceItem[] mItems;
private final @SliceHint String[] mHints;
@@ -270,10 +264,17 @@ public final class Slice implements Parcelable {
* Add a sub-slice to the slice being constructed
*/
public Builder addSubSlice(@NonNull Slice slice) {
- List<String> hints = slice.getHints();
- slice.mSpec = null;
- mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, hints.toArray(
- new String[hints.size()])));
+ return addSubSlice(slice, null);
+ }
+
+ /**
+ * Add a sub-slice to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Builder addSubSlice(@NonNull Slice slice, @Nullable String subType) {
+ mItems.add(new SliceItem(slice, SliceItem.FORMAT_SLICE, subType,
+ slice.getHints().toArray(new String[slice.getHints().size()])));
return this;
}
@@ -281,95 +282,125 @@ public final class Slice implements Parcelable {
* Add an action to the slice being constructed
*/
public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) {
+ return addAction(action, s, null);
+ }
+
+ /**
+ * Add an action to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s,
+ @Nullable String subType) {
List<String> hints = s.getHints();
s.mSpec = null;
- mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, hints.toArray(
+ mItems.add(new SliceItem(action, s, SliceItem.FORMAT_ACTION, subType, hints.toArray(
new String[hints.size()])));
return this;
}
/**
* Add text to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addText(CharSequence text, @SliceHint String... hints) {
- mItems.add(new SliceItem(text, SliceItem.TYPE_TEXT, hints));
+ public Builder addText(CharSequence text, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(text, SliceItem.FORMAT_TEXT, subType, hints));
return this;
}
/**
* Add text to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addText(CharSequence text, @SliceHint List<String> hints) {
- return addText(text, hints.toArray(new String[hints.size()]));
+ public Builder addText(CharSequence text, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addText(text, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add an image to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addIcon(Icon icon, @SliceHint String... hints) {
- mItems.add(new SliceItem(icon, SliceItem.TYPE_IMAGE, hints));
+ public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint String... hints) {
+ mItems.add(new SliceItem(icon, SliceItem.FORMAT_IMAGE, subType, hints));
return this;
}
/**
* Add an image to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addIcon(Icon icon, @SliceHint List<String> hints) {
- return addIcon(icon, hints.toArray(new String[hints.size()]));
- }
-
- /**
- * @hide This isn't final
- */
- public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) {
- mItems.add(new SliceItem(remoteView, SliceItem.TYPE_REMOTE_VIEW, hints));
- return this;
+ public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint List<String> hints) {
+ return addIcon(icon, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add remote input to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addRemoteInput(RemoteInput remoteInput,
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
@SliceHint List<String> hints) {
- return addRemoteInput(remoteInput, hints.toArray(new String[hints.size()]));
+ return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add remote input to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) {
- mItems.add(new SliceItem(remoteInput, SliceItem.TYPE_REMOTE_INPUT, hints));
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(remoteInput, SliceItem.FORMAT_REMOTE_INPUT,
+ subType, hints));
return this;
}
/**
* Add a color to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addColor(int color, @SliceHint String... hints) {
- mItems.add(new SliceItem(color, SliceItem.TYPE_COLOR, hints));
+ public Builder addColor(int color, @Nullable String subType, @SliceHint String... hints) {
+ mItems.add(new SliceItem(color, SliceItem.FORMAT_COLOR, subType, hints));
return this;
}
/**
* Add a color to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addColor(int color, @SliceHint List<String> hints) {
- return addColor(color, hints.toArray(new String[hints.size()]));
+ public Builder addColor(int color, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addColor(color, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add a timestamp to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addTimestamp(long time, @SliceHint String... hints) {
- mItems.add(new SliceItem(time, SliceItem.TYPE_TIMESTAMP, hints));
+ public Slice.Builder addTimestamp(long time, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(time, SliceItem.FORMAT_TIMESTAMP, subType,
+ hints));
return this;
}
/**
* Add a timestamp to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addTimestamp(long time, @SliceHint List<String> hints) {
- return addTimestamp(time, hints.toArray(new String[hints.size()]));
+ public Slice.Builder addTimestamp(long time, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addTimestamp(time, subType, hints.toArray(new String[hints.size()]));
}
/**
@@ -404,15 +435,15 @@ public final class Slice implements Parcelable {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mItems.length; i++) {
sb.append(indent);
- if (mItems[i].getType() == SliceItem.TYPE_SLICE) {
+ if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_SLICE)) {
sb.append("slice:\n");
sb.append(mItems[i].getSlice().toString(indent + " "));
- } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) {
+ } else if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_TEXT)) {
sb.append("text: ");
sb.append(mItems[i].getText());
sb.append("\n");
} else {
- sb.append(SliceItem.typeToString(mItems[i].getType()));
+ sb.append(mItems[i].getFormat());
sb.append("\n");
}
}
diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java
index 6e69b0511207..8d81199185d2 100644
--- a/core/java/android/app/slice/SliceItem.java
+++ b/core/java/android/app/slice/SliceItem.java
@@ -16,8 +16,8 @@
package android.app.slice;
-import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.StringDef;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.graphics.drawable.Icon;
@@ -38,13 +38,13 @@ import java.util.List;
*
* A SliceItem a piece of content and some hints about what that content
* means or how it should be displayed. The types of content can be:
- * <li>{@link #TYPE_SLICE}</li>
- * <li>{@link #TYPE_TEXT}</li>
- * <li>{@link #TYPE_IMAGE}</li>
- * <li>{@link #TYPE_ACTION}</li>
- * <li>{@link #TYPE_COLOR}</li>
- * <li>{@link #TYPE_TIMESTAMP}</li>
- * <li>{@link #TYPE_REMOTE_INPUT}</li>
+ * <li>{@link #FORMAT_SLICE}</li>
+ * <li>{@link #FORMAT_TEXT}</li>
+ * <li>{@link #FORMAT_IMAGE}</li>
+ * <li>{@link #FORMAT_ACTION}</li>
+ * <li>{@link #FORMAT_COLOR}</li>
+ * <li>{@link #FORMAT_TIMESTAMP}</li>
+ * <li>{@link #FORMAT_REMOTE_INPUT}</li>
*
* The hints that a {@link SliceItem} are a set of strings which annotate
* the content. The hints that are guaranteed to be understood by the system
@@ -55,68 +55,68 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- @IntDef({TYPE_SLICE, TYPE_TEXT, TYPE_IMAGE, TYPE_ACTION, TYPE_COLOR,
- TYPE_TIMESTAMP, TYPE_REMOTE_INPUT})
+ @StringDef({FORMAT_SLICE, FORMAT_TEXT, FORMAT_IMAGE, FORMAT_ACTION, FORMAT_COLOR,
+ FORMAT_TIMESTAMP, FORMAT_REMOTE_INPUT})
public @interface SliceType {}
/**
* A {@link SliceItem} that contains a {@link Slice}
*/
- public static final int TYPE_SLICE = 1;
+ public static final String FORMAT_SLICE = "slice";
/**
* A {@link SliceItem} that contains a {@link CharSequence}
*/
- public static final int TYPE_TEXT = 2;
+ public static final String FORMAT_TEXT = "text";
/**
* A {@link SliceItem} that contains an {@link Icon}
*/
- public static final int TYPE_IMAGE = 3;
+ public static final String FORMAT_IMAGE = "image";
/**
* A {@link SliceItem} that contains a {@link PendingIntent}
*
* Note: Actions contain 2 pieces of data, In addition to the pending intent, the
* item contains a {@link Slice} that the action applies to.
*/
- public static final int TYPE_ACTION = 4;
- /**
- * @hide This isn't final
- */
- public static final int TYPE_REMOTE_VIEW = 5;
+ public static final String FORMAT_ACTION = "action";
/**
* A {@link SliceItem} that contains a Color int.
*/
- public static final int TYPE_COLOR = 6;
+ public static final String FORMAT_COLOR = "color";
/**
* A {@link SliceItem} that contains a timestamp.
*/
- public static final int TYPE_TIMESTAMP = 8;
+ public static final String FORMAT_TIMESTAMP = "timestamp";
/**
* A {@link SliceItem} that contains a {@link RemoteInput}.
*/
- public static final int TYPE_REMOTE_INPUT = 9;
+ public static final String FORMAT_REMOTE_INPUT = "input";
/**
* @hide
*/
protected @Slice.SliceHint
String[] mHints;
- private final int mType;
+ private final String mFormat;
+ private final String mSubType;
private final Object mObj;
/**
* @hide
*/
- public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) {
+ public SliceItem(Object obj, @SliceType String format, String subType,
+ @Slice.SliceHint String[] hints) {
mHints = hints;
- mType = type;
+ mFormat = format;
+ mSubType = subType;
mObj = obj;
}
/**
* @hide
*/
- public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) {
- this(new Pair<>(intent, slice), type, hints);
+ public SliceItem(PendingIntent intent, Slice slice, String format, String subType,
+ @Slice.SliceHint String[] hints) {
+ this(new Pair<>(intent, slice), format, subType, hints);
}
/**
@@ -141,26 +141,51 @@ public final class SliceItem implements Parcelable {
ArrayUtils.removeElement(String.class, mHints, hint);
}
- public @SliceType int getType() {
- return mType;
+ /**
+ * Get the format of this SliceItem.
+ * <p>
+ * The format will be one of the following types supported by the platform:
+ * <li>{@link #FORMAT_SLICE}</li>
+ * <li>{@link #FORMAT_TEXT}</li>
+ * <li>{@link #FORMAT_IMAGE}</li>
+ * <li>{@link #FORMAT_ACTION}</li>
+ * <li>{@link #FORMAT_COLOR}</li>
+ * <li>{@link #FORMAT_TIMESTAMP}</li>
+ * <li>{@link #FORMAT_REMOTE_INPUT}</li>
+ * @see #getSubType() ()
+ */
+ public String getFormat() {
+ return mFormat;
+ }
+
+ /**
+ * Get the sub-type of this SliceItem.
+ * <p>
+ * Subtypes provide additional information about the type of this information beyond basic
+ * interpretations inferred by {@link #getFormat()}. For example a slice may contain
+ * many {@link #FORMAT_TEXT} items, but only some of them may be {@link Slice#SUBTYPE_MESSAGE}.
+ * @see #getFormat()
+ */
+ public String getSubType() {
+ return mSubType;
}
/**
- * @return The text held by this {@link #TYPE_TEXT} SliceItem
+ * @return The text held by this {@link #FORMAT_TEXT} SliceItem
*/
public CharSequence getText() {
return (CharSequence) mObj;
}
/**
- * @return The icon held by this {@link #TYPE_IMAGE} SliceItem
+ * @return The icon held by this {@link #FORMAT_IMAGE} SliceItem
*/
public Icon getIcon() {
return (Icon) mObj;
}
/**
- * @return The pending intent held by this {@link #TYPE_ACTION} SliceItem
+ * @return The pending intent held by this {@link #FORMAT_ACTION} SliceItem
*/
public PendingIntent getAction() {
return ((Pair<PendingIntent, Slice>) mObj).first;
@@ -174,31 +199,31 @@ public final class SliceItem implements Parcelable {
}
/**
- * @return The remote input held by this {@link #TYPE_REMOTE_INPUT} SliceItem
+ * @return The remote input held by this {@link #FORMAT_REMOTE_INPUT} SliceItem
*/
public RemoteInput getRemoteInput() {
return (RemoteInput) mObj;
}
/**
- * @return The color held by this {@link #TYPE_COLOR} SliceItem
+ * @return The color held by this {@link #FORMAT_COLOR} SliceItem
*/
public int getColor() {
return (Integer) mObj;
}
/**
- * @return The slice held by this {@link #TYPE_ACTION} or {@link #TYPE_SLICE} SliceItem
+ * @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem
*/
public Slice getSlice() {
- if (getType() == TYPE_ACTION) {
+ if (getFormat() == FORMAT_ACTION) {
return ((Pair<PendingIntent, Slice>) mObj).second;
}
return (Slice) mObj;
}
/**
- * @return The timestamp held by this {@link #TYPE_TIMESTAMP} SliceItem
+ * @return The timestamp held by this {@link #FORMAT_TIMESTAMP} SliceItem
*/
public long getTimestamp() {
return (Long) mObj;
@@ -217,8 +242,9 @@ public final class SliceItem implements Parcelable {
*/
public SliceItem(Parcel in) {
mHints = in.readStringArray();
- mType = in.readInt();
- mObj = readObj(mType, in);
+ mFormat = in.readString();
+ mSubType = in.readString();
+ mObj = readObj(mFormat, in);
}
@Override
@@ -229,8 +255,9 @@ public final class SliceItem implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringArray(mHints);
- dest.writeInt(mType);
- writeObj(dest, flags, mObj, mType);
+ dest.writeString(mFormat);
+ dest.writeString(mSubType);
+ writeObj(dest, flags, mObj, mFormat);
}
/**
@@ -259,49 +286,54 @@ public final class SliceItem implements Parcelable {
return false;
}
- private void writeObj(Parcel dest, int flags, Object obj, int type) {
- switch (type) {
- case TYPE_SLICE:
- case TYPE_REMOTE_VIEW:
- case TYPE_IMAGE:
- case TYPE_REMOTE_INPUT:
+ private static String getBaseType(String type) {
+ int index = type.indexOf('/');
+ if (index >= 0) {
+ return type.substring(0, index);
+ }
+ return type;
+ }
+
+ private static void writeObj(Parcel dest, int flags, Object obj, String type) {
+ switch (getBaseType(type)) {
+ case FORMAT_SLICE:
+ case FORMAT_IMAGE:
+ case FORMAT_REMOTE_INPUT:
((Parcelable) obj).writeToParcel(dest, flags);
break;
- case TYPE_ACTION:
+ case FORMAT_ACTION:
((Pair<PendingIntent, Slice>) obj).first.writeToParcel(dest, flags);
((Pair<PendingIntent, Slice>) obj).second.writeToParcel(dest, flags);
break;
- case TYPE_TEXT:
- TextUtils.writeToParcel((CharSequence) mObj, dest, flags);
+ case FORMAT_TEXT:
+ TextUtils.writeToParcel((CharSequence) obj, dest, flags);
break;
- case TYPE_COLOR:
- dest.writeInt((Integer) mObj);
+ case FORMAT_COLOR:
+ dest.writeInt((Integer) obj);
break;
- case TYPE_TIMESTAMP:
- dest.writeLong((Long) mObj);
+ case FORMAT_TIMESTAMP:
+ dest.writeLong((Long) obj);
break;
}
}
- private static Object readObj(int type, Parcel in) {
- switch (type) {
- case TYPE_SLICE:
+ private static Object readObj(String type, Parcel in) {
+ switch (getBaseType(type)) {
+ case FORMAT_SLICE:
return Slice.CREATOR.createFromParcel(in);
- case TYPE_TEXT:
+ case FORMAT_TEXT:
return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- case TYPE_IMAGE:
+ case FORMAT_IMAGE:
return Icon.CREATOR.createFromParcel(in);
- case TYPE_ACTION:
- return new Pair<PendingIntent, Slice>(
+ case FORMAT_ACTION:
+ return new Pair<>(
PendingIntent.CREATOR.createFromParcel(in),
Slice.CREATOR.createFromParcel(in));
- case TYPE_REMOTE_VIEW:
- return RemoteViews.CREATOR.createFromParcel(in);
- case TYPE_COLOR:
+ case FORMAT_COLOR:
return in.readInt();
- case TYPE_TIMESTAMP:
+ case FORMAT_TIMESTAMP:
return in.readLong();
- case TYPE_REMOTE_INPUT:
+ case FORMAT_REMOTE_INPUT:
return RemoteInput.CREATOR.createFromParcel(in);
}
throw new RuntimeException("Unsupported type " + type);
@@ -318,29 +350,4 @@ public final class SliceItem implements Parcelable {
return new SliceItem[size];
}
};
-
- /**
- * @hide
- */
- public static String typeToString(int type) {
- switch (type) {
- case TYPE_SLICE:
- return "Slice";
- case TYPE_TEXT:
- return "Text";
- case TYPE_IMAGE:
- return "Image";
- case TYPE_ACTION:
- return "Action";
- case TYPE_REMOTE_VIEW:
- return "RemoteView";
- case TYPE_COLOR:
- return "Color";
- case TYPE_TIMESTAMP:
- return "Timestamp";
- case TYPE_REMOTE_INPUT:
- return "RemoteInput";
- }
- return "Unrecognized type: " + type;
- }
}
diff --git a/core/java/android/app/slice/SliceQuery.java b/core/java/android/app/slice/SliceQuery.java
index 9943c492a358..20eca880f63b 100644
--- a/core/java/android/app/slice/SliceQuery.java
+++ b/core/java/android/app/slice/SliceQuery.java
@@ -19,6 +19,7 @@ package android.app.slice;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.Queue;
import java.util.Spliterators;
import java.util.stream.Collectors;
@@ -37,14 +38,15 @@ public class SliceQuery {
*/
public static SliceItem getPrimaryIcon(Slice slice) {
for (SliceItem item : slice.getItems()) {
- if (item.getType() == SliceItem.TYPE_IMAGE) {
+ if (Objects.equals(item.getFormat(), SliceItem.FORMAT_IMAGE)) {
return item;
}
- if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+ if (!(compareTypes(item, SliceItem.FORMAT_SLICE)
+ && item.hasHint(Slice.HINT_LIST))
&& !item.hasHint(Slice.HINT_ACTIONS)
&& !item.hasHint(Slice.HINT_LIST_ITEM)
- && (item.getType() != SliceItem.TYPE_ACTION)) {
- SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
+ && !compareTypes(item, SliceItem.FORMAT_ACTION)) {
+ SliceItem icon = SliceQuery.find(item, SliceItem.FORMAT_IMAGE);
if (icon != null) {
return icon;
}
@@ -78,23 +80,23 @@ public class SliceQuery {
/**
* @hide
*/
- public static List<SliceItem> findAll(SliceItem s, int type) {
+ public static List<SliceItem> findAll(SliceItem s, String type) {
return findAll(s, type, (String[]) null, null);
}
/**
* @hide
*/
- public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) {
+ public static List<SliceItem> findAll(SliceItem s, String type, String hints, String nonHints) {
return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
}
/**
* @hide
*/
- public static List<SliceItem> findAll(SliceItem s, int type, String[] hints,
+ public static List<SliceItem> findAll(SliceItem s, String type, String[] hints,
String[] nonHints) {
- return stream(s).filter(item -> (type == -1 || item.getType() == type)
+ return stream(s).filter(item -> compareTypes(item, type)
&& (item.hasHints(hints) && !item.hasAnyHints(nonHints)))
.collect(Collectors.toList());
}
@@ -102,45 +104,45 @@ public class SliceQuery {
/**
* @hide
*/
- public static SliceItem find(Slice s, int type, String hints, String nonHints) {
+ public static SliceItem find(Slice s, String type, String hints, String nonHints) {
return find(s, type, new String[]{ hints }, new String[]{ nonHints });
}
/**
* @hide
*/
- public static SliceItem find(Slice s, int type) {
+ public static SliceItem find(Slice s, String type) {
return find(s, type, (String[]) null, null);
}
/**
* @hide
*/
- public static SliceItem find(SliceItem s, int type) {
+ public static SliceItem find(SliceItem s, String type) {
return find(s, type, (String[]) null, null);
}
/**
* @hide
*/
- public static SliceItem find(SliceItem s, int type, String hints, String nonHints) {
+ public static SliceItem find(SliceItem s, String type, String hints, String nonHints) {
return find(s, type, new String[]{ hints }, new String[]{ nonHints });
}
/**
* @hide
*/
- public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
+ public static SliceItem find(Slice s, String type, String[] hints, String[] nonHints) {
List<String> h = s.getHints();
- return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type,
- hints, nonHints);
+ return find(new SliceItem(s, SliceItem.FORMAT_SLICE, null, h.toArray(new String[h.size()])),
+ type, hints, nonHints);
}
/**
* @hide
*/
- public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) {
- return stream(s).filter(item -> (item.getType() == type || type == -1)
+ public static SliceItem find(SliceItem s, String type, String[] hints, String[] nonHints) {
+ return stream(s).filter(item -> compareTypes(item, type)
&& (item.hasHints(hints) && !item.hasAnyHints(nonHints))).findFirst().orElse(null);
}
@@ -159,8 +161,8 @@ public class SliceQuery {
@Override
public SliceItem next() {
SliceItem item = items.poll();
- if (item.getType() == SliceItem.TYPE_SLICE
- || item.getType() == SliceItem.TYPE_ACTION) {
+ if (compareTypes(item, SliceItem.FORMAT_SLICE)
+ || compareTypes(item, SliceItem.FORMAT_ACTION)) {
items.addAll(item.getSlice().getItems());
}
return item;
@@ -168,4 +170,19 @@ public class SliceQuery {
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
}
+
+ /**
+ * @hide
+ */
+ public static boolean compareTypes(SliceItem item, String desiredType) {
+ final int typeLength = desiredType.length();
+ if (typeLength == 3 && desiredType.equals("*/*")) {
+ return true;
+ }
+ if (item.getSubType() == null && desiredType.indexOf('/') < 0) {
+ return item.getFormat().equals(desiredType);
+ }
+ return (item.getFormat() + "/" + item.getSubType())
+ .matches(desiredType.replaceAll("\\*", ".*"));
+ }
}
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 33386e5ce58a..7f24c51d37a1 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -145,7 +145,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
}
/**
- * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
+ * Starts an asynchronous load of the data. When the result is ready the callbacks
* will be called on the UI thread. If a previous load has been completed and is still valid
* the result may be passed to the callbacks immediately.
*
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 20342807f75f..edb27cd4ecf1 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -19,6 +19,7 @@ package android.content.pm;
import static android.os.Build.VERSION_CODES.DONUT;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
@@ -890,6 +891,29 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public int versionCode;
/**
+ * The user-visible SDK version (ex. 26) of the framework against which the application claims
+ * to have been compiled, or {@code 0} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.SDK_INT}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ public int compileSdkVersion;
+
+ /**
+ * The development codename (ex. "O", "REL") of the framework against which the application
+ * claims to have been compiled, or {@code null} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ @Nullable
+ public String compileSdkVersionCodename;
+
+ /**
* When false, indicates that all components within this application are
* considered disabled, regardless of their individually set enabled status.
*/
@@ -1305,6 +1329,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(targetSandboxVersion);
dest.writeString(classLoaderName);
dest.writeStringArray(splitClassLoaderNames);
+ dest.writeInt(compileSdkVersion);
+ dest.writeString(compileSdkVersionCodename);
}
public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -1372,6 +1398,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
targetSandboxVersion = source.readInt();
classLoaderName = source.readString();
splitClassLoaderNames = source.readStringArray();
+ compileSdkVersion = source.readInt();
+ compileSdkVersionCodename = source.readString();
}
/**
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index ba488f6a0518..f8889b6853d6 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -289,6 +290,29 @@ public class PackageInfo implements Parcelable {
/** @hide */
public boolean isStaticOverlay;
+ /**
+ * The user-visible SDK version (ex. 26) of the framework against which the application claims
+ * to have been compiled, or {@code 0} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ public int compileSdkVersion;
+
+ /**
+ * The development codename (ex. "O", "REL") of the framework against which the application
+ * claims to have been compiled, or {@code null} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ @Nullable
+ public String compileSdkVersionCodename;
+
public PackageInfo() {
}
@@ -344,6 +368,8 @@ public class PackageInfo implements Parcelable {
dest.writeString(overlayTarget);
dest.writeInt(isStaticOverlay ? 1 : 0);
dest.writeInt(overlayPriority);
+ dest.writeInt(compileSdkVersion);
+ dest.writeString(compileSdkVersionCodename);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -396,6 +422,8 @@ public class PackageInfo implements Parcelable {
overlayTarget = source.readString();
isStaticOverlay = source.readInt() != 0;
overlayPriority = source.readInt();
+ compileSdkVersion = source.readInt();
+ compileSdkVersionCodename = source.readString();
// The component lists were flattened with the redundant ApplicationInfo
// instances omitted. Distribute the canonical one here as appropriate.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1c5cf15da062..d9ac6818975c 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -678,6 +678,8 @@ public class PackageParser {
pi.overlayTarget = p.mOverlayTarget;
pi.overlayPriority = p.mOverlayPriority;
pi.isStaticOverlay = p.mIsStaticOverlay;
+ pi.compileSdkVersion = p.mCompileSdkVersion;
+ pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
if ((flags&PackageManager.GET_GIDS) != 0) {
@@ -2076,6 +2078,16 @@ public class PackageParser {
pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
+ pkg.mCompileSdkVersion = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);
+ pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;
+ pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);
+ if (pkg.mCompileSdkVersionCodename != null) {
+ pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();
+ }
+ pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;
+
sa.recycle();
return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
@@ -5967,6 +5979,9 @@ public class PackageParser {
public boolean mIsStaticOverlay;
public boolean mTrustedOverlay;
+ public int mCompileSdkVersion;
+ public String mCompileSdkVersionCodename;
+
/**
* Data used to feed the KeySetManagerService
*/
@@ -6458,6 +6473,8 @@ public class PackageParser {
mOverlayPriority = dest.readInt();
mIsStaticOverlay = (dest.readInt() == 1);
mTrustedOverlay = (dest.readInt() == 1);
+ mCompileSdkVersion = dest.readInt();
+ mCompileSdkVersionCodename = dest.readString();
mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot);
mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
@@ -6581,6 +6598,8 @@ public class PackageParser {
dest.writeInt(mOverlayPriority);
dest.writeInt(mIsStaticOverlay ? 1 : 0);
dest.writeInt(mTrustedOverlay ? 1 : 0);
+ dest.writeInt(mCompileSdkVersion);
+ dest.writeString(mCompileSdkVersionCodename);
dest.writeArraySet(mSigningKeys);
dest.writeArraySet(mUpgradeKeySets);
writeKeySetMapping(dest, mKeySetMapping);
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 8b0fef4fe2bc..5adb11964c8c 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -570,6 +570,16 @@ public final class SQLiteConnectionPool implements Closeable {
mAvailableNonPrimaryConnections.clear();
}
+ /**
+ * Close non-primary connections that are not currently in use. This method is safe to use
+ * in finalize block as it doesn't throw RuntimeExceptions.
+ */
+ void closeAvailableNonPrimaryConnectionsAndLogExceptions() {
+ synchronized (mLock) {
+ closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
+ }
+ }
+
// Can't throw.
private void closeExcessConnectionsAndLogExceptionsLocked() {
int availableCount = mAvailableNonPrimaryConnections.size();
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 863fb1986e06..09bb9c69dc09 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1740,7 +1740,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
private int executeSql(String sql, Object[] bindArgs) throws SQLException {
acquireReference();
try {
- if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
+ final int statementType = DatabaseUtils.getSqlStatementType(sql);
+ if (statementType == DatabaseUtils.STATEMENT_ATTACH) {
boolean disableWal = false;
synchronized (mLock) {
if (!mHasAttachedDbsLocked) {
@@ -1754,11 +1755,14 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
}
- SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
- try {
+ try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) {
return statement.executeUpdateDelete();
} finally {
- statement.close();
+ // If schema was updated, close non-primary connections, otherwise they might
+ // have outdated schema information
+ if (statementType == DatabaseUtils.STATEMENT_DDL) {
+ mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions();
+ }
}
} finally {
releaseReference();
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index e845359a35c4..cd551bd42e0f 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -174,6 +174,11 @@ public abstract class DisplayManagerInternal {
public abstract boolean isUidPresentOnDisplay(int uid, int displayId);
/**
+ * Persist brightness slider events.
+ */
+ public abstract void persistBrightnessSliderEvents();
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 025d46d12567..151e62de7b70 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -34,7 +34,7 @@ interface IUsbManager
/* Returns a file descriptor for communicating with the USB device.
* The native fd can be passed to usb_device_new() in libusbhost.
*/
- ParcelFileDescriptor openDevice(String deviceName);
+ ParcelFileDescriptor openDevice(String deviceName, String packageName);
/* Returns the currently attached USB accessory */
UsbAccessory getCurrentAccessory();
@@ -55,7 +55,7 @@ interface IUsbManager
void setAccessoryPackage(in UsbAccessory accessory, String packageName, int userId);
/* Returns true if the caller has permission to access the device. */
- boolean hasDevicePermission(in UsbDevice device);
+ boolean hasDevicePermission(in UsbDevice device, String packageName);
/* Returns true if the caller has permission to access the accessory. */
boolean hasAccessoryPermission(in UsbAccessory accessory);
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 6ce96698e444..bdb90bcca4f8 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -344,7 +344,7 @@ public class UsbManager {
public UsbDeviceConnection openDevice(UsbDevice device) {
try {
String deviceName = device.getDeviceName();
- ParcelFileDescriptor pfd = mService.openDevice(deviceName);
+ ParcelFileDescriptor pfd = mService.openDevice(deviceName, mContext.getPackageName());
if (pfd != null) {
UsbDeviceConnection connection = new UsbDeviceConnection(device);
boolean result = connection.open(deviceName, pfd, mContext);
@@ -400,6 +400,9 @@ public class UsbManager {
* Permission might have been granted temporarily via
* {@link #requestPermission(UsbDevice, PendingIntent)} or
* by the user choosing the caller as the default application for the device.
+ * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that
+ * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they
+ * have additionally the {@link android.Manifest.permission#CAMERA} permission.
*
* @param device to check permissions for
* @return true if caller has permission
@@ -409,7 +412,7 @@ public class UsbManager {
return false;
}
try {
- return mService.hasDevicePermission(device);
+ return mService.hasDevicePermission(device, mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -450,6 +453,10 @@ public class UsbManager {
* permission was granted by the user
* </ul>
*
+ * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that
+ * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they
+ * have additionally the {@link android.Manifest.permission#CAMERA} permission.
+ *
* @param device to request permissions for
* @param pi PendingIntent for returning result
*/
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 64f8f39e2bca..d6e62cf1f88d 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -15,6 +15,7 @@
*/
package android.net;
+import android.annotation.NonNull;
import android.annotation.StringDef;
import android.os.Build;
import android.os.Parcel;
@@ -27,8 +28,10 @@ import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
- * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to
- * RFC 4301.
+ * This class represents a single algorithm that can be used by an {@link IpSecTransform}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
*/
public final class IpSecAlgorithm implements Parcelable {
/**
@@ -39,16 +42,16 @@ public final class IpSecAlgorithm implements Parcelable {
public static final String CRYPT_AES_CBC = "cbc(aes)";
/**
- * MD5 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in new
- * applications and is provided for legacy compatibility with 3gpp infrastructure.
+ * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+ * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
*
* <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128.
*/
public static final String AUTH_HMAC_MD5 = "hmac(md5)";
/**
- * SHA1 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in
- * new applications and is provided for legacy compatibility with 3gpp infrastructure.
+ * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+ * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
*
* <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160.
*/
@@ -69,7 +72,7 @@ public final class IpSecAlgorithm implements Parcelable {
public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
/**
- * SHA512 HMAC Authentication/Integrity Algorithm
+ * SHA512 HMAC Authentication/Integrity Algorithm.
*
* <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512.
*/
@@ -80,9 +83,9 @@ public final class IpSecAlgorithm implements Parcelable {
*
* <p>Valid lengths for keying material are {160, 224, 288}.
*
- * <p>As per RFC4106 (Section 8.1), keying material consists of a 128, 192, or 256 bit AES key
- * followed by a 32-bit salt. RFC compliance requires that the salt must be unique per
- * invocation with the same key.
+ * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section
+ * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
+ * salt. RFC compliance requires that the salt must be unique per invocation with the same key.
*
* <p>Valid ICV (truncation) lengths are {64, 96, 128}.
*/
@@ -105,48 +108,47 @@ public final class IpSecAlgorithm implements Parcelable {
private final int mTruncLenBits;
/**
- * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
- * algorithm
+ * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+ * defined as constants in this class.
*
- * @param algorithm type for IpSec.
- * @param key non-null Key padded to a multiple of 8 bits.
+ * @param algorithm name of the algorithm.
+ * @param key key padded to a multiple of 8 bits.
*/
- public IpSecAlgorithm(String algorithm, byte[] key) {
+ public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key) {
this(algorithm, key, key.length * 8);
}
/**
- * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
- * algorithm
+ * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+ * defined as constants in this class.
+ *
+ * <p>This constructor only supports algorithms that use a truncation length. i.e.
+ * Authentication and Authenticated Encryption algorithms.
*
- * @param algoName precise name of the algorithm to be used.
- * @param key non-null Key padded to a multiple of 8 bits.
- * @param truncLenBits the number of bits of output hash to use; only meaningful for
- * Authentication or Authenticated Encryption (equivalent to ICV length).
+ * @param algorithm name of the algorithm.
+ * @param key key padded to a multiple of 8 bits.
+ * @param truncLenBits number of bits of output hash to use.
*/
- public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) {
- if (!isTruncationLengthValid(algoName, truncLenBits)) {
+ public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
+ if (!isTruncationLengthValid(algorithm, truncLenBits)) {
throw new IllegalArgumentException("Unknown algorithm or invalid length");
}
- mName = algoName;
+ mName = algorithm;
mKey = key.clone();
mTruncLenBits = Math.min(truncLenBits, key.length * 8);
}
- /** Retrieve the algorithm name */
+ /** Get the algorithm name */
public String getName() {
return mName;
}
- /** Retrieve the key for this algorithm */
+ /** Get the key for this algorithm */
public byte[] getKey() {
return mKey.clone();
}
- /**
- * Retrieve the truncation length, in bits, for the key in this algo. By default this will be
- * the length in bits of the key.
- */
+ /** Get the truncation length of this algorithm, in bits */
public int getTruncationLengthBits() {
return mTruncLenBits;
}
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 61b13a922dd4..e6cd3fc130a0 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -20,7 +20,12 @@ import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
-/** @hide */
+/**
+ * This class encapsulates all the configuration parameters needed to create IPsec transforms and
+ * policies.
+ *
+ * @hide
+ */
public final class IpSecConfig implements Parcelable {
private static final String TAG = "IpSecConfig";
@@ -38,6 +43,9 @@ public final class IpSecConfig implements Parcelable {
// for outbound packets. It may also be used to select packets.
private Network mNetwork;
+ /**
+ * This class captures the parameters that specifically apply to inbound or outbound traffic.
+ */
public static class Flow {
// Minimum requirements for identifying a transform
// SPI identifying the IPsec flow in packet processing
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index eccd5f47f2dd..a9e60ec88a8e 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -19,6 +19,7 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.NonNull;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.Context;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
@@ -37,22 +38,28 @@ import java.net.InetAddress;
import java.net.Socket;
/**
- * This class contains methods for managing IPsec sessions, which will perform kernel-space
- * encryption and decryption of socket or Network traffic.
+ * This class contains methods for managing IPsec sessions. Once configured, the kernel will apply
+ * confidentiality (encryption) and integrity (authentication) to IP traffic.
*
- * <p>An IpSecManager may be obtained by calling {@link
- * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link
- * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE}
+ * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
+ * transport mode security associations and apply them to individual sockets. Applications looking
+ * to create a VPN should use {@link VpnService}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
*/
@SystemService(Context.IPSEC_SERVICE)
public final class IpSecManager {
private static final String TAG = "IpSecManager";
/**
- * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
+ * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
*
* <p>No IPsec packet may contain an SPI of 0.
+ *
+ * @hide
*/
+ @TestApi
public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
/** @hide */
@@ -66,10 +73,12 @@ public final class IpSecManager {
public static final int INVALID_RESOURCE_ID = 0;
/**
- * Indicates that the combination of remote InetAddress and SPI was non-unique for a given
- * request. If encountered, selection of a new SPI is required before a transform may be
- * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random
- * or reserved using reserveSecurityParameterIndex.
+ * Thrown to indicate that a requested SPI is in use.
+ *
+ * <p>The combination of remote {@code InetAddress} and SPI must be unique across all apps on
+ * one device. If this error is encountered, a new SPI is required before a transform may be
+ * created. This error can be avoided by calling {@link
+ * IpSecManager#reserveSecurityParameterIndex}.
*/
public static final class SpiUnavailableException extends AndroidException {
private final int mSpi;
@@ -78,24 +87,26 @@ public final class IpSecManager {
* Construct an exception indicating that a transform with the given SPI is already in use
* or otherwise unavailable.
*
- * @param msg Description indicating the colliding SPI
+ * @param msg description indicating the colliding SPI
* @param spi the SPI that could not be used due to a collision
*/
SpiUnavailableException(String msg, int spi) {
- super(msg + "(spi: " + spi + ")");
+ super(msg + " (spi: " + spi + ")");
mSpi = spi;
}
- /** Retrieve the SPI that caused a collision */
+ /** Get the SPI that caused a collision. */
public int getSpi() {
return mSpi;
}
}
/**
- * Indicates that the requested system resource for IPsec, such as a socket or other system
- * resource is unavailable. If this exception is thrown, try releasing allocated objects of the
- * type requested.
+ * Thrown to indicate that an IPsec resource is unavailable.
+ *
+ * <p>This could apply to resources such as sockets, {@link SecurityParameterIndex}, {@link
+ * IpSecTransform}, or other system resources. If this exception is thrown, users should release
+ * allocated objects of the type requested.
*/
public static final class ResourceUnavailableException extends AndroidException {
@@ -106,6 +117,13 @@ public final class IpSecManager {
private final IIpSecService mService;
+ /**
+ * This class represents a reserved SPI.
+ *
+ * <p>Objects of this type are used to track reserved security parameter indices. They can be
+ * obtained by calling {@link IpSecManager#reserveSecurityParameterIndex} and must be released
+ * by calling {@link #close()} when they are no longer needed.
+ */
public static final class SecurityParameterIndex implements AutoCloseable {
private final IIpSecService mService;
private final InetAddress mRemoteAddress;
@@ -113,7 +131,7 @@ public final class IpSecManager {
private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
private int mResourceId;
- /** Return the underlying SPI held by this object */
+ /** Get the underlying SPI held by this object. */
public int getSpi() {
return mSpi;
}
@@ -135,6 +153,7 @@ public final class IpSecManager {
mCloseGuard.close();
}
+ /** Check that the SPI was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -197,13 +216,13 @@ public final class IpSecManager {
}
/**
- * Reserve an SPI for traffic bound towards the specified remote address.
+ * Reserve a random SPI for traffic bound to or from the specified remote address.
*
* <p>If successful, this SPI is guaranteed available until released by a call to {@link
* SecurityParameterIndex#close()}.
*
* @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
- * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
+ * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
* @return the reserved SecurityParameterIndex
* @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
* for this user
@@ -223,17 +242,18 @@ public final class IpSecManager {
}
/**
- * Reserve an SPI for traffic bound towards the specified remote address.
+ * Reserve the requested SPI for traffic bound to or from the specified remote address.
*
* <p>If successful, this SPI is guaranteed available until released by a call to {@link
* SecurityParameterIndex#close()}.
*
* @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
- * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
- * @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
+ * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
+ * @param requestedSpi the requested SPI, or '0' to allocate a random SPI
* @return the reserved SecurityParameterIndex
* @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
* for this user
+ * @throws SpiUnavailableException indicating that the requested SPI could not be reserved
*/
public SecurityParameterIndex reserveSecurityParameterIndex(
int direction, InetAddress remoteAddress, int requestedSpi)
@@ -245,16 +265,28 @@ public final class IpSecManager {
}
/**
- * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
- * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
- * transform. For security reasons, attempts to send traffic to any IP address other than the
- * address associated with that transform will throw an IOException. In addition, if the
- * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
- * send() or receive() until the transform is removed from the socket by calling {@link
- * #removeTransportModeTransform(Socket, IpSecTransform)};
+ * Apply an IPsec transform to a stream socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+ *
+ * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+ * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+ * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+ * allows rekey procedures where both transforms are valid until both endpoints are using the
+ * new transform and all in-flight packets have been received.
*
* @param socket a stream socket
- * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
* @hide
*/
public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
@@ -265,16 +297,28 @@ public final class IpSecManager {
}
/**
- * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec
- * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
- * transform. For security reasons, attempts to send traffic to any IP address other than the
- * address associated with that transform will throw an IOException. In addition, if the
- * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
- * send() or receive() until the transform is removed from the socket by calling {@link
- * #removeTransportModeTransform(DatagramSocket, IpSecTransform)};
+ * Apply an IPsec transform to a datagram socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+ *
+ * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+ * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+ * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+ * allows rekey procedures where both transforms are valid until both endpoints are using the
+ * new transform and all in-flight packets have been received.
*
* @param socket a datagram socket
- * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
* @hide
*/
public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
@@ -285,16 +329,28 @@ public final class IpSecManager {
}
/**
- * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
- * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
- * transform. For security reasons, attempts to send traffic to any IP address other than the
- * address associated with that transform will throw an IOException. In addition, if the
- * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
- * send() or receive() until the transform is removed from the socket by calling {@link
- * #removeTransportModeTransform(FileDescriptor, IpSecTransform)};
+ * Apply an IPsec transform to a socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+ *
+ * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+ * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+ * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+ * allows rekey procedures where both transforms are valid until both endpoints are using the
+ * new transform and all in-flight packets have been received.
*
* @param socket a socket file descriptor
- * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
*/
public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
throws IOException {
@@ -323,6 +379,7 @@ public final class IpSecManager {
* Applications should probably not use this API directly. Instead, they should use {@link
* VpnService} to provide VPN capability in a more generic fashion.
*
+ * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked.
* @param net a {@link Network} that will be tunneled via IP Sec.
* @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform.
* @hide
@@ -330,14 +387,19 @@ public final class IpSecManager {
public void applyTunnelModeTransform(Network net, IpSecTransform transform) {}
/**
- * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
- * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
- * communication in the clear in the event socket reuse is desired. This operation will succeed
- * regardless of the underlying state of a transform. If a transform is removed, communication
- * on all sockets to which that transform was applied will fail until this method is called.
+ * Remove an IPsec transform from a stream socket.
+ *
+ * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+ * regardless of the state of the transform. Removing a transform from a socket allows the
+ * socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
*
- * @param socket a socket that previously had a transform applied to it.
+ * @param socket a socket that previously had a transform applied to it
* @param transform the IPsec Transform that was previously applied to the given socket
+ * @throws IOException indicating that the transform could not be removed from the socket
* @hide
*/
public void removeTransportModeTransform(Socket socket, IpSecTransform transform)
@@ -348,14 +410,19 @@ public final class IpSecManager {
}
/**
- * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not
- * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
- * communication in the clear in the event socket reuse is desired. This operation will succeed
- * regardless of the underlying state of a transform. If a transform is removed, communication
- * on all sockets to which that transform was applied will fail until this method is called.
+ * Remove an IPsec transform from a datagram socket.
*
- * @param socket a socket that previously had a transform applied to it.
+ * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+ * regardless of the state of the transform. Removing a transform from a socket allows the
+ * socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
* @param transform the IPsec Transform that was previously applied to the given socket
+ * @throws IOException indicating that the transform could not be removed from the socket
* @hide
*/
public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
@@ -366,14 +433,19 @@ public final class IpSecManager {
}
/**
- * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
- * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
- * communication in the clear in the event socket reuse is desired. This operation will succeed
- * regardless of the underlying state of a transform. If a transform is removed, communication
- * on all sockets to which that transform was applied will fail until this method is called.
+ * Remove an IPsec transform from a socket.
+ *
+ * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+ * regardless of the state of the transform. Removing a transform from a socket allows the
+ * socket to be reused for communication in the clear.
*
- * @param socket a socket file descriptor that previously had a transform applied to it.
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
* @param transform the IPsec Transform that was previously applied to the given socket
+ * @throws IOException indicating that the transform could not be removed from the socket
*/
public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
throws IOException {
@@ -382,7 +454,7 @@ public final class IpSecManager {
}
}
- /* Call down to activate a transform */
+ /* Call down to remove a transform */
private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
try {
mService.removeTransportModeTransform(pfd, transform.getResourceId());
@@ -397,6 +469,7 @@ public final class IpSecManager {
* all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
* lost, all traffic will drop.
*
+ * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked.
* @param net a network that currently has transform applied to it.
* @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
* network
@@ -405,11 +478,18 @@ public final class IpSecManager {
public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
/**
- * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for
- * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic.
+ * This class provides access to a UDP encapsulation Socket.
*
- * <p>The socket provided by this class cannot be re-bound or closed via the inner
- * FileDescriptor. Instead, disposing of this socket requires a call to close().
+ * <p>{@code UdpEncapsulationSocket} wraps a system-provided datagram socket intended for IKEv2
+ * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link
+ * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the
+ * caller. The caller should not close the {@code FileDescriptor} returned by {@link
+ * #getSocket}, but should use {@link #close} instead.
+ *
+ * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic
+ * of the next user who binds to that port. To prevent this scenario, these sockets are held
+ * open by the system so that they may only be closed by calling {@link #close} or when the user
+ * process exits.
*/
public static final class UdpEncapsulationSocket implements AutoCloseable {
private final ParcelFileDescriptor mPfd;
@@ -443,7 +523,7 @@ public final class IpSecManager {
mCloseGuard.open("constructor");
}
- /** Access the inner UDP Encapsulation Socket */
+ /** Get the wrapped socket. */
public FileDescriptor getSocket() {
if (mPfd == null) {
return null;
@@ -451,22 +531,19 @@ public final class IpSecManager {
return mPfd.getFileDescriptor();
}
- /** Retrieve the port number of the inner encapsulation socket */
+ /** Get the bound port of the wrapped socket. */
public int getPort() {
return mPort;
}
- @Override
/**
- * Release the resources that have been reserved for this Socket.
+ * Close this socket.
*
- * <p>This method closes the underlying socket, reducing a user's allocated sockets in the
- * system. This must be done as part of cleanup following use of a socket. Failure to do so
- * will cause the socket to count against a total allocation limit for IpSec and eventually
- * fail due to resource limits.
- *
- * @param fd a file descriptor previously returned as a UDP Encapsulation socket.
+ * <p>This closes the wrapped socket. Open encapsulation sockets count against a user's
+ * resource limits, and forgetting to close them eventually will result in {@link
+ * ResourceUnavailableException} being thrown.
*/
+ @Override
public void close() throws IOException {
try {
mService.closeUdpEncapsulationSocket(mResourceId);
@@ -483,6 +560,7 @@ public final class IpSecManager {
mCloseGuard.close();
}
+ /** Check that the socket was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -499,21 +577,14 @@ public final class IpSecManager {
};
/**
- * Open a socket that is bound to a free UDP port on the system.
- *
- * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
- * the caller. This provides safe access to a socket on a port that can later be used as a UDP
- * Encapsulation port.
+ * Open a socket for UDP encapsulation and bind to the given port.
*
- * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
- * socket port. Explicitly opening this port is only necessary if communication is desired on
- * that port.
+ * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
*
- * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this
- * method will bind to the specified port or fail. To retrieve the port number, call {@link
- * android.system.Os#getsockname(FileDescriptor)}.
- * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime
- * of the object.
+ * @param port a local UDP port
+ * @return a socket that is bound to the given port
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
*/
// Returning a socket in this fashion that has been created and bound by the system
// is the only safe way to ensure that a socket is both accessible to the user and
@@ -533,17 +604,16 @@ public final class IpSecManager {
}
/**
- * Open a socket that is bound to a port selected by the system.
+ * Open a socket for UDP encapsulation.
*
- * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
- * the caller. This provides safe access to a socket on a port that can later be used as a UDP
- * Encapsulation port.
+ * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
*
- * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
- * socket port. Explicitly opening this port is only necessary if communication is desired on
- * that port.
+ * <p>The local port of the returned socket can be obtained by calling {@link
+ * UdpEncapsulationSocket#getPort()}.
*
- * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port
+ * @return a socket that is bound to a local port
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
*/
// Returning a socket in this fashion that has been created and bound by the system
// is the only safe way to ensure that a socket is both accessible to the user and
@@ -556,7 +626,7 @@ public final class IpSecManager {
}
/**
- * Retrieve an instance of an IpSecManager within you application context
+ * Construct an instance of IpSecManager within an application context.
*
* @param context the application context for this manager
* @hide
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 48b5bd5c3d5b..cda4ec762caf 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -38,27 +38,29 @@ import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
/**
- * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec.
+ * This class represents an IPsec transform, which comprises security associations in one or both
+ * directions.
*
- * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout
- * the lifetime of the underlying transform. If a transform object leaves scope, the underlying
- * transform may be disabled automatically, with likely undesirable results.
+ * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
+ * object encapsulates the properties and state of an inbound and outbound IPsec security
+ * association. That includes, but is not limited to, algorithm choice, key material, and allocated
+ * system resources.
*
- * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
- * of traffic or may represent a transport mode transform operating on a Socket or Sockets.
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
*/
public final class IpSecTransform implements AutoCloseable {
private static final String TAG = "IpSecTransform";
/**
- * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
- * to traffic towards the host.
+ * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+ * applies to traffic towards the host.
*/
public static final int DIRECTION_IN = 0;
/**
- * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
- * to traffic from the host.
+ * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+ * applies to traffic from the host.
*/
public static final int DIRECTION_OUT = 1;
@@ -77,16 +79,16 @@ public final class IpSecTransform implements AutoCloseable {
public static final int ENCAP_NONE = 0;
/**
- * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad
- * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP.
+ * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP
+ * header and payload. This prevents traffic from being interpreted as ESP or IKEv2.
*
* @hide
*/
public static final int ENCAP_ESPINUDP_NON_IKE = 1;
/**
- * IpSec traffic will be encapsulated within UDP as per <a
- * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
+ * IPsec traffic will be encapsulated within UDP as per
+ * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>.
*
* @hide
*/
@@ -165,13 +167,14 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Deactivate an IpSecTransform and free all resources for that transform that are managed by
- * the system for this Transform.
+ * Deactivate this {@code IpSecTransform} and free allocated resources.
*
- * <p>Deactivating a transform while it is still applied to any Socket will result in sockets
- * refusing to send or receive data. This method will silently succeed if the specified
- * transform has already been removed; thus, it is always safe to attempt cleanup when a
- * transform is no longer needed.
+ * <p>Deactivating a transform while it is still applied to a socket will result in errors on
+ * that socket. Make sure to remove transforms by calling {@link
+ * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a
+ * socket will not deactivate it (because one transform may be applied to multiple sockets).
+ *
+ * <p>It is safe to call this method on a transform that has already been deactivated.
*/
public void close() {
Log.d(TAG, "Removing Transform with Id " + mResourceId);
@@ -197,6 +200,7 @@ public final class IpSecTransform implements AutoCloseable {
}
}
+ /** Check that the transform was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -264,65 +268,63 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Builder object to facilitate the creation of IpSecTransform objects.
- *
- * <p>Apply additional properties to the transform and then call a build() method to return an
- * IpSecTransform object.
- *
- * @see Builder#buildTransportModeTransform(InetAddress)
+ * This class is used to build {@link IpSecTransform} objects.
*/
public static class Builder {
private Context mContext;
private IpSecConfig mConfig;
/**
- * Add an encryption algorithm to the transform for the given direction.
+ * Set the encryption algorithm for the given direction.
*
- * <p>If encryption is set for a given direction without also providing an SPI for that
- * direction, creation of an IpSecTransform will fail upon calling a build() method.
+ * <p>If encryption is set for a direction without also providing an SPI for that direction,
+ * creation of an {@code IpSecTransform} will fail when attempting to build the transform.
*
- * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ * <p>Encryption is mutually exclusive with authenticated encryption.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
*/
public IpSecTransform.Builder setEncryption(
@TransformDirection int direction, IpSecAlgorithm algo) {
+ // TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
mConfig.setEncryption(direction, algo);
return this;
}
/**
- * Add an authentication/integrity algorithm to the transform.
+ * Set the authentication (integrity) algorithm for the given direction.
*
- * <p>If authentication is set for a given direction without also providing an SPI for that
- * direction, creation of an IpSecTransform will fail upon calling a build() method.
+ * <p>If authentication is set for a direction without also providing an SPI for that
+ * direction, creation of an {@code IpSecTransform} will fail when attempting to build the
+ * transform.
*
- * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ * <p>Authentication is mutually exclusive with authenticated encryption.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
*/
public IpSecTransform.Builder setAuthentication(
@TransformDirection int direction, IpSecAlgorithm algo) {
+ // TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
mConfig.setAuthentication(direction, algo);
return this;
}
/**
- * Add an authenticated encryption algorithm to the transform for the given direction.
+ * Set the authenticated encryption algorithm for the given direction.
*
* <p>If an authenticated encryption algorithm is set for a given direction without also
- * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling
- * a build() method.
+ * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when
+ * attempting to build the transform.
*
* <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated
* Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as
- * referred to in RFC 4301)
+ * referred to in <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
*
* <p>Authenticated encryption is mutually exclusive with encryption and authentication.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
* be applied.
*/
@@ -333,19 +335,16 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
- * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
- * given destination address.
+ * Set the SPI for the given direction.
*
- * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
- * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int,
- * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being
- * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}.
+ * <p>Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies
+ * packets to a given destination address. To prevent SPI collisions, values should be
+ * reserved by calling {@link IpSecManager#reserveSecurityParameterIndex}.
*
- * <p>Unless an SPI is set for a given direction, traffic in that direction will be
- * sent/received without any IPsec applied.
+ * <p>If the SPI and algorithms are omitted for one direction, traffic in that direction
+ * will not be encrypted or authenticated.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
* traffic
*/
@@ -356,11 +355,10 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Specify the network on which this transform will emit its traffic; (otherwise it will
- * emit on the default network).
+ * Set the {@link Network} which will carry tunneled traffic.
*
- * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in
- * tunnel mode.
+ * <p>Restricts the transformed traffic to a particular {@link Network}. This is required
+ * for tunnel mode, otherwise tunneled traffic would be sent on the default network.
*
* @hide
*/
@@ -371,15 +369,18 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Add UDP encapsulation to an IPv4 transform
+ * Add UDP encapsulation to an IPv4 transform.
*
- * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for
- * details on how UDP should be applied to IPsec.
+ * <p>This allows IPsec traffic to pass through a NAT.
*
- * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and
- * receiving encapsulating traffic.
- * @param remotePort the UDP port number of the remote that will send and receive
- * encapsulated traffic. In the case of IKE, this is likely port 4500.
+ * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec
+ * ESP Packets</a>
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23,
+ * NAT Traversal of IKEv2</a>
+ *
+ * @param localSocket a socket for sending and receiving encapsulated traffic
+ * @param remotePort the UDP port number of the remote host that will send and receive
+ * encapsulated traffic. In the case of IKEv2, this should be port 4500.
*/
public IpSecTransform.Builder setIpv4Encapsulation(
IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
@@ -393,12 +394,15 @@ public final class IpSecTransform implements AutoCloseable {
// TODO: Probably a better exception to throw for NATTKeepalive failure
// TODO: Specify the needed NATT keepalive permission.
/**
- * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded
- * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot
- * be activated, then the transform will fail to activate and throw an IOException.
+ * Set NAT-T keepalives to be sent with a given interval.
+ *
+ * <p>This will set power-efficient keepalive packets to be sent by the system. If NAT-T
+ * keepalive is requested but cannot be activated, then creation of an {@link
+ * IpSecTransform} will fail when calling the build method.
+ *
+ * @param intervalSeconds the maximum number of seconds between keepalive packets. Must be
+ * between 20s and 3600s.
*
- * @param intervalSeconds the maximum number of seconds between keepalive packets, no less
- * than 20s and no more than 3600s.
* @hide
*/
@SystemApi
@@ -408,36 +412,29 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform.
- * Some parameters have interdependencies that are checked at build time. If a well-formed
- * transform cannot be created from the supplied parameters, this method will throw an
- * Exception.
+ * Build a transport mode {@link IpSecTransform}.
*
- * <p>Upon a successful return from this call, the provided IpSecTransform will be active
- * and may be applied to sockets. If too many IpSecTransform objects are active for a given
- * user this operation will fail and throw ResourceUnavailableException. To avoid these
- * exceptions, unused Transform objects must be cleaned up by calling {@link
- * IpSecTransform#close()} when they are no longer needed.
+ * <p>This builds and activates a transport mode transform. Note that an active transform
+ * will not affect any network traffic until it has been applied to one or more sockets.
*
- * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this
- * socket will cause the transform to be applied.
- * <p>Note that an active transform will not impact any network traffic until it has
- * been applied to one or more Sockets. Calling this method is a necessary precondition
- * for applying it to a socket, but is not sufficient to actually apply IPsec.
+ * @see IpSecManager#applyTransportModeTransform
+ *
+ * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use
+ * this transform
* @throws IllegalArgumentException indicating that a particular combination of transform
- * properties is invalid.
- * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms
- * may be allocated
- * @throws SpiUnavailableException if the SPI collides with an existing transform
- * (unlikely).
- * @throws ResourceUnavailableException if the current user currently has exceeded the
- * number of allowed active transforms.
+ * properties is invalid
+ * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are
+ * active
+ * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+ * collides with an existing transform
+ * @throws IOException indicating other errors
*/
public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
throws IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException, IOException {
mConfig.setMode(MODE_TRANSPORT);
mConfig.setRemoteAddress(remoteAddress.getHostAddress());
+ // FIXME: modifying a builder after calling build can change the built transform.
return new IpSecTransform(mContext, mConfig).activate();
}
@@ -465,9 +462,9 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Create a new IpSecTransform.Builder to construct an IpSecTransform
+ * Create a new IpSecTransform.Builder.
*
- * @param context current Context
+ * @param context current context
*/
public Builder(@NonNull Context context) {
Preconditions.checkNotNull(context);
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a8bd940326d6..af91e81959d1 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -222,8 +222,10 @@ public abstract class BatteryStats implements Parcelable {
* - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly]
* New in version 27:
* - Always On Display (screen doze mode) time and power
+ * New in version 28:
+ * - Light/Deep Doze power
*/
- static final int CHECKIN_VERSION = 27;
+ static final int CHECKIN_VERSION = 28;
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -2696,6 +2698,18 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getUahDischarge(int which);
/**
+ * @return the amount of battery discharge while the device is in light idle mode, measured in
+ * micro-Ampere-hours.
+ */
+ public abstract long getUahDischargeLightDoze(int which);
+
+ /**
+ * @return the amount of battery discharge while the device is in deep idle mode, measured in
+ * micro-Ampere-hours.
+ */
+ public abstract long getUahDischargeDeepDoze(int which);
+
+ /**
* Returns the estimated real battery capacity, which may be less than the capacity
* declared by the PowerProfile.
* @return The estimated battery capacity in mAh.
@@ -3327,6 +3341,8 @@ public abstract class BatteryStats implements Parcelable {
final long dischargeCount = getUahDischarge(which);
final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
+ final long dischargeLightDozeCount = getUahDischargeLightDoze(which);
+ final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which);
final StringBuilder sb = new StringBuilder(128);
@@ -3497,14 +3513,16 @@ public abstract class BatteryStats implements Parcelable {
getDischargeStartLevel()-getDischargeCurrentLevel(),
getDischargeAmountScreenOn(), getDischargeAmountScreenOff(),
dischargeCount / 1000, dischargeScreenOffCount / 1000,
- getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000);
+ getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000,
+ dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000);
} else {
dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA,
getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(),
getDischargeAmountScreenOnSinceCharge(),
getDischargeAmountScreenOffSinceCharge(),
dischargeCount / 1000, dischargeScreenOffCount / 1000,
- getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000);
+ getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000,
+ dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000);
}
if (reqUid < 0) {
@@ -4169,6 +4187,26 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
+ final long dischargeLightDozeCount = getUahDischargeLightDoze(which);
+ if (dischargeLightDozeCount >= 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Device light doze discharge: ");
+ sb.append(BatteryStatsHelper.makemAh(dischargeLightDozeCount / 1000.0));
+ sb.append(" mAh");
+ pw.println(sb.toString());
+ }
+
+ final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which);
+ if (dischargeDeepDozeCount >= 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Device deep doze discharge: ");
+ sb.append(BatteryStatsHelper.makemAh(dischargeDeepDozeCount / 1000.0));
+ sb.append(" mAh");
+ pw.println(sb.toString());
+ }
+
pw.print(" Start clock time: ");
pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString());
@@ -7131,6 +7169,10 @@ public abstract class BatteryStats implements Parcelable {
getUahDischargeScreenOff(which) / 1000);
proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
getUahDischargeScreenDoze(which) / 1000);
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_LIGHT_DOZE,
+ getUahDischargeLightDoze(which) / 1000);
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_DEEP_DOZE,
+ getUahDischargeDeepDoze(which) / 1000);
proto.end(bdToken);
// Time remaining
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index d0920187498f..36121d452d96 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1780,7 +1780,7 @@ public final class Parcel {
final int truncatedSize = Math.min(stackTrace.length, 5);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < truncatedSize; i++) {
- sb.append("\tat ").append(stackTrace[i]);
+ sb.append("\tat ").append(stackTrace[i]).append('\n');
}
writeString(sb.toString());
final int payloadPosition = dataPosition();
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 0fce7a454717..5dd8d05cfb98 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -387,6 +387,12 @@ public final class PowerManager {
public static final int GO_TO_SLEEP_REASON_SLEEP_BUTTON = 6;
/**
+ * Go to sleep reason code: Going to sleep by request of an accessibility service
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_ACCESSIBILITY = 7;
+
+ /**
* Go to sleep flag: Skip dozing state and directly go to full sleep.
* @hide
*/
diff --git a/core/java/android/os/ShellCallback.java b/core/java/android/os/ShellCallback.java
index ad9fbfbfae40..6a62424cc117 100644
--- a/core/java/android/os/ShellCallback.java
+++ b/core/java/android/os/ShellCallback.java
@@ -105,6 +105,9 @@ public class ShellCallback implements Parcelable {
ShellCallback(Parcel in) {
mLocal = false;
mShellCallback = IShellCallback.Stub.asInterface(in.readStrongBinder());
+ if (mShellCallback != null) {
+ Binder.allowBlocking(mShellCallback.asBinder());
+ }
}
public static final Parcelable.Creator<ShellCallback> CREATOR
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index d75219fdfd11..fa05a5e1b22e 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -91,7 +91,13 @@ public abstract class ShellCommand {
mCmd = cmd;
mResultReceiver = resultReceiver;
- if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget);
+ if (DEBUG) {
+ RuntimeException here = new RuntimeException("here");
+ here.fillInStackTrace();
+ Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
+ Slog.d(TAG, "Calling uid=" + Binder.getCallingUid()
+ + " pid=" + Binder.getCallingPid() + " ShellCallback=" + getShellCallback());
+ }
int res = -1;
try {
res = onCommand(mCmd);
@@ -227,15 +233,19 @@ public abstract class ShellCommand {
* @hide
*/
public ParcelFileDescriptor openFileForSystem(String path, String mode) {
+ if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode);
try {
ParcelFileDescriptor pfd = getShellCallback().openFile(path,
"u:r:system_server:s0", mode);
if (pfd != null) {
+ if (DEBUG) Slog.d(TAG, "Got file: " + pfd);
return pfd;
}
} catch (RuntimeException e) {
+ if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage());
getErrPrintWriter().println("Failure opening file: " + e.getMessage());
}
+ if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path);
getErrPrintWriter().println("Error: Unable to open file: " + path);
getErrPrintWriter().println("Consider using a file under /data/local/tmp/");
return null;
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index cd1b1908e4d8..d07b2ac980eb 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -381,4 +381,9 @@ public abstract class WindowManagerInternal {
* Sets callback to DragDropController.
*/
public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
+
+ /**
+ * @see android.view.IWindowManager#lockNow
+ */
+ public abstract void lockNow();
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index e330916130dd..a2c55b091860 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -90,6 +90,31 @@ import java.util.concurrent.Executor;
* another process. The hierarchy is inflated from a layout resource
* file, and this class provides some basic operations for modifying
* the content of the inflated hierarchy.
+ *
+ * <p>{@code RemoteViews} is limited to support for the following layouts:</p>
+ * <ul>
+ * <li>{@link android.widget.AdapterViewFlipper}</li>
+ * <li>{@link android.widget.FrameLayout}</li>
+ * <li>{@link android.widget.GridLayout}</li>
+ * <li>{@link android.widget.GridView}</li>
+ * <li>{@link android.widget.LinearLayout}</li>
+ * <li>{@link android.widget.ListView}</li>
+ * <li>{@link android.widget.RelativeLayout}</li>
+ * <li>{@link android.widget.StackView}</li>
+ * <li>{@link android.widget.ViewFlipper}</li>
+ * </ul>
+ * <p>And the following widgets:</p>
+ * <ul>
+ * <li>{@link android.widget.AnalogClock}</li>
+ * <li>{@link android.widget.Button}</li>
+ * <li>{@link android.widget.Chronometer}</li>
+ * <li>{@link android.widget.ImageButton}</li>
+ * <li>{@link android.widget.ImageView}</li>
+ * <li>{@link android.widget.ProgressBar}</li>
+ * <li>{@link android.widget.TextClock}</li>
+ * <li>{@link android.widget.TextView}</li>
+ * </ul>
+ * <p>Descendants of these classes are not supported.</p>
*/
public class RemoteViews implements Parcelable, Filter {
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 7519fce467d9..fbdf17d84aa8 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -102,6 +102,7 @@ public final class ProcessState {
STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
STATE_CACHED_ACTIVITY_CLIENT, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_RECENT
STATE_CACHED_EMPTY, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 83b7d2f948f8..a1e6fd8e22f9 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -43,6 +43,7 @@ import dalvik.system.VMRuntime;
import java.io.Closeable;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.util.List;
@@ -118,6 +119,17 @@ public class NativeLibraryHelper {
return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable);
}
+ public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException {
+ final long[] apkHandles = new long[1];
+ final String path = lite.baseCodePath;
+ apkHandles[0] = nativeOpenApkFd(fd, path);
+ if (apkHandles[0] == 0) {
+ throw new IOException("Unable to open APK " + path + " from fd " + fd);
+ }
+
+ return new Handle(apkHandles, lite.multiArch, lite.extractNativeLibs, lite.debuggable);
+ }
+
Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs,
boolean debuggable) {
this.apkHandles = apkHandles;
@@ -152,6 +164,7 @@ public class NativeLibraryHelper {
}
private static native long nativeOpenApk(String path);
+ private static native long nativeOpenApkFd(FileDescriptor fd, String debugPath);
private static native void nativeClose(long handle);
private static native long nativeSumNativeBinaries(long handle, String cpuAbi,
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 59a7995ab2ac..e765ab1eae2f 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -42,6 +42,7 @@ import com.android.internal.annotations.VisibleForTesting;
import libcore.io.IoUtils;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Objects;
import java.util.UUID;
@@ -383,9 +384,15 @@ public class PackageHelper {
public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
throws IOException {
+ return calculateInstalledSize(pkg, abiOverride, null);
+ }
+
+ public static long calculateInstalledSize(PackageLite pkg, String abiOverride,
+ FileDescriptor fd) throws IOException {
NativeLibraryHelper.Handle handle = null;
try {
- handle = NativeLibraryHelper.Handle.create(pkg);
+ handle = fd != null ? NativeLibraryHelper.Handle.createFd(pkg, fd)
+ : NativeLibraryHelper.Handle.create(pkg);
return calculateInstalledSize(pkg, handle, abiOverride);
} finally {
IoUtils.closeQuietly(handle);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f2483c0a71e5..6ede72d49806 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -120,7 +120,7 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 169 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -599,6 +599,8 @@ public class BatteryStatsImpl extends BatteryStats {
private LongSamplingCounter mDischargeScreenOffCounter;
private LongSamplingCounter mDischargeScreenDozeCounter;
private LongSamplingCounter mDischargeCounter;
+ private LongSamplingCounter mDischargeLightDozeCounter;
+ private LongSamplingCounter mDischargeDeepDozeCounter;
static final int MAX_LEVEL_STEPS = 200;
@@ -697,6 +699,16 @@ public class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public long getUahDischargeLightDoze(int which) {
+ return mDischargeLightDozeCounter.getCountLocked(which);
+ }
+
+ @Override
+ public long getUahDischargeDeepDoze(int which) {
+ return mDischargeDeepDozeCounter.getCountLocked(which);
+ }
+
+ @Override
public int getEstimatedBatteryCapacity() {
return mEstimatedBatteryCapacity;
}
@@ -9085,6 +9097,8 @@ public class BatteryStatsImpl extends BatteryStats {
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase);
mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
+ mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
+ mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
mOnBattery = mOnBatteryInternal = false;
long uptime = mClocks.uptimeMillis() * 1000;
@@ -9664,6 +9678,8 @@ public class BatteryStatsImpl extends BatteryStats {
mChargeStepTracker.init();
mDischargeScreenOffCounter.reset(false);
mDischargeScreenDozeCounter.reset(false);
+ mDischargeLightDozeCounter.reset(false);
+ mDischargeDeepDozeCounter.reset(false);
mDischargeCounter.reset(false);
}
@@ -11263,6 +11279,11 @@ public class BatteryStatsImpl extends BatteryStats {
if (isScreenDoze(mScreenState)) {
mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
}
+ if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+ mDischargeLightDozeCounter.addCountLocked(chargeDiff);
+ } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) {
+ mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
+ }
}
mHistoryCur.batteryChargeUAh = chargeUAh;
setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
@@ -11308,6 +11329,11 @@ public class BatteryStatsImpl extends BatteryStats {
if (isScreenDoze(mScreenState)) {
mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
}
+ if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+ mDischargeLightDozeCounter.addCountLocked(chargeDiff);
+ } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) {
+ mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
+ }
}
mHistoryCur.batteryChargeUAh = chargeUAh;
changed = true;
@@ -12069,6 +12095,8 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeCounter.readSummaryFromParcelLocked(in);
mDischargeScreenOffCounter.readSummaryFromParcelLocked(in);
mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in);
+ mDischargeLightDozeCounter.readSummaryFromParcelLocked(in);
+ mDischargeDeepDozeCounter.readSummaryFromParcelLocked(in);
int NPKG = in.readInt();
if (NPKG > 0) {
mDailyPackageChanges = new ArrayList<>(NPKG);
@@ -12493,6 +12521,8 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeCounter.writeSummaryFromParcelLocked(out);
mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out);
mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out);
+ mDischargeLightDozeCounter.writeSummaryFromParcelLocked(out);
+ mDischargeDeepDozeCounter.writeSummaryFromParcelLocked(out);
if (mDailyPackageChanges != null) {
final int NPKG = mDailyPackageChanges.size();
out.writeInt(NPKG);
@@ -13044,6 +13074,8 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in);
mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
mLastWriteTime = in.readLong();
mRpmStats.clear();
@@ -13230,6 +13262,8 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeCounter.writeToParcel(out);
mDischargeScreenOffCounter.writeToParcel(out);
mDischargeScreenDozeCounter.writeToParcel(out);
+ mDischargeLightDozeCounter.writeToParcel(out);
+ mDischargeDeepDozeCounter.writeToParcel(out);
out.writeLong(mLastWriteTime);
out.writeInt(mRpmStats.size());
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index fce5dd58d7f9..17b98dade74f 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -557,6 +557,23 @@ com_android_internal_content_NativeLibraryHelper_openApk(JNIEnv *env, jclass, js
return reinterpret_cast<jlong>(zipFile);
}
+static jlong
+com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass,
+ jobject fileDescriptor, jstring debugPathName)
+{
+ ScopedUtfChars debugFilePath(env, debugPathName);
+
+ int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+ if (fd < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+ return 0;
+ }
+
+ ZipFileRO* zipFile = ZipFileRO::openFd(fd, debugFilePath.c_str());
+
+ return reinterpret_cast<jlong>(zipFile);
+}
+
static void
com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle)
{
@@ -567,6 +584,9 @@ static const JNINativeMethod gMethods[] = {
{"nativeOpenApk",
"(Ljava/lang/String;)J",
(void *)com_android_internal_content_NativeLibraryHelper_openApk},
+ {"nativeOpenApkFd",
+ "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
+ (void *)com_android_internal_content_NativeLibraryHelper_openApkFd},
{"nativeClose",
"(J)V",
(void *)com_android_internal_content_NativeLibraryHelper_close},
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index c33c0a0e0518..0a3344fe13f1 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -120,6 +120,14 @@ message SystemProto {
// via a coulomb counter. For historical reasons, total_mah_screen_doze is
// a subset of total_mah_screen_off.
optional int64 total_mah_screen_doze = 8;
+ // Total amount of battery discharged in mAh while the device was in light doze mode.
+ // This will only be non-zero for devices that report battery discharge
+ // via a coulomb counter.
+ optional int64 total_mah_light_doze = 9;
+ // Total amount of battery discharged in mAh while the device was in deep doze mode.
+ // This will only be non-zero for devices that report battery discharge
+ // via a coulomb counter.
+ optional int64 total_mah_deep_doze = 10;
};
optional BatteryDischarge battery_discharge = 2;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 86103e4c8902..d15c0752d5cc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -485,7 +485,6 @@
<protected-broadcast android:name="android.content.jobscheduler.JOB_DEADLINE_EXPIRED" />
<protected-broadcast android:name="android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW" />
<protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_SUPL" />
- <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
<protected-broadcast android:name="android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED" />
<protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" />
<protected-broadcast android:name="android.os.storage.action.DISK_SCANNED" />
@@ -504,6 +503,8 @@
<protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_CHANGED" />
<protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED" />
<protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
<protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
<protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
@@ -3958,6 +3959,10 @@
<service android:name="com.android.server.net.watchlist.ReportWatchlistJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
- </application>
+
+ <service android:name="com.android.server.display.BrightnessIdleJob"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+</application>
</manifest>
diff --git a/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml b/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml
new file mode 100644
index 000000000000..89e58aae6c34
--- /dev/null
+++ b/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="?attr/dialogPreferredPadding"
+ android:paddingLeft="?attr/dialogPreferredPadding"
+ android:paddingRight="?attr/dialogPreferredPadding">
+
+ <CheckBox
+ android:id="@+id/ask_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:text="@string/unsupported_compile_sdk_show" />
+</FrameLayout>
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index f26d6ed10c0c..e12f04af773b 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -21,9 +21,10 @@
for watch products. Do not translate. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Only show settings item due to smaller real estate. -->
+ <!-- Show smaller list of items due to smaller real estate. -->
<string-array translatable="false" name="config_globalActionsList">
- <item>assist</item>
+ <item>power</item>
+ <item>restart</item>
</string-array>
<!-- Base "touch slop" value used by ViewConfiguration as a
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index be0f6d9ac647..fe8ca560e5bf 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1340,6 +1340,23 @@
<p>The default value of this attribute is <code>1</code>. -->
<attr name="targetSandboxVersion" format="integer" />
+ <!-- The user-visible SDK version (ex. 26) of the framework against which the application was
+ compiled. This attribute is automatically specified by the Android build tools and should
+ NOT be manually specified.
+ <p>
+ This attribute is the compile-time equivalent of
+ {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}. -->
+ <attr name="compileSdkVersion" format="integer" />
+
+ <!-- The development codename (ex. "O") of the framework against which the application was
+ compiled, or "REL" if the application was compiled against a release build. This attribute
+ is automatically specified by the Android build tools and should NOT be manually
+ specified.
+ <p>
+ This attribute is the compile-time equivalent of
+ {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. -->
+ <attr name="compileSdkVersionCodename" format="string" />
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -1369,6 +1386,8 @@
<attr name="isolatedSplits" />
<attr name="isFeatureSplit" />
<attr name="targetSandboxVersion" />
+ <attr name="compileSdkVersion" />
+ <attr name="compileSdkVersionCodename" />
</declare-styleable>
<!-- The <code>application</code> tag describes application-level components
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index edd793d89afa..c10461643406 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -940,9 +940,16 @@
1 - Global actions menu
2 - Power off (with confirmation)
3 - Power off (without confirmation)
+ 4 - Go to voice assist
-->
<integer name="config_longPressOnPowerBehavior">1</integer>
+ <!-- Control the behavior when the user long presses the power button for a long time.
+ 0 - Nothing
+ 1 - Global actions menu
+ -->
+ <integer name="config_veryLongPressOnPowerBehavior">0</integer>
+
<!-- Control the behavior when the user long presses the back button. Non-zero values are only
valid for watches as part of CDD/CTS.
0 - Nothing
@@ -986,6 +993,9 @@
-->
<integer name="config_shortPressOnSleepBehavior">0</integer>
+ <!-- Time to wait while a button is pressed before triggering a very long press. -->
+ <integer name="config_veryLongPressTimeout">6000</integer>
+
<!-- Package name for default keyguard appwidget [DO NOT TRANSLATE] -->
<string name="widget_default_package_name" translatable="false"></string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index fdd56c410ed2..d3533fe6e79f 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2848,6 +2848,10 @@
<public name="ttcIndex" />
<public name="fontVariationSettings" />
<public name="dialogCornerRadius" />
+ <!-- @hide For use by platform and tools only. Developers should not specify this value. -->
+ <public name="compileSdkVersion" />
+ <!-- @hide For use by platform and tools only. Developers should not specify this value. -->
+ <public name="compileSdkVersionCodename" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7a3fa1a7b512..999ba7392fab 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2852,6 +2852,13 @@
<!-- [CHAR LIMIT=50] Unsupported display size dialog: check box label. -->
<string name="unsupported_display_size_show">Always show</string>
+ <!-- [CHAR LIMIT=200] Unsupported compile SDK dialog: message. Shown when an app may not be compatible with the device's current version of Android. -->
+ <string name="unsupported_compile_sdk_message"><xliff:g id="app_name">%1$s</xliff:g> was built for preview version %2$s of the Android OS and may behave unexpectedly. An updated version of the app may be available.</string>
+ <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: check box label. -->
+ <string name="unsupported_compile_sdk_show">Always show</string>
+ <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: label for button to check for an app update. -->
+ <string name="unsupported_compile_sdk_check_update">Check for update</string>
+
<!-- Text of the alert that is displayed when an application has violated StrictMode. -->
<string name="smv_application">The app <xliff:g id="application">%1$s</xliff:g>
(process <xliff:g id="process">%2$s</xliff:g>) has violated its self-enforced StrictMode policy.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 54970851f3c1..a1158162ee73 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -419,6 +419,8 @@
<java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
<java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
<java-symbol type="integer" name="config_longPressOnPowerBehavior" />
+ <java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" />
+ <java-symbol type="integer" name="config_veryLongPressTimeout" />
<java-symbol type="integer" name="config_longPressOnBackBehavior" />
<java-symbol type="integer" name="config_backPanicBehavior" />
<java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" />
@@ -3151,4 +3153,9 @@
<!-- From media projection -->
<java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" />
<java-symbol type="string" name="config_batterySaverDeviceSpecificConfig" />
+
+ <!-- Compile SDK check -->
+ <java-symbol type="layout" name="unsupported_compile_sdk_dialog_content" />
+ <java-symbol type="string" name="unsupported_compile_sdk_message" />
+ <java-symbol type="string" name="unsupported_compile_sdk_check_update" />
</resources>
diff --git a/data/keyboards/OWNERS b/data/keyboards/OWNERS
new file mode 100644
index 000000000000..031a6c1c7a89
--- /dev/null
+++ b/data/keyboards/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+
+michaelwr@google.com
+svv@google.com
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 3c8736ea0c4c..0485625e81e8 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -148,11 +148,15 @@ AssetManager::~AssetManager() {
int count = android_atomic_dec(&gCount);
if (kIsDebug) {
ALOGI("Destroying AssetManager in %p #%d\n", this, count);
+ } else {
+ ALOGV("Destroying AssetManager in %p #%d\n", this, count);
}
// Manually close any fd paths for which we have not yet opened their zip (which
// will take ownership of the fd and close it when done).
for (size_t i=0; i<mAssetPaths.size(); i++) {
+ ALOGV("Cleaning path #%d: fd=%d, zip=%p", (int)i, mAssetPaths[i].rawFd,
+ mAssetPaths[i].zip.get());
if (mAssetPaths[i].rawFd >= 0 && mAssetPaths[i].zip == NULL) {
close(mAssetPaths[i].rawFd);
}
@@ -202,7 +206,7 @@ bool AssetManager::addAssetPath(
ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
ap.isSystemAsset = isSystemAsset;
- mAssetPaths.add(ap);
+ ssize_t apPos = mAssetPaths.add(ap);
// new paths are always added at the end
if (cookie) {
@@ -219,7 +223,7 @@ bool AssetManager::addAssetPath(
#endif
if (mResources != NULL) {
- appendPathToResTable(ap, appAsLib);
+ appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib);
}
return true;
@@ -304,7 +308,7 @@ bool AssetManager::addAssetFd(
ALOGV("In %p Asset fd %d name: %s", this, fd, ap.path.string());
- mAssetPaths.add(ap);
+ ssize_t apPos = mAssetPaths.add(ap);
// new paths are always added at the end
if (cookie) {
@@ -312,7 +316,7 @@ bool AssetManager::addAssetFd(
}
if (mResources != NULL) {
- appendPathToResTable(ap, appAsLib);
+ appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib);
}
return true;
@@ -442,7 +446,8 @@ Asset* AssetManager::open(const char* fileName, AccessMode mode)
i--;
ALOGV("Looking for asset '%s' in '%s'\n",
assetName.string(), mAssetPaths.itemAt(i).path.string());
- Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i));
+ Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode,
+ mAssetPaths.editItemAt(i));
if (pAsset != NULL) {
return pAsset != kExcludedAsset ? pAsset : NULL;
}
@@ -471,7 +476,7 @@ Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode, int32_t
i--;
ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string());
Asset* pAsset = openNonAssetInPathLocked(
- fileName, mode, mAssetPaths.itemAt(i));
+ fileName, mode, mAssetPaths.editItemAt(i));
if (pAsset != NULL) {
if (outCookie != NULL) *outCookie = static_cast<int32_t>(i + 1);
return pAsset != kExcludedAsset ? pAsset : NULL;
@@ -493,7 +498,7 @@ Asset* AssetManager::openNonAsset(const int32_t cookie, const char* fileName, Ac
ALOGV("Looking for non-asset '%s' in '%s'\n", fileName,
mAssetPaths.itemAt(which).path.string());
Asset* pAsset = openNonAssetInPathLocked(
- fileName, mode, mAssetPaths.itemAt(which));
+ fileName, mode, mAssetPaths.editItemAt(which));
if (pAsset != NULL) {
return pAsset != kExcludedAsset ? pAsset : NULL;
}
@@ -527,7 +532,7 @@ FileType AssetManager::getFileType(const char* fileName)
}
}
-bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const {
+bool AssetManager::appendPathToResTable(asset_path& ap, bool appAsLib) const {
// skip those ap's that correspond to system overlays
if (ap.isSystemOverlay) {
return true;
@@ -645,7 +650,8 @@ const ResTable* AssetManager::getResTable(bool required) const
bool onlyEmptyResources = true;
const size_t N = mAssetPaths.size();
for (size_t i=0; i<N; i++) {
- bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
+ bool empty = appendPathToResTable(
+ const_cast<AssetManager*>(this)->mAssetPaths.editItemAt(i));
onlyEmptyResources = onlyEmptyResources && empty;
}
@@ -770,7 +776,7 @@ void AssetManager::getLocales(Vector<String8>* locales, bool includeSystemLocale
* be used.
*/
Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
- const asset_path& ap)
+ asset_path& ap)
{
Asset* pAsset = NULL;
@@ -851,17 +857,19 @@ String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* roo
* Return a pointer to one of our open Zip archives. Returns NULL if no
* matching Zip file exists.
*/
-ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap)
+ZipFileRO* AssetManager::getZipFileLocked(asset_path& ap)
{
- ALOGV("getZipFileLocked() in %p\n", this);
+ ALOGV("getZipFileLocked() in %p: ap=%p zip=%p", this, &ap, ap.zip.get());
if (ap.zip != NULL) {
return ap.zip->getZip();
}
if (ap.rawFd < 0) {
+ ALOGV("getZipFileLocked: Creating new zip from path %s", ap.path.string());
ap.zip = mZipSet.getSharedZip(ap.path);
} else {
+ ALOGV("getZipFileLocked: Creating new zip from fd %d", ap.rawFd);
ap.zip = SharedZip::create(ap.rawFd, ap.path);
}
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index 4254614c8448..ecc5dc1ad331 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -222,16 +222,16 @@ private:
bool isSystemOverlay;
bool isSystemAsset;
bool assumeOwnership;
- mutable sp<SharedZip> zip;
+ sp<SharedZip> zip;
};
Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
- const asset_path& path);
+ asset_path& path);
String8 createPathNameLocked(const asset_path& path, const char* rootDir);
String8 createZipSourceNameLocked(const String8& zipFileName,
const String8& dirName, const String8& fileName);
- ZipFileRO* getZipFileLocked(const asset_path& path);
+ ZipFileRO* getZipFileLocked(asset_path& path);
Asset* openAssetFromFileLocked(const String8& fileName, AccessMode mode);
Asset* openAssetFromZipLocked(const ZipFileRO* pZipFile,
const ZipEntryRO entry, AccessMode mode, const String8& entryName);
@@ -247,7 +247,7 @@ private:
const ResTable* getResTable(bool required = true) const;
void setLocaleLocked(const char* locale);
void updateResourceParamsLocked() const;
- bool appendPathToResTable(const asset_path& ap, bool appAsLib=false) const;
+ bool appendPathToResTable(asset_path& ap, bool appAsLib=false) const;
Asset* openIdmapLocked(const struct asset_path& ap) const;
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index c475e122833a..306ed83c426b 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -721,14 +721,16 @@ public final class MediaFormat {
/**
* A key for boolean DEFAULT behavior for the track. The track with DEFAULT=true is
* selected in the absence of a specific user choice.
- * This is currently only used for subtitle tracks, when the user selected
- * 'Default' for the captioning locale.
+ * This is currently used in two scenarios:
+ * 1) for subtitle tracks, when the user selected 'Default' for the captioning locale.
+ * 2) for a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track, indicating the image is the
+ * primary item in the file.
+
* The associated value is an integer, where non-0 means TRUE. This is an optional
* field; if not specified, DEFAULT is considered to be FALSE.
*/
public static final String KEY_IS_DEFAULT = "is-default";
-
/**
* A key for the FORCED field for subtitle tracks. True if it is a
* forced subtitle track. Forced subtitle tracks are essential for the
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index 91e57ee073b0..02c71b283b21 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -258,12 +258,18 @@ final public class MediaMuxer {
* in include/media/stagefright/MediaMuxer.h!
*/
private OutputFormat() {}
+ /** @hide */
+ public static final int MUXER_OUTPUT_FIRST = 0;
/** MPEG4 media file format*/
- public static final int MUXER_OUTPUT_MPEG_4 = 0;
+ public static final int MUXER_OUTPUT_MPEG_4 = MUXER_OUTPUT_FIRST;
/** WEBM media file format*/
- public static final int MUXER_OUTPUT_WEBM = 1;
+ public static final int MUXER_OUTPUT_WEBM = MUXER_OUTPUT_FIRST + 1;
/** 3GPP media file format*/
- public static final int MUXER_OUTPUT_3GPP = 2;
+ public static final int MUXER_OUTPUT_3GPP = MUXER_OUTPUT_FIRST + 2;
+ /** HEIF media file format*/
+ public static final int MUXER_OUTPUT_HEIF = MUXER_OUTPUT_FIRST + 3;
+ /** @hide */
+ public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_HEIF;
};
/** @hide */
@@ -271,6 +277,7 @@ final public class MediaMuxer {
OutputFormat.MUXER_OUTPUT_MPEG_4,
OutputFormat.MUXER_OUTPUT_WEBM,
OutputFormat.MUXER_OUTPUT_3GPP,
+ OutputFormat.MUXER_OUTPUT_HEIF,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Format {}
@@ -347,8 +354,7 @@ final public class MediaMuxer {
}
private void setUpMediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException {
- if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 && format != OutputFormat.MUXER_OUTPUT_WEBM
- && format != OutputFormat.MUXER_OUTPUT_3GPP) {
+ if (format < OutputFormat.MUXER_OUTPUT_FIRST || format > OutputFormat.MUXER_OUTPUT_LAST) {
throw new IllegalArgumentException("format: " + format + " is invalid");
}
mNativeObject = nativeSetup(fd, format);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 104a77def44e..cb3d59c4dff6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -94,7 +94,7 @@ public class KeyguardSliceView extends LinearLayout {
private void showSlice(Slice slice) {
// Items will be wrapped into an action when they have tap targets.
- SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+ SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION);
if (actionSlice != null) {
mSlice = actionSlice.getSlice();
mSliceAction = actionSlice.getAction();
@@ -108,7 +108,7 @@ public class KeyguardSliceView extends LinearLayout {
return;
}
- SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null);
+ SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null);
if (title == null) {
mTitle.setVisibility(GONE);
} else {
@@ -116,7 +116,7 @@ public class KeyguardSliceView extends LinearLayout {
mTitle.setText(title.getText());
}
- SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE);
+ SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE);
if (text == null) {
mText.setVisibility(GONE);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 03018f7d8778..6ddc76b595b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -84,7 +84,7 @@ public class KeyguardSliceProvider extends SliceProvider {
@Override
public Slice onBindSlice(Uri sliceUri) {
- return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+ return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 700c01a55ad6..0f498bcfbd58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -910,6 +910,7 @@ public class UserSwitcherController {
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.guest_exit_guest_dialog_remove), this);
+ SystemUIDialog.setWindowOnTop(this);
setCanceledOnTouchOutside(false);
mGuestId = guestId;
mTargetId = targetId;
@@ -937,6 +938,7 @@ public class UserSwitcherController {
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(android.R.string.ok), this);
+ SystemUIDialog.setWindowOnTop(this);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 4437aaca521d..4eae3426f815 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -63,7 +63,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase {
@Test
public void returnsValidSlice() {
Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
- SliceItem text = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+ SliceItem text = SliceQuery.find(slice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE,
null /* nonHints */);
Assert.assertNotNull("Slice must provide a title.", text);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
index 5db6f7da8102..5867006d6c18 100644
--- a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
@@ -21,7 +21,11 @@ import android.app.StatusBarManager;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Binder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
+import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -72,6 +76,9 @@ public class GlobalActionPerformer {
case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: {
return toggleSplitScreen();
}
+ case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: {
+ return lockScreen();
+ }
}
return false;
} finally {
@@ -153,4 +160,11 @@ public class GlobalActionPerformer {
}
return true;
}
+
+ private boolean lockScreen() {
+ mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(),
+ PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0);
+ mWindowManagerService.lockNow();
+ return true;
+ }
}
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index a45a4f0c35d3..dd29a04e5f54 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -48,7 +48,6 @@ import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
import android.app.backup.ISelectBackupTransportCallback;
-import android.app.backup.SelectBackupTransportCallback;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -103,6 +102,7 @@ import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.internal.BackupHandler;
import com.android.server.backup.internal.BackupRequest;
import com.android.server.backup.internal.ClearDataObserver;
+import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
import com.android.server.backup.internal.PerformInitializeTask;
import com.android.server.backup.internal.ProvisionedObserver;
@@ -117,6 +117,7 @@ import com.android.server.backup.params.ClearRetryParams;
import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.restore.ActiveRestoreSession;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.TransportClient;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
@@ -1585,8 +1586,27 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
}
+ // We're using pieces of the new binding on-demand infra-structure and the old always-bound
+ // infra-structure below this comment. The TransportManager.getCurrentTransportClient() line
+ // is using the new one and TransportManager.getCurrentTransportBinder() is using the old.
+ // This is weird but there is a reason.
+ // This is the natural place to put TransportManager.getCurrentTransportClient() because of
+ // the null handling below that should be the same for TransportClient.
+ // TransportClient.connect() would return a IBackupTransport for us (instead of using the
+ // old infra), but it may block and we don't want this in this thread.
+ // The only usage of transport in this method is for transport.transportDirName(). When the
+ // push-from-transport part of binding on-demand is in place we will replace the calls for
+ // IBackupTransport.transportDirName() with calls for
+ // TransportManager.transportDirName(transportName) or similar. So we'll leave the old piece
+ // here until we implement that.
+ // TODO(brufino): Remove always-bound code mTransportManager.getCurrentTransportBinder()
+ TransportClient transportClient =
+ mTransportManager.getCurrentTransportClient("BMS.requestBackup()");
IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
- if (transport == null) {
+ if (transportClient == null || transport == null) {
+ if (transportClient != null) {
+ mTransportManager.disposeOfTransportClient(transportClient, "BMS.requestBackup()");
+ }
BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL,
@@ -1594,6 +1614,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
return BackupManager.ERROR_TRANSPORT_ABORTED;
}
+ OnTaskFinishedListener listener =
+ caller -> mTransportManager.disposeOfTransportClient(transportClient, caller);
+
ArrayList<String> fullBackupList = new ArrayList<>();
ArrayList<String> kvBackupList = new ArrayList<>();
for (String packageName : packages) {
@@ -1640,8 +1663,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0;
Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
- msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer,
- monitor, true, nonIncrementalBackup);
+ msg.obj = new BackupParams(transportClient, dirName, kvBackupList, fullBackupList, observer,
+ monitor, listener, true, nonIncrementalBackup);
mBackupHandler.sendMessage(msg);
return BackupManager.SUCCESS;
}
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 7a0173f669af..a2b5cb883b8a 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -16,8 +16,9 @@
package com.android.server.backup;
+import android.annotation.Nullable;
import android.app.backup.BackupManager;
-import android.app.backup.SelectBackupTransportCallback;
+import android.app.backup.BackupTransport;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -44,9 +45,11 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportClientManager;
+import com.android.server.backup.transport.TransportConnectionListener;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -60,8 +63,7 @@ public class TransportManager {
private static final String TAG = "BackupTransportManager";
@VisibleForTesting
- /* package */ static final String SERVICE_ACTION_TRANSPORT_HOST =
- "android.backup.TRANSPORT_HOST";
+ public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
@@ -72,6 +74,7 @@ public class TransportManager {
private final PackageManager mPackageManager;
private final Set<ComponentName> mTransportWhitelist;
private final Handler mHandler;
+ private final TransportClientManager mTransportClientManager;
/**
* This listener is called after we bind to any transport. If it returns true, this is a valid
@@ -95,6 +98,10 @@ public class TransportManager {
@GuardedBy("mTransportLock")
private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
+ /** Names of transports we've bound to at least once */
+ @GuardedBy("mTransportLock")
+ private final Map<String, ComponentName> mTransportsByName = new ArrayMap<>();
+
/**
* Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}.
*/
@@ -123,6 +130,7 @@ public class TransportManager {
mCurrentTransportName = defaultTransport;
mTransportBoundListener = listener;
mHandler = new RebindOnTimeoutHandler(looper);
+ mTransportClientManager = new TransportClientManager(context);
}
void onPackageAdded(String packageName) {
@@ -204,6 +212,67 @@ public class TransportManager {
return null;
}
+ /**
+ * Returns the transport name associated with {@param transportClient} or {@code null} if not
+ * found.
+ */
+ @Nullable
+ public String getTransportName(TransportClient transportClient) {
+ ComponentName transportComponent = transportClient.getTransportComponent();
+ synchronized (mTransportLock) {
+ for (Map.Entry<String, ComponentName> transportEntry : mTransportsByName.entrySet()) {
+ if (transportEntry.getValue().equals(transportComponent)) {
+ return transportEntry.getKey();
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Returns a {@link TransportClient} for {@param transportName} or {@code null} if not found.
+ *
+ * @param transportName The name of the transport as returned by {@link BackupTransport#name()}.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient} or null if not found.
+ */
+ @Nullable
+ public TransportClient getTransportClient(String transportName, String caller) {
+ ComponentName transportComponent = mTransportsByName.get(transportName);
+ if (transportComponent == null) {
+ Slog.w(TAG, "Transport " + transportName + " not registered");
+ return null;
+ }
+ return mTransportClientManager.getTransportClient(transportComponent, caller);
+ }
+
+ /**
+ * Returns a {@link TransportClient} for the current transport or null if not found.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient} or null if not found.
+ */
+ @Nullable
+ public TransportClient getCurrentTransportClient(String caller) {
+ return getTransportClient(mCurrentTransportName, caller);
+ }
+
+ /**
+ * Disposes of the {@link TransportClient}.
+ *
+ * @param transportClient The {@link TransportClient} to be disposed of.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+ mTransportClientManager.disposeOfTransportClient(transportClient, caller);
+ }
+
String[] getBoundTransportNames() {
synchronized (mTransportLock) {
return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]);
@@ -374,6 +443,7 @@ public class TransportManager {
String componentShortString = component.flattenToShortString().intern();
if (success) {
Slog.d(TAG, "Bound to transport: " + componentShortString);
+ mTransportsByName.put(mTransportName, component);
mBoundTransports.put(mTransportName, component);
for (TransportReadyCallback listener : mListeners) {
listener.onSuccess(mTransportName);
@@ -528,7 +598,7 @@ public class TransportManager {
// These only exists to make it testable with Robolectric, which is not updated to API level 24
// yet.
// TODO: Get rid of this once Robolectric is updated.
- private static UserHandle createSystemUserHandle() {
+ public static UserHandle createSystemUserHandle() {
return new UserHandle(UserHandle.USER_SYSTEM);
}
}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 8f823004d993..9011b95cf614 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -38,6 +38,8 @@ import com.android.server.EventLogTags;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.TransportManager;
import com.android.server.backup.fullbackup.PerformAdbBackupTask;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.params.AdbBackupParams;
@@ -51,10 +53,8 @@ import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.restore.PerformAdbRestoreTask;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
-import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
/**
* Asynchronous backup/restore handler thread.
@@ -81,7 +81,7 @@ public class BackupHandler extends Handler {
public static final int MSG_BACKUP_RESTORE_STEP = 20;
public static final int MSG_OP_COMPLETE = 21;
- private RefactoredBackupManagerService backupManagerService;
+ private final RefactoredBackupManagerService backupManagerService;
public BackupHandler(
RefactoredBackupManagerService backupManagerService, Looper looper) {
@@ -91,13 +91,23 @@ public class BackupHandler extends Handler {
public void handleMessage(Message msg) {
+ TransportManager transportManager = backupManagerService.getTransportManager();
switch (msg.what) {
case MSG_RUN_BACKUP: {
backupManagerService.setLastBackupPass(System.currentTimeMillis());
+ String callerLogString = "BH/MSG_RUN_BACKUP";
+ TransportClient transportClient =
+ transportManager.getCurrentTransportClient(callerLogString);
IBackupTransport transport =
- backupManagerService.getTransportManager().getCurrentTransportBinder();
+ transportClient != null
+ ? transportClient.connect(callerLogString)
+ : null;
if (transport == null) {
+ if (transportClient != null) {
+ transportManager
+ .disposeOfTransportClient(transportClient, callerLogString);
+ }
Slog.v(TAG, "Backup requested but no transport available");
synchronized (backupManagerService.getQueueLock()) {
backupManagerService.setBackupRunning(false);
@@ -138,9 +148,13 @@ public class BackupHandler extends Handler {
// Spin up a backup state sequence and set it running
try {
String dirName = transport.transportDirName();
+ OnTaskFinishedListener listener =
+ caller ->
+ transportManager
+ .disposeOfTransportClient(transportClient, caller);
PerformBackupTask pbt = new PerformBackupTask(
- backupManagerService, transport, dirName, queue,
- oldJournal, null, null, Collections.<String>emptyList(), false,
+ backupManagerService, transportClient, dirName, queue,
+ oldJournal, null, null, listener, Collections.emptyList(), false,
false /* nonIncremental */);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
@@ -157,6 +171,7 @@ public class BackupHandler extends Handler {
}
if (!staged) {
+ transportManager.disposeOfTransportClient(transportClient, callerLogString);
// if we didn't actually hand off the wakelock, rewind until next time
synchronized (backupManagerService.getQueueLock()) {
backupManagerService.setBackupRunning(false);
@@ -382,9 +397,9 @@ public class BackupHandler extends Handler {
PerformBackupTask pbt = new PerformBackupTask(
backupManagerService,
- params.transport, params.dirName,
- kvQueue, null, params.observer, params.monitor, params.fullPackages, true,
- params.nonIncrementalBackup);
+ params.transportClient, params.dirName,
+ kvQueue, null, params.observer, params.monitor, params.listener,
+ params.fullPackages, true, params.nonIncrementalBackup);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
break;
diff --git a/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java b/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java
new file mode 100644
index 000000000000..e417f06c8a05
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportConnectionListener;
+
+/** Listener to be called when a task finishes, successfully or not. */
+public interface OnTaskFinishedListener {
+ OnTaskFinishedListener NOP = caller -> {};
+
+ /**
+ * Called when a task finishes, successfully or not.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ void onFinished(String caller);
+}
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index c0caa557b4ae..1fa215a1664a 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -63,6 +63,8 @@ import com.android.server.backup.KeyValueBackupJob;
import com.android.server.backup.PackageManagerBackupAgent;
import com.android.server.backup.RefactoredBackupManagerService;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportUtils;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
@@ -112,7 +114,6 @@ public class PerformBackupTask implements BackupRestoreTask {
private RefactoredBackupManagerService backupManagerService;
private final Object mCancelLock = new Object();
- IBackupTransport mTransport;
ArrayList<BackupRequest> mQueue;
ArrayList<BackupRequest> mOriginalQueue;
File mStateDir;
@@ -122,6 +123,8 @@ public class PerformBackupTask implements BackupRestoreTask {
IBackupObserver mObserver;
IBackupManagerMonitor mMonitor;
+ private final TransportClient mTransportClient;
+ private final OnTaskFinishedListener mListener;
private final PerformFullTransportBackupTask mFullBackupTask;
private final int mCurrentOpToken;
private volatile int mEphemeralOpToken;
@@ -143,17 +146,19 @@ public class PerformBackupTask implements BackupRestoreTask {
private volatile boolean mCancelAll;
public PerformBackupTask(RefactoredBackupManagerService backupManagerService,
- IBackupTransport transport, String dirName,
+ TransportClient transportClient, String dirName,
ArrayList<BackupRequest> queue, @Nullable DataChangedJournal journal,
IBackupObserver observer, IBackupManagerMonitor monitor,
- List<String> pendingFullBackups, boolean userInitiated, boolean nonIncremental) {
+ @Nullable OnTaskFinishedListener listener, List<String> pendingFullBackups,
+ boolean userInitiated, boolean nonIncremental) {
this.backupManagerService = backupManagerService;
- mTransport = transport;
+ mTransportClient = transportClient;
mOriginalQueue = queue;
mQueue = new ArrayList<>();
mJournal = journal;
mObserver = observer;
mMonitor = monitor;
+ mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP;
mPendingFullBackups = pendingFullBackups;
mUserInitiated = userInitiated;
mNonIncremental = nonIncremental;
@@ -289,10 +294,10 @@ public class PerformBackupTask implements BackupRestoreTask {
if (DEBUG) {
Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
}
-
File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
try {
- final String transportName = mTransport.transportDirName();
+ IBackupTransport transport = mTransportClient.connectOrThrow("PBT.beginBackup()");
+ final String transportName = transport.transportDirName();
EventLog.writeEvent(EventLogTags.BACKUP_START, transportName);
// If we haven't stored package manager metadata yet, we must init the transport.
@@ -300,7 +305,7 @@ public class PerformBackupTask implements BackupRestoreTask {
Slog.i(TAG, "Initializing (wiping) backup state and transport storage");
backupManagerService.addBackupTrace("initializing transport " + transportName);
backupManagerService.resetBackupState(mStateDir); // Just to make sure.
- mStatus = mTransport.initializeDevice();
+ mStatus = transport.initializeDevice();
backupManagerService.addBackupTrace("transport.initializeDevice() == " + mStatus);
if (mStatus == BackupTransport.TRANSPORT_OK) {
@@ -324,7 +329,7 @@ public class PerformBackupTask implements BackupRestoreTask {
PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent();
mStatus = invokeAgentForBackup(
PACKAGE_MANAGER_SENTINEL,
- IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
+ IBackupAgent.Stub.asInterface(pmAgent.onBind()));
backupManagerService.addBackupTrace("PMBA invoke: " + mStatus);
// Because the PMBA is a local instance, it has already executed its
@@ -445,7 +450,7 @@ public class PerformBackupTask implements BackupRestoreTask {
backupManagerService.addBackupTrace("agent bound; a? = " + (agent != null));
if (agent != null) {
mAgentBinder = agent;
- mStatus = invokeAgentForBackup(request.packageName, agent, mTransport);
+ mStatus = invokeAgentForBackup(request.packageName, agent);
// at this point we'll either get a completion callback from the
// agent, or a timeout message on the main handler. either way, we're
// done here as long as we're successful so far.
@@ -526,11 +531,14 @@ public class PerformBackupTask implements BackupRestoreTask {
// If everything actually went through and this is the first time we've
// done a backup, we can now record what the current backup dataset token
// is.
+ String callerLogString = "PBT.finalizeBackup()";
if ((backupManagerService.getCurrentToken() == 0) && (mStatus
== BackupTransport.TRANSPORT_OK)) {
backupManagerService.addBackupTrace("success; recording token");
try {
- backupManagerService.setCurrentToken(mTransport.getCurrentRestoreSet());
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow(callerLogString);
+ backupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
backupManagerService.writeRestoreTokens();
} catch (Exception e) {
// nothing for it at this point, unfortunately, but this will be
@@ -553,13 +561,13 @@ public class PerformBackupTask implements BackupRestoreTask {
backupManagerService.addBackupTrace("init required; rerunning");
try {
final String name = backupManagerService.getTransportManager().getTransportName(
- mTransport);
+ mTransportClient);
if (name != null) {
backupManagerService.getPendingInits().add(name);
} else {
if (DEBUG) {
- Slog.w(TAG, "Couldn't find name of transport " + mTransport
- + " for init");
+ Slog.w(TAG, "Couldn't find name of transport "
+ + mTransportClient.getTransportComponent() + " for init");
}
}
} catch (Exception e) {
@@ -577,17 +585,21 @@ public class PerformBackupTask implements BackupRestoreTask {
if (!mCancelAll && mStatus == BackupTransport.TRANSPORT_OK &&
mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) {
+ // TODO(brufino): Move the onFinish() call to the full-backup task
+ mListener.onFinished(callerLogString);
Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
backupManagerService.getWakelock().acquire();
(new Thread(mFullBackupTask, "full-transport-requested")).start();
} else if (mCancelAll) {
+ mListener.onFinished(callerLogString);
if (mFullBackupTask != null) {
mFullBackupTask.unregisterTask();
}
BackupObserverUtils.sendBackupFinished(mObserver,
BackupManager.ERROR_BACKUP_CANCELLED);
} else {
+ mListener.onFinished(callerLogString);
mFullBackupTask.unregisterTask();
switch (mStatus) {
case BackupTransport.TRANSPORT_OK:
@@ -619,8 +631,7 @@ public class PerformBackupTask implements BackupRestoreTask {
// Invoke an agent's doBackup() and start a timeout message spinning on the main
// handler in case it doesn't get back to us.
- int invokeAgentForBackup(String packageName, IBackupAgent agent,
- IBackupTransport transport) {
+ int invokeAgentForBackup(String packageName, IBackupAgent agent) {
if (DEBUG) {
Slog.d(TAG, "invokeAgentForBackup on " + packageName);
}
@@ -671,7 +682,10 @@ public class PerformBackupTask implements BackupRestoreTask {
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
- final long quota = mTransport.getBackupQuota(packageName, false /* isFullBackup */);
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow("PBT.invokeAgentForBackup()");
+
+ final long quota = transport.getBackupQuota(packageName, false /* isFullBackup */);
callingAgent = true;
// Initiate the target's backup pass
@@ -888,10 +902,12 @@ public class PerformBackupTask implements BackupRestoreTask {
clearAgentState();
backupManagerService.addBackupTrace("operation complete");
+ IBackupTransport transport = mTransportClient.connect("PBT.operationComplete()");
ParcelFileDescriptor backupData = null;
mStatus = BackupTransport.TRANSPORT_OK;
long size = 0;
try {
+ TransportUtils.checkTransport(transport);
size = mBackupDataName.length();
if (size > 0) {
if (mStatus == BackupTransport.TRANSPORT_OK) {
@@ -899,7 +915,7 @@ public class PerformBackupTask implements BackupRestoreTask {
ParcelFileDescriptor.MODE_READ_ONLY);
backupManagerService.addBackupTrace("sending data to transport");
int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
- mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
+ mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
}
// TODO - We call finishBackup() for each application backed up, because
@@ -910,7 +926,7 @@ public class PerformBackupTask implements BackupRestoreTask {
backupManagerService.addBackupTrace("data delivered: " + mStatus);
if (mStatus == BackupTransport.TRANSPORT_OK) {
backupManagerService.addBackupTrace("finishing op on transport");
- mStatus = mTransport.finishBackup();
+ mStatus = transport.finishBackup();
backupManagerService.addBackupTrace("finished: " + mStatus);
} else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
backupManagerService.addBackupTrace("transport rejected package");
@@ -981,8 +997,8 @@ public class PerformBackupTask implements BackupRestoreTask {
}
if (mAgentBinder != null) {
try {
- long quota = mTransport.getBackupQuota(mCurrentPackage.packageName,
- false);
+ TransportUtils.checkTransport(transport);
+ long quota = transport.getBackupQuota(mCurrentPackage.packageName, false);
mAgentBinder.doQuotaExceeded(size, quota);
} catch (Exception e) {
Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
@@ -1052,7 +1068,9 @@ public class PerformBackupTask implements BackupRestoreTask {
// by way of retry/backoff time.
long delay;
try {
- delay = mTransport.requestBackupTime();
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow("PBT.revertAndEndBackup()");
+ delay = transport.requestBackupTime();
} catch (Exception e) {
Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e.getMessage());
delay = 0; // use the scheduler's default
diff --git a/services/backup/java/com/android/server/backup/params/BackupParams.java b/services/backup/java/com/android/server/backup/params/BackupParams.java
index 4fd7ddbc28f7..2ba8ec16a45c 100644
--- a/services/backup/java/com/android/server/backup/params/BackupParams.java
+++ b/services/backup/java/com/android/server/backup/params/BackupParams.java
@@ -19,30 +19,34 @@ package com.android.server.backup.params;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
-import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.transport.TransportClient;
import java.util.ArrayList;
public class BackupParams {
- public IBackupTransport transport;
+ public TransportClient transportClient;
public String dirName;
public ArrayList<String> kvPackages;
public ArrayList<String> fullPackages;
public IBackupObserver observer;
public IBackupManagerMonitor monitor;
+ public OnTaskFinishedListener listener;
public boolean userInitiated;
public boolean nonIncrementalBackup;
- public BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages,
- ArrayList<String> fullPackages, IBackupObserver observer,
- IBackupManagerMonitor monitor, boolean userInitiated, boolean nonIncrementalBackup) {
- this.transport = transport;
+ public BackupParams(TransportClient transportClient, String dirName,
+ ArrayList<String> kvPackages, ArrayList<String> fullPackages, IBackupObserver observer,
+ IBackupManagerMonitor monitor, OnTaskFinishedListener listener, boolean userInitiated,
+ boolean nonIncrementalBackup) {
+ this.transportClient = transportClient;
this.dirName = dirName;
this.kvPackages = kvPackages;
this.fullPackages = fullPackages;
this.observer = observer;
this.monitor = monitor;
+ this.listener = listener;
this.userInitiated = userInitiated;
this.nonIncrementalBackup = nonIncrementalBackup;
}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java
new file mode 100644
index 000000000000..9c39729f0dda
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.backup.transport;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.backup.IBackupTransport;
+import com.android.internal.util.Preconditions;
+import com.android.server.backup.TransportManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * A {@link TransportClient} manages the connection to an {@link IBackupTransport} service, obtained
+ * via the {@param bindIntent} parameter provided in the constructor. A {@link TransportClient} is
+ * responsible for only one connection to the transport service, not more.
+ *
+ * <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can
+ * call either {@link #connect(String)}, if you can block your thread, or {@link
+ * #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link
+ * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport.
+ * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly
+ * via {@link TransportManager#disposeOfTransportClient(TransportClient, String)}.
+ *
+ * <p>DO NOT forget to unbind otherwise there will be dangling connections floating around.
+ *
+ * <p>This class is thread-safe.
+ *
+ * @see TransportManager
+ */
+public class TransportClient {
+ private static final String TAG = "TransportClient";
+
+ private final Context mContext;
+ private final Intent mBindIntent;
+ private final String mIdentifier;
+ private final ComponentName mTransportComponent;
+ private final Handler mListenerHandler;
+ private final String mPrefixForLog;
+ private final Object mStateLock = new Object();
+
+ @GuardedBy("mStateLock")
+ private final Map<TransportConnectionListener, String> mListeners = new ArrayMap<>();
+
+ @GuardedBy("mStateLock")
+ @State
+ private int mState = State.IDLE;
+
+ @GuardedBy("mStateLock")
+ private volatile IBackupTransport mTransport;
+
+ TransportClient(
+ Context context,
+ Intent bindIntent,
+ ComponentName transportComponent,
+ String identifier) {
+ this(context, bindIntent, transportComponent, identifier, Handler.getMain());
+ }
+
+ @VisibleForTesting
+ TransportClient(
+ Context context,
+ Intent bindIntent,
+ ComponentName transportComponent,
+ String identifier,
+ Handler listenerHandler) {
+ mContext = context;
+ mTransportComponent = transportComponent;
+ mBindIntent = bindIntent;
+ mIdentifier = identifier;
+ mListenerHandler = listenerHandler;
+
+ // For logging
+ String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", "");
+ mPrefixForLog = classNameForLog + "#" + mIdentifier + ": ";
+ }
+
+ public ComponentName getTransportComponent() {
+ return mTransportComponent;
+ }
+
+ // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one
+ // of these calls, if a binding happen again the new service can be a different instance. Since
+ // transports are stateful, we don't want a new instance responding for an old instance's state.
+ private ServiceConnection mConnection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder binder) {
+ IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ if (mState != State.UNUSABLE) {
+ log(Log.DEBUG, "Transport connected");
+ setStateLocked(State.CONNECTED, transport);
+ notifyListenersAndClearLocked(transport);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ synchronized (mStateLock) {
+ log(Log.ERROR, "Service disconnected: client UNUSABLE");
+ setStateLocked(State.UNUSABLE, null);
+ // After unbindService() no calls back to mConnection
+ mContext.unbindService(this);
+ }
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ log(Log.ERROR, "Binding died: client UNUSABLE");
+ // After unbindService() no calls back to mConnection
+ switch (mState) {
+ case State.UNUSABLE:
+ break;
+ case State.IDLE:
+ log(Log.ERROR, "Unexpected state transition IDLE => UNUSABLE");
+ setStateLocked(State.UNUSABLE, null);
+ break;
+ case State.BOUND_AND_CONNECTING:
+ setStateLocked(State.UNUSABLE, null);
+ mContext.unbindService(this);
+ notifyListenersAndClearLocked(null);
+ break;
+ case State.CONNECTED:
+ setStateLocked(State.UNUSABLE, null);
+ mContext.unbindService(this);
+ break;
+ }
+ }
+ }
+ };
+
+ /**
+ * Attempts to connect to the transport (if needed).
+ *
+ * <p>Note that being bound is not the same as connected. To be connected you also need to be
+ * bound. You go from nothing to bound, then to bound and connected. To have a usable transport
+ * binder instance you need to be connected. This method will attempt to connect and return an
+ * usable transport binder regardless of the state of the object, it may already be connected,
+ * or bound but not connected, not bound at all or even unusable.
+ *
+ * <p>So, a {@link Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)} (or
+ * one of its variants) can be called or not depending on the inner state. However, it won't be
+ * called again if we're already bound. For example, if one was already requested but the
+ * framework has not yet returned (meaning we're bound but still trying to connect) it won't
+ * trigger another one, just piggyback on the original request.
+ *
+ * <p>It's guaranteed that you are going to get a call back to {@param listener} after this
+ * call. However, the {@param IBackupTransport} parameter, the transport binder, is not
+ * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can
+ * throw {@link DeadObjectException}s on method calls. You should check for both in your code.
+ * The reasons for a null transport binder are:
+ *
+ * <ul>
+ * <li>Some code called {@link #unbind(String)} before you got a callback.
+ * <li>The framework had already called {@link
+ * ServiceConnection#onServiceDisconnected(ComponentName)} or {@link
+ * ServiceConnection#onBindingDied(ComponentName)} on this object's connection before.
+ * Check the documentation of those methods for when that happens.
+ * <li>The framework returns false for {@link Context#bindServiceAsUser(Intent,
+ * ServiceConnection, int, UserHandle)} (or one of its variants). Check documentation for
+ * when this happens.
+ * </ul>
+ *
+ * For unusable transport binders check {@link DeadObjectException}.
+ *
+ * @param listener The listener that will be called with the (possibly null or unusable) {@link
+ * IBackupTransport} instance and this {@link TransportClient} object.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. This
+ * should be a human-readable short string that is easily identifiable in the logs. Ideally
+ * TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very
+ * descriptive like MyHandler.handleMessage() you should put something that someone reading
+ * the code would understand, like MyHandler/MSG_FOO.
+ * @see #connect(String)
+ * @see DeadObjectException
+ * @see ServiceConnection#onServiceConnected(ComponentName, IBinder)
+ * @see ServiceConnection#onServiceDisconnected(ComponentName)
+ * @see Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)
+ */
+ public void connectAsync(TransportConnectionListener listener, String caller) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ switch (mState) {
+ case State.UNUSABLE:
+ log(Log.DEBUG, caller, "Async connect: UNUSABLE client");
+ notifyListener(listener, null, caller);
+ break;
+ case State.IDLE:
+ boolean hasBound =
+ mContext.bindServiceAsUser(
+ mBindIntent,
+ mConnection,
+ Context.BIND_AUTO_CREATE,
+ TransportManager.createSystemUserHandle());
+ if (hasBound) {
+ // We don't need to set a time-out because we are guaranteed to get a call
+ // back in ServiceConnection, either an onServiceConnected() or
+ // onBindingDied().
+ log(Log.DEBUG, caller, "Async connect: service bound, connecting");
+ setStateLocked(State.BOUND_AND_CONNECTING, null);
+ mListeners.put(listener, caller);
+ } else {
+ log(Log.ERROR, "Async connect: bindService returned false");
+ // mState remains State.IDLE
+ mContext.unbindService(mConnection);
+ notifyListener(listener, null, caller);
+ }
+ break;
+ case State.BOUND_AND_CONNECTING:
+ log(Log.DEBUG, caller, "Async connect: already connecting, adding listener");
+ mListeners.put(listener, caller);
+ break;
+ case State.CONNECTED:
+ log(Log.DEBUG, caller, "Async connect: reusing transport");
+ notifyListener(listener, mTransport, caller);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Removes the transport binding.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ */
+ public void unbind(String caller) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ log(Log.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")");
+ switch (mState) {
+ case State.UNUSABLE:
+ case State.IDLE:
+ break;
+ case State.BOUND_AND_CONNECTING:
+ setStateLocked(State.IDLE, null);
+ // After unbindService() no calls back to mConnection
+ mContext.unbindService(mConnection);
+ notifyListenersAndClearLocked(null);
+ break;
+ case State.CONNECTED:
+ setStateLocked(State.IDLE, null);
+ mContext.unbindService(mConnection);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Attempts to connect to the transport (if needed) and returns it.
+ *
+ * <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The
+ * same observations about state are valid here. Also, what was said about the {@link
+ * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return
+ * value of this method.
+ *
+ * <p>This is a potentially blocking operation, so be sure to call this carefully on the correct
+ * threads. You can't call this from the process main-thread (it throws an exception if you do
+ * so).
+ *
+ * <p>In most cases only the first call to this method will block, the following calls should
+ * return instantly. However, this is not guaranteed.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can
+ * still be unusable - throws {@link DeadObjectException} on method calls
+ */
+ @WorkerThread
+ @Nullable
+ public IBackupTransport connect(String caller) {
+ // If called on the main-thread this could deadlock waiting because calls to
+ // ServiceConnection are on the main-thread as well
+ Preconditions.checkState(
+ !Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread");
+
+ IBackupTransport transport = mTransport;
+ if (transport != null) {
+ log(Log.DEBUG, caller, "Sync connect: reusing transport");
+ return transport;
+ }
+
+ // If it's already UNUSABLE we return straight away, no need to go to main-thread
+ synchronized (mStateLock) {
+ if (mState == State.UNUSABLE) {
+ log(Log.DEBUG, caller, "Sync connect: UNUSABLE client");
+ return null;
+ }
+ }
+
+ CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>();
+ TransportConnectionListener requestListener =
+ (requestedTransport, transportClient) ->
+ transportFuture.complete(requestedTransport);
+
+ log(Log.DEBUG, caller, "Sync connect: calling async");
+ connectAsync(requestListener, caller);
+
+ try {
+ return transportFuture.get();
+ } catch (InterruptedException | ExecutionException e) {
+ String error = e.getClass().getSimpleName();
+ log(Log.ERROR, caller, error + " while waiting for transport: " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Tries to connect to the transport, if it fails throws {@link TransportNotAvailableException}.
+ *
+ * <p>Same as {@link #connect(String)} except it throws instead of returning null.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ * @return A {@link IBackupTransport} transport binder instance.
+ * @see #connect(String)
+ * @throws TransportNotAvailableException if connection attempt fails.
+ */
+ @WorkerThread
+ public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
+ IBackupTransport transport = connect(caller);
+ if (transport == null) {
+ log(Log.ERROR, caller, "Transport connection failed");
+ throw new TransportNotAvailableException();
+ }
+ return transport;
+ }
+
+ @Override
+ public String toString() {
+ return "TransportClient{"
+ + mTransportComponent.flattenToShortString()
+ + "#"
+ + mIdentifier
+ + "}";
+ }
+
+ private void notifyListener(
+ TransportConnectionListener listener, IBackupTransport transport, String caller) {
+ log(Log.VERBOSE, caller, "Notifying listener of transport = " + transport);
+ mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
+ }
+
+ @GuardedBy("mStateLock")
+ private void notifyListenersAndClearLocked(IBackupTransport transport) {
+ for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) {
+ TransportConnectionListener listener = entry.getKey();
+ String caller = entry.getValue();
+ notifyListener(listener, transport, caller);
+ }
+ mListeners.clear();
+ }
+
+ @GuardedBy("mStateLock")
+ private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
+ log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
+ mState = state;
+ mTransport = transport;
+ }
+
+ @GuardedBy("mStateLock")
+ private void checkStateIntegrityLocked() {
+ switch (mState) {
+ case State.UNUSABLE:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = UNUSABLE");
+ checkState(
+ mTransport == null, "Transport expected to be null when state = UNUSABLE");
+ case State.IDLE:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = IDLE");
+ checkState(mTransport == null, "Transport expected to be null when state = IDLE");
+ break;
+ case State.BOUND_AND_CONNECTING:
+ checkState(
+ mTransport == null,
+ "Transport expected to be null when state = BOUND_AND_CONNECTING");
+ break;
+ case State.CONNECTED:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = CONNECTED");
+ checkState(
+ mTransport != null,
+ "Transport expected to be non-null when state = CONNECTED");
+ break;
+ default:
+ checkState(false, "Unexpected state = " + stateToString(mState));
+ }
+ }
+
+ private void checkState(boolean assertion, String message) {
+ if (!assertion) {
+ log(Log.ERROR, message);
+ }
+ }
+
+ private String stateToString(@State int state) {
+ switch (state) {
+ case State.UNUSABLE:
+ return "UNUSABLE";
+ case State.IDLE:
+ return "IDLE";
+ case State.BOUND_AND_CONNECTING:
+ return "BOUND_AND_CONNECTING";
+ case State.CONNECTED:
+ return "CONNECTED";
+ default:
+ return "<UNKNOWN = " + state + ">";
+ }
+ }
+
+ private void log(int priority, String message) {
+ TransportUtils.log(priority, TAG, message);
+ }
+
+ private void log(int priority, String caller, String msg) {
+ TransportUtils.log(priority, TAG, mPrefixForLog, caller, msg);
+ // TODO(brufino): Log in internal list for dump
+ // CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis());
+ }
+
+ @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface State {
+ int UNUSABLE = 0;
+ int IDLE = 1;
+ int BOUND_AND_CONNECTING = 2;
+ int CONNECTED = 3;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
new file mode 100644
index 000000000000..1cbe74716b03
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.server.backup.TransportManager;
+
+/**
+ * Manages the creation and disposal of {@link TransportClient}s. The only class that should use
+ * this is {@link TransportManager}, all the other usages should go to {@link TransportManager}.
+ *
+ * <p>TODO(brufino): Implement pool of TransportClients
+ */
+public class TransportClientManager {
+ private static final String TAG = "TransportClientManager";
+
+ private final Context mContext;
+ private final Object mTransportClientsLock = new Object();
+ private int mTransportClientsCreated = 0;
+
+ public TransportClientManager(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Retrieves a {@link TransportClient} for the transport identified by {@param
+ * transportComponent}.
+ *
+ * @param transportComponent The {@link ComponentName} of the transport.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient}.
+ */
+ public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
+ Intent bindIntent =
+ new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
+ synchronized (mTransportClientsLock) {
+ TransportClient transportClient =
+ new TransportClient(
+ mContext,
+ bindIntent,
+ transportComponent,
+ Integer.toString(mTransportClientsCreated));
+ mTransportClientsCreated++;
+ TransportUtils.log(Log.DEBUG, TAG, caller, "Retrieving " + transportClient);
+ return transportClient;
+ }
+ }
+
+ /**
+ * Disposes of the {@link TransportClient}.
+ *
+ * @param transportClient The {@link TransportClient} to be disposed of.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+ TransportUtils.log(Log.DEBUG, TAG, caller, "Disposing of " + transportClient);
+ transportClient.unbind(caller);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java
new file mode 100644
index 000000000000..1ccffd01d12c
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.backup.transport;
+
+import android.annotation.Nullable;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Listener to be called by {@link TransportClient#connectAsync(TransportConnectionListener,
+ * String)}.
+ */
+public interface TransportConnectionListener {
+ /**
+ * Called when {@link TransportClient} has a transport binder available or that it decided it
+ * couldn't obtain one, in which case {@param transport} is null.
+ *
+ * @param transport A {@link IBackupTransport} transport binder or null.
+ * @param transportClient The {@link TransportClient} used to retrieve this transport binder.
+ */
+ void onTransportConnectionResult(
+ @Nullable IBackupTransport transport, TransportClient transportClient);
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java b/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java
new file mode 100644
index 000000000000..a02f03c2d1cb
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.transport;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Exception thrown when the {@link IBackupTransport} is not available. This happen when a {@link
+ * TransportClient} connection attempt fails. Check {@link
+ * TransportClient#connectAsync(TransportConnectionListener, String)} for when that happens.
+ *
+ * @see TransportClient#connectAsync(TransportConnectionListener, String)
+ */
+class TransportNotAvailableException extends Exception {
+ TransportNotAvailableException() {
+ super("Transport not available");
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportUtils.java b/services/backup/java/com/android/server/backup/transport/TransportUtils.java
new file mode 100644
index 000000000000..514717f46b9f
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.transport;
+
+import android.annotation.Nullable;
+import android.os.DeadObjectException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.backup.IBackupTransport;
+
+/** Utility methods for transport-related operations. */
+public class TransportUtils {
+
+ /**
+ * Throws {@link TransportNotAvailableException} if {@param transport} is null. The semantics is
+ * similar to a {@link DeadObjectException} coming from a dead transport binder.
+ */
+ public static IBackupTransport checkTransport(@Nullable IBackupTransport transport)
+ throws TransportNotAvailableException {
+ if (transport == null) {
+ throw new TransportNotAvailableException();
+ }
+ return transport;
+ }
+
+ static void log(int priority, String tag, String message) {
+ log(priority, tag, null, message);
+ }
+
+ static void log(int priority, String tag, @Nullable String caller, String message) {
+ log(priority, tag, "", caller, message);
+ }
+
+ static void log(
+ int priority, String tag, String prefix, @Nullable String caller, String message) {
+ if (Log.isLoggable(tag, priority)) {
+ if (caller != null) {
+ prefix += "[" + caller + "] ";
+ }
+ Slog.println(priority, tag, prefix + message);
+ }
+ }
+
+ private TransportUtils() {}
+}
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 0fd59eaa111f..bdfccd6e9d93 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -118,7 +118,7 @@ public class LocationManagerService extends ILocationManager.Stub {
private static final String TAG = "LocationManagerService";
public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
- private static final String WAKELOCK_KEY = TAG;
+ private static final String WAKELOCK_KEY = "*location*";
// Location resolution level: no location data whatsoever
private static final int RESOLUTION_LEVEL_NONE = 0;
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index e5fbdd21438c..296fb3210118 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,3 +1,4 @@
+# Connectivity / Networking
per-file ConnectivityService.java=ek@google.com
per-file ConnectivityService.java=hugobenichi@google.com
per-file ConnectivityService.java=lorenzo@google.com
@@ -7,3 +8,9 @@ per-file NetworkManagementService.java=lorenzo@google.com
per-file NsdService.java=ek@google.com
per-file NsdService.java=hugobenichi@google.com
per-file NsdService.java=lorenzo@google.com
+
+# Vibrator
+per-file VibratorService.java=michaelwr@google.com
+
+# Threads
+per-file DisplayThread.java=michaelwr@google.com
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b3b831edcab7..ae91b8208b72 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -351,7 +351,6 @@ import android.util.AtomicFile;
import android.util.StatsLog;
import android.util.TimingsTraceLog;
import android.util.DebugUtils;
-import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
@@ -716,6 +715,8 @@ public class ActivityManagerService extends IActivityManager.Stub
final AppErrors mAppErrors;
+ final AppWarnings mAppWarnings;
+
/**
* Dump of the activity state at the time of the last ANR. Cleared after
* {@link WindowManagerService#LAST_ANR_LIFETIME_DURATION_MSECS}
@@ -1726,7 +1727,6 @@ public class ActivityManagerService extends IActivityManager.Stub
static final int IDLE_UIDS_MSG = 58;
static final int LOG_STACK_STATE = 60;
static final int VR_MODE_CHANGE_MSG = 61;
- static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 62;
static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63;
static final int NOTIFY_VR_SLEEPING_MSG = 65;
static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66;
@@ -1745,7 +1745,6 @@ public class ActivityManagerService extends IActivityManager.Stub
static KillHandler sKillHandler = null;
CompatModeDialog mCompatModeDialog;
- UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
long mLastMemUsageReportTime = 0;
/**
@@ -1927,23 +1926,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
break;
}
- case SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG: {
- synchronized (ActivityManagerService.this) {
- final ActivityRecord ar = (ActivityRecord) msg.obj;
- if (mUnsupportedDisplaySizeDialog != null) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
- if (ar != null && mCompatModePackages.getPackageNotifyUnsupportedZoomLocked(
- ar.packageName)) {
- // TODO(multi-display): Show dialog on appropriate display.
- mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
- ActivityManagerService.this, mUiContext, ar.info.applicationInfo);
- mUnsupportedDisplaySizeDialog.show();
- }
- }
- break;
- }
case DISMISS_DIALOG_UI_MSG: {
final Dialog d = (Dialog) msg.obj;
d.dismiss();
@@ -2676,6 +2658,7 @@ public class ActivityManagerService extends IActivityManager.Stub
GL_ES_VERSION = 0;
mActivityStarter = null;
mAppErrors = null;
+ mAppWarnings = null;
mAppOpsService = mInjector.getAppOpsService(null, null);
mBatteryStatsService = null;
mCompatModePackages = null;
@@ -2743,10 +2726,13 @@ public class ActivityManagerService extends IActivityManager.Stub
mProviderMap = new ProviderMap(this);
mAppErrors = new AppErrors(mUiContext, this);
- // TODO: Move creation of battery stats service outside of activity manager service.
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
systemDir.mkdirs();
+
+ mAppWarnings = new AppWarnings(this, mUiContext, mHandler, mUiHandler, systemDir);
+
+ // TODO: Move creation of battery stats service outside of activity manager service.
mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler);
mBatteryStatsService.getActiveStatistics().readLocked();
mBatteryStatsService.scheduleWriteToDisk();
@@ -2793,7 +2779,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
mTaskChangeNotificationController =
new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
- mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager());
+ mActivityStarter = new ActivityStarter(this);
mRecentTasks = createRecentTasks();
mStackSupervisor.setRecentTasks(mRecentTasks);
mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
@@ -3310,22 +3296,25 @@ public class ActivityManagerService extends IActivityManager.Stub
mUiHandler.sendMessage(msg);
}
- final void showUnsupportedZoomDialogIfNeededLocked(ActivityRecord r) {
- final Configuration globalConfig = getGlobalConfiguration();
- if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
- && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
- final Message msg = Message.obtain();
- msg.what = SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG;
- msg.obj = r;
- mUiHandler.sendMessage(msg);
- }
+ final AppWarnings getAppWarningsLocked() {
+ return mAppWarnings;
+ }
+
+ /**
+ * Shows app warning dialogs, if necessary.
+ *
+ * @param r activity record for which the warnings may be displayed
+ */
+ final void showAppWarningsIfNeededLocked(ActivityRecord r) {
+ mAppWarnings.showUnsupportedCompileSdkDialogIfNeeded(r);
+ mAppWarnings.showUnsupportedDisplaySizeDialogIfNeeded(r);
}
private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
String what, Object obj, ProcessRecord srcApp) {
app.lastActivityTime = now;
- if (app.activities.size() > 0) {
+ if (app.activities.size() > 0 || app.recentTasks.size() > 0) {
// Don't want to touch dependent processes that are hosting activities.
return index;
}
@@ -3389,7 +3378,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
ProcessRecord client) {
final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
- || app.treatLikeActivity;
+ || app.treatLikeActivity || app.recentTasks.size() > 0;
final boolean hasService = false; // not impl yet. app.services.size() > 0;
if (!activityChange && hasActivity) {
// The process has activities, so we are only allowing activity-based adjustments
@@ -3493,7 +3482,8 @@ public class ActivityManagerService extends IActivityManager.Stub
int nextIndex;
if (hasActivity) {
final int N = mLruProcesses.size();
- if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) {
+ if ((app.activities.size() == 0 || app.recentTasks.size() > 0)
+ && mLruProcessActivityStart < (N - 1)) {
// Process doesn't have activities, but has clients with
// activities... move it up, but one below the top (the top
// should always have a real activity).
@@ -5330,6 +5320,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// Remove this application's activities from active lists.
boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);
+ app.clearRecentTasks();
+
app.activities.clear();
if (app.instr != null) {
@@ -11715,6 +11707,15 @@ public class ActivityManagerService extends IActivityManager.Stub
return true;
}
+ /**
+ * Returns the PackageManager. Used by classes hosted by {@link ActivityManagerService}. The
+ * PackageManager could be unavailable at construction time and therefore needs to be accessed
+ * on demand.
+ */
+ IPackageManager getPackageManager() {
+ return AppGlobals.getPackageManager();
+ }
+
PackageManagerInternal getPackageManagerInternalLocked() {
if (mPackageManagerInt == null) {
mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
@@ -14566,6 +14567,18 @@ public class ActivityManagerService extends IActivityManager.Stub
final String dropboxTag = processClass(process) + "_" + eventType;
if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
+ // Log to StatsLog before the rate-limiting.
+ // The logging below is adapated from appendDropboxProcessHeaders.
+ StatsLog.write(StatsLog.DROPBOX_ERROR_CHANGED,
+ process != null ? process.uid : -1,
+ dropboxTag,
+ processName,
+ process != null ? process.pid : -1,
+ (process != null && process.info != null) ?
+ (process.info.isInstantApp() ? 1 : 0) : -1,
+ activity != null ? activity.shortComponentName : null,
+ process != null ? (process.isInterestingToUserLocked() ? 1 : 0) : -1);
+
// Rate-limit how often we're willing to do the heavy lifting below to
// collect and record logs; currently 5 logs per 10 second period.
final long now = SystemClock.elapsedRealtime();
@@ -19350,13 +19363,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mRecentTasks.removeTasksByPackageName(ssp, userId);
mServices.forceStopPackageLocked(ssp, userId);
-
- // Hide the "unsupported display" dialog if necessary.
- if (mUnsupportedDisplaySizeDialog != null && ssp.equals(
- mUnsupportedDisplaySizeDialog.getPackageName())) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
+ mAppWarnings.onPackageUninstalled(ssp);
mCompatModePackages.handlePackageUninstalledLocked(ssp);
mBatteryStatsService.notePackageUninstalled(ssp);
}
@@ -19435,13 +19442,8 @@ public class ActivityManagerService extends IActivityManager.Stub
Uri data = intent.getData();
String ssp;
if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
- // Hide the "unsupported display" dialog if necessary.
- if (mUnsupportedDisplaySizeDialog != null && ssp.equals(
- mUnsupportedDisplaySizeDialog.getPackageName())) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
mCompatModePackages.handlePackageDataClearedLocked(ssp);
+ mAppWarnings.onPackageDataCleared(ssp);
}
break;
}
@@ -20631,8 +20633,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
if (isDensityChange && displayId == DEFAULT_DISPLAY) {
- // Reset the unsupported display size dialog.
- mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
+ mAppWarnings.onDensityChanged();
killAllBackgroundProcessesExcept(N,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
@@ -21013,7 +21014,7 @@ public class ActivityManagerService extends IActivityManager.Stub
+ " instead of expected " + app);
if (r.app == null || (r.app.uid == app.uid)) {
// Only fix things up when they look sane
- r.app = app;
+ r.setProcess(app);
} else {
continue;
}
@@ -21092,6 +21093,11 @@ public class ActivityManagerService extends IActivityManager.Stub
adj += minLayer;
}
}
+ if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.recentTasks.size() > 0) {
+ procState = ActivityManager.PROCESS_STATE_CACHED_RECENT;
+ app.adjType = "cch-rec";
+ if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Raise to cached recent: " + app);
+ }
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
|| procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -22652,6 +22658,7 @@ public class ActivityManagerService extends IActivityManager.Stub
switch (app.curProcState) {
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
// This process is a cached process holding activities...
// assign it the next cached value for that type, and then
// step that cached level.
@@ -23376,7 +23383,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// has been removed.
for (i=mRemovedProcesses.size()-1; i>=0; i--) {
final ProcessRecord app = mRemovedProcesses.get(i);
- if (app.activities.size() == 0
+ if (app.activities.size() == 0 && app.recentTasks.size() == 0
&& app.curReceivers.isEmpty() && app.services.size() == 0) {
Slog.i(
TAG, "Exiting empty application process "
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 9a16745c9ca3..5927666141d4 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -934,6 +934,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
}
}
+ void setProcess(ProcessRecord proc) {
+ app = proc;
+ final ActivityRecord root = task != null ? task.getRootActivity() : null;
+ if (root == this) {
+ task.setRootProcess(proc);
+ }
+ }
+
AppWindowContainerController getWindowContainerController() {
return mWindowContainerController;
}
@@ -2549,7 +2557,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
}
results = null;
newIntents = null;
- service.showUnsupportedZoomDialogIfNeededLocked(this);
+ service.getAppWarningsLocked().onResumeActivity(this);
service.showAskCompatModeDialogLocked(this);
} else {
service.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index c086c5292059..6985d6e36ac1 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2602,7 +2602,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
next.shortComponentName);
next.sleeping = false;
- mService.showUnsupportedZoomDialogIfNeededLocked(next);
+ mService.getAppWarningsLocked().onResumeActivity(next);
mService.showAskCompatModeDialogLocked(next);
next.app.pendingUiClean = true;
next.app.forceProcessStateUpTo(mService.mTopProcessState);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 745e9fbc9b9d..ddde4bc4957c 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1270,7 +1270,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// schedule launch ticks to collect information about slow apps.
r.startLaunchTickingLocked();
- r.app = app;
+ r.setProcess(app);
if (mKeyguardController.isKeyguardLocked()) {
r.notifyUnknownVisibilityLaunched();
@@ -1358,7 +1358,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY);
r.sleeping = false;
r.forceNewConfig = false;
- mService.showUnsupportedZoomDialogIfNeededLocked(r);
+ mService.getAppWarningsLocked().onStartActivity(r);
mService.showAskCompatModeDialogLocked(r);
r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo);
ProfilerInfo profilerInfo = null;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 9b8cbc1043a5..03162bbe0199 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -134,7 +134,6 @@ class ActivityStarter {
private static final int INVALID_LAUNCH_MODE = -1;
private final ActivityManagerService mService;
- private final IPackageManager mPackageManager;
private final ActivityStackSupervisor mSupervisor;
private final ActivityStartInterceptor mInterceptor;
@@ -234,9 +233,8 @@ class ActivityStarter {
mIntentDelivered = false;
}
- ActivityStarter(ActivityManagerService service, IPackageManager packageManager) {
+ ActivityStarter(ActivityManagerService service) {
mService = service;
- mPackageManager = packageManager;
mSupervisor = mService.mStackSupervisor;
mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
}
@@ -379,7 +377,7 @@ class ActivityStarter {
&& sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
try {
intent.addCategory(Intent.CATEGORY_VOICE);
- if (!mPackageManager.activitySupportsIntent(
+ if (!mService.getPackageManager().activitySupportsIntent(
intent.getComponent(), intent, resolvedType)) {
Slog.w(TAG,
"Activity being started in current voice task does not support voice: "
@@ -397,7 +395,7 @@ class ActivityStarter {
// If the caller is starting a new voice session, just make sure the target
// is actually allowing it to run this way.
try {
- if (!mPackageManager.activitySupportsIntent(intent.getComponent(),
+ if (!mService.getPackageManager().activitySupportsIntent(intent.getComponent(),
intent, resolvedType)) {
Slog.w(TAG,
"Activity being started in new voice task does not support: "
diff --git a/services/core/java/com/android/server/am/AppWarnings.java b/services/core/java/com/android/server/am/AppWarnings.java
new file mode 100644
index 000000000000..a3c0345066de
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppWarnings.java
@@ -0,0 +1,498 @@
+/*
+ * 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.am;
+
+import android.annotation.UiThread;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.AtomicFile;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+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.FileOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages warning dialogs shown during application lifecycle.
+ */
+class AppWarnings {
+ private static final String TAG = "AppWarnings";
+ private static final String CONFIG_FILE_NAME = "packages-warnings.xml";
+
+ public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01;
+ public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
+
+ private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
+
+ private final ActivityManagerService mAms;
+ private final Context mUiContext;
+ private final ConfigHandler mAmsHandler;
+ private final UiHandler mUiHandler;
+ private final AtomicFile mConfigFile;
+
+ private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
+ private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog;
+
+ /**
+ * Creates a new warning dialog manager.
+ * <p>
+ * <strong>Note:</strong> Must be called from the ActivityManagerService thread.
+ *
+ * @param ams
+ * @param uiContext
+ * @param amsHandler
+ * @param uiHandler
+ * @param systemDir
+ */
+ public AppWarnings(ActivityManagerService ams, Context uiContext, Handler amsHandler,
+ Handler uiHandler, File systemDir) {
+ mAms = ams;
+ mUiContext = uiContext;
+ mAmsHandler = new ConfigHandler(amsHandler.getLooper());
+ mUiHandler = new UiHandler(uiHandler.getLooper());
+ mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME));
+
+ readConfigFromFileAmsThread();
+ }
+
+ /**
+ * Shows the "unsupported display size" warning, if necessary.
+ *
+ * @param r activity record for which the warning may be displayed
+ */
+ public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) {
+ final Configuration globalConfig = mAms.getGlobalConfiguration();
+ if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
+ && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
+ mUiHandler.showUnsupportedDisplaySizeDialog(r);
+ }
+ }
+
+ /**
+ * Shows the "unsupported compile SDK" warning, if necessary.
+ *
+ * @param r activity record for which the warning may be displayed
+ */
+ public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) {
+ if (r.appInfo.compileSdkVersion == 0 || r.appInfo.compileSdkVersionCodename == null) {
+ // We don't know enough about this package. Abort!
+ return;
+ }
+
+ // If the application was built against an pre-release SDK that's older than the current
+ // platform OR if the current platform is pre-release and older than the SDK against which
+ // the application was built OR both are pre-release with the same SDK_INT but different
+ // codenames (e.g. simultaneous pre-release development), then we're likely to run into
+ // compatibility issues. Warn the user and offer to check for an update.
+ final int compileSdk = r.appInfo.compileSdkVersion;
+ final int platformSdk = Build.VERSION.SDK_INT;
+ final boolean isCompileSdkPreview = !"REL".equals(r.appInfo.compileSdkVersionCodename);
+ final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME);
+ if ((isCompileSdkPreview && compileSdk < platformSdk)
+ || (isPlatformSdkPreview && platformSdk < compileSdk)
+ || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk
+ && !Build.VERSION.CODENAME.equals(r.appInfo.compileSdkVersionCodename))) {
+ mUiHandler.showUnsupportedCompileSdkDialog(r);
+ }
+ }
+
+ /**
+ * Called when an activity is being started.
+ *
+ * @param r record for the activity being started
+ */
+ public void onStartActivity(ActivityRecord r) {
+ showUnsupportedCompileSdkDialogIfNeeded(r);
+ showUnsupportedDisplaySizeDialogIfNeeded(r);
+ }
+
+ /**
+ * Called when an activity was previously started and is being resumed.
+ *
+ * @param r record for the activity being resumed
+ */
+ public void onResumeActivity(ActivityRecord r) {
+ showUnsupportedDisplaySizeDialogIfNeeded(r);
+ }
+
+ /**
+ * Called by ActivityManagerService when package data has been cleared.
+ *
+ * @param name the package whose data has been cleared
+ */
+ public void onPackageDataCleared(String name) {
+ removePackageAndHideDialogs(name);
+ }
+
+ /**
+ * Called by ActivityManagerService when a package has been uninstalled.
+ *
+ * @param name the package that has been uninstalled
+ */
+ public void onPackageUninstalled(String name) {
+ removePackageAndHideDialogs(name);
+ }
+
+ /**
+ * Called by ActivityManagerService when the default display density has changed.
+ */
+ public void onDensityChanged() {
+ mUiHandler.hideUnsupportedDisplaySizeDialog();
+ }
+
+ /**
+ * Does what it says on the tin.
+ */
+ private void removePackageAndHideDialogs(String name) {
+ mUiHandler.hideDialogsForPackage(name);
+
+ synchronized (mPackageFlags) {
+ mPackageFlags.remove(name);
+ mAmsHandler.scheduleWrite();
+ }
+ }
+
+ /**
+ * Hides the "unsupported display size" warning.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ */
+ @UiThread
+ private void hideUnsupportedDisplaySizeDialogUiThread() {
+ if (mUnsupportedDisplaySizeDialog != null) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+ }
+
+ /**
+ * Shows the "unsupported display size" warning for the given application.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param ar record for the activity that triggered the warning
+ */
+ @UiThread
+ private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) {
+ if (mUnsupportedDisplaySizeDialog != null) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+ if (ar != null && !hasPackageFlag(
+ ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
+ mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
+ AppWarnings.this, mUiContext, ar.info.applicationInfo);
+ mUnsupportedDisplaySizeDialog.show();
+ }
+ }
+
+ /**
+ * Shows the "unsupported compile SDK" warning for the given application.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param ar record for the activity that triggered the warning
+ */
+ @UiThread
+ private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) {
+ if (mUnsupportedCompileSdkDialog != null) {
+ mUnsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialog = null;
+ }
+ if (ar != null && !hasPackageFlag(
+ ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
+ mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
+ AppWarnings.this, mUiContext, ar.info.applicationInfo);
+ mUnsupportedCompileSdkDialog.show();
+ }
+ }
+
+ /**
+ * Dismisses all warnings for the given package.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param name the package for which warnings should be dismissed, or {@code null} to dismiss
+ * all warnings
+ */
+ @UiThread
+ private void hideDialogsForPackageUiThread(String name) {
+ // Hides the "unsupported display" dialog if necessary.
+ if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals(
+ mUnsupportedDisplaySizeDialog.getPackageName()))) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+
+ // Hides the "unsupported compile SDK" dialog if necessary.
+ if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals(
+ mUnsupportedCompileSdkDialog.getPackageName()))) {
+ mUnsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialog = null;
+ }
+ }
+
+ /**
+ * Returns the value of the flag for the given package.
+ *
+ * @param name the package from which to retrieve the flag
+ * @param flag the bitmask for the flag to retrieve
+ * @return {@code true} if the flag is enabled, {@code false} otherwise
+ */
+ boolean hasPackageFlag(String name, int flag) {
+ return (getPackageFlags(name) & flag) == flag;
+ }
+
+ /**
+ * Sets the flag for the given package to the specified value.
+ *
+ * @param name the package on which to set the flag
+ * @param flag the bitmask for flag to set
+ * @param enabled the value to set for the flag
+ */
+ void setPackageFlag(String name, int flag, boolean enabled) {
+ synchronized (mPackageFlags) {
+ final int curFlags = getPackageFlags(name);
+ final int newFlags = enabled ? (curFlags & ~flag) : (curFlags | flag);
+ if (curFlags != newFlags) {
+ if (newFlags != 0) {
+ mPackageFlags.put(name, newFlags);
+ } else {
+ mPackageFlags.remove(name);
+ }
+ mAmsHandler.scheduleWrite();
+ }
+ }
+ }
+
+ /**
+ * Returns the bitmask of flags set for the specified package.
+ */
+ private int getPackageFlags(String name) {
+ synchronized (mPackageFlags) {
+ return mPackageFlags.getOrDefault(name, 0);
+ }
+ }
+
+ /**
+ * Handles messages on the system process UI thread.
+ */
+ private final class UiHandler extends Handler {
+ private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1;
+ private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2;
+ private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3;
+ private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
+
+ public UiHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
+ final ActivityRecord ar = (ActivityRecord) msg.obj;
+ showUnsupportedDisplaySizeDialogUiThread(ar);
+ } break;
+ case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
+ hideUnsupportedDisplaySizeDialogUiThread();
+ } break;
+ case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: {
+ final ActivityRecord ar = (ActivityRecord) msg.obj;
+ showUnsupportedCompileSdkDialogUiThread(ar);
+ } break;
+ case MSG_HIDE_DIALOGS_FOR_PACKAGE: {
+ final String name = (String) msg.obj;
+ hideDialogsForPackageUiThread(name);
+ } break;
+ }
+ }
+
+ public void showUnsupportedDisplaySizeDialog(ActivityRecord r) {
+ removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget();
+ }
+
+ public void hideUnsupportedDisplaySizeDialog() {
+ removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ }
+
+ public void showUnsupportedCompileSdkDialog(ActivityRecord r) {
+ removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG);
+ obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget();
+ }
+
+ public void hideDialogsForPackage(String name) {
+ obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget();
+ }
+ }
+
+ /**
+ * Handles messages on the ActivityManagerService thread.
+ */
+ private final class ConfigHandler extends Handler {
+ private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG;
+
+ private static final int DELAY_MSG_WRITE = 10000;
+
+ public ConfigHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_WRITE:
+ writeConfigToFileAmsThread();
+ break;
+ }
+ }
+
+ public void scheduleWrite() {
+ removeMessages(MSG_WRITE);
+ sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE);
+ }
+ }
+
+ /**
+ * Writes the configuration file.
+ * <p>
+ * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you
+ * don't care where you're doing I/O operations. But you <i>do</i> care, don't you?
+ */
+ private void writeConfigToFileAmsThread() {
+ // Create a shallow copy so that we don't have to synchronize on config.
+ final HashMap<String, Integer> packageFlags;
+ synchronized (mPackageFlags) {
+ packageFlags = new HashMap<>(mPackageFlags);
+ }
+
+ FileOutputStream fos = null;
+ try {
+ fos = mConfigFile.startWrite();
+
+ final XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ out.startTag(null, "packages");
+
+ for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) {
+ String pkg = entry.getKey();
+ int mode = entry.getValue();
+ if (mode == 0) {
+ continue;
+ }
+ out.startTag(null, "package");
+ out.attribute(null, "name", pkg);
+ out.attribute(null, "flags", Integer.toString(mode));
+ out.endTag(null, "package");
+ }
+
+ out.endTag(null, "packages");
+ out.endDocument();
+
+ mConfigFile.finishWrite(fos);
+ } catch (java.io.IOException e1) {
+ Slog.w(TAG, "Error writing package metadata", e1);
+ if (fos != null) {
+ mConfigFile.failWrite(fos);
+ }
+ }
+ }
+
+ /**
+ * Reads the configuration file and populates the package flags.
+ * <p>
+ * <strong>Note:</strong> Must be called from the constructor (and thus on the
+ * ActivityManagerService thread) since we don't synchronize on config.
+ */
+ private void readConfigFromFileAmsThread() {
+ FileInputStream fis = null;
+
+ try {
+ fis = mConfigFile.openRead();
+
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, StandardCharsets.UTF_8.name());
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG &&
+ eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+ if (eventType == XmlPullParser.END_DOCUMENT) {
+ return;
+ }
+
+ String tagName = parser.getName();
+ if ("packages".equals(tagName)) {
+ eventType = parser.next();
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ tagName = parser.getName();
+ if (parser.getDepth() == 2) {
+ if ("package".equals(tagName)) {
+ final String name = parser.getAttributeValue(null, "name");
+ if (name != null) {
+ final String flags = parser.getAttributeValue(
+ null, "flags");
+ int flagsInt = 0;
+ if (flags != null) {
+ try {
+ flagsInt = Integer.parseInt(flags);
+ } catch (NumberFormatException e) {
+ }
+ }
+ mPackageFlags.put(name, flagsInt);
+ }
+ }
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ }
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Error reading package metadata", e);
+ } catch (java.io.IOException e) {
+ if (fis != null) Slog.w(TAG, "Error reading package metadata", e);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (java.io.IOException e1) {
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java
index bfc0456586c1..82019fde30b7 100644
--- a/services/core/java/com/android/server/am/CompatModePackages.java
+++ b/services/core/java/com/android/server/am/CompatModePackages.java
@@ -60,6 +60,8 @@ public final class CompatModePackages {
public static final int COMPAT_FLAG_ENABLED = 1<<1;
// Unsupported zoom state: don't warn the user about unsupported zoom mode.
public static final int UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY = 1<<2;
+ // Unsupported compile SDK state: don't warn the user about unsupported compile SDK.
+ public static final int UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY = 1<<3;
private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
@@ -237,6 +239,10 @@ public final class CompatModePackages {
return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0;
}
+ public boolean getPackageNotifyUnsupportedCompileSdkLocked(String packageName) {
+ return (getPackageFlags(packageName)&UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY) == 0;
+ }
+
public void setFrontActivityAskCompatModeLocked(boolean ask) {
ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
if (r != null) {
@@ -245,22 +251,20 @@ public final class CompatModePackages {
}
public void setPackageAskCompatModeLocked(String packageName, boolean ask) {
- int curFlags = getPackageFlags(packageName);
- int newFlags = ask ? (curFlags&~COMPAT_FLAG_DONT_ASK) : (curFlags|COMPAT_FLAG_DONT_ASK);
- if (curFlags != newFlags) {
- if (newFlags != 0) {
- mPackages.put(packageName, newFlags);
- } else {
- mPackages.remove(packageName);
- }
- scheduleWrite();
- }
+ setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask);
}
public void setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify) {
+ setPackageFlagLocked(packageName, UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY, notify);
+ }
+
+ public void setPackageNotifyUnsupportedCompileSdkLocked(String packageName, boolean notify) {
+ setPackageFlagLocked(packageName, UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY, notify);
+ }
+
+ private void setPackageFlagLocked(String packageName, int flag, boolean set) {
final int curFlags = getPackageFlags(packageName);
- final int newFlags = notify ? (curFlags&~UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) :
- (curFlags|UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY);
+ final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag);
if (curFlags != newFlags) {
if (newFlags != 0) {
mPackages.put(packageName, newFlags);
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 7810c5e40901..1a196013b124 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -400,6 +400,9 @@ final class ProcessList {
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
procState = "CACC";
break;
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+ procState = "CRE ";
+ break;
case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
procState = "CEM ";
break;
@@ -494,6 +497,7 @@ final class ProcessList {
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -515,6 +519,7 @@ final class ProcessList {
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -536,6 +541,7 @@ final class ProcessList {
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -557,6 +563,7 @@ final class ProcessList {
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -578,6 +585,7 @@ final class ProcessList {
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e84772318e9b..9d3c2ae3617a 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -164,6 +164,8 @@ final class ProcessRecord {
// all activities running in the process
final ArrayList<ActivityRecord> activities = new ArrayList<>();
+ // any tasks this process had run root activities in
+ final ArrayList<TaskRecord> recentTasks = new ArrayList<>();
// all ServiceRecord running in this process
final ArraySet<ServiceRecord> services = new ArraySet<>();
// services that are currently executing code (need to remain foreground).
@@ -396,6 +398,12 @@ final class ProcessRecord {
pw.print(prefix); pw.print(" - "); pw.println(activities.get(i));
}
}
+ if (recentTasks.size() > 0) {
+ pw.print(prefix); pw.println("Recent Tasks:");
+ for (int i=0; i<recentTasks.size(); i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(recentTasks.get(i));
+ }
+ }
if (services.size() > 0) {
pw.print(prefix); pw.println("Services:");
for (int i=0; i<services.size(); i++) {
@@ -512,6 +520,13 @@ final class ProcessRecord {
}
}
+ public void clearRecentTasks() {
+ for (int i = recentTasks.size() - 1; i >= 0; i--) {
+ recentTasks.get(i).clearRootProcess();
+ }
+ recentTasks.clear();
+ }
+
/**
* This method returns true if any of the activities within the process record are interesting
* to the user. See HistoryRecord.isInterestingToUserLocked()
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 949f51fe1b09..a9c6eee05bf6 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -257,6 +257,11 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
/** Current stack. Setter must always be used to update the value. */
private ActivityStack mStack;
+ /** The process that had previously hosted the root activity of this task.
+ * Used to know that we should try harder to keep this process around, in case the
+ * user wants to return to it. */
+ private ProcessRecord mRootProcess;
+
/** Takes on same value as first root activity */
boolean isPersistable = false;
int maxRecents;
@@ -962,6 +967,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
mService.notifyTaskPersisterLocked(this, false);
}
+ clearRootProcess();
+
// TODO: Use window container controller once tasks are better synced between AM and WM
mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId);
}
@@ -2114,6 +2121,22 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
}
+ void setRootProcess(ProcessRecord proc) {
+ clearRootProcess();
+ if (intent != null &&
+ (intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
+ mRootProcess = proc;
+ proc.recentTasks.add(this);
+ }
+ }
+
+ void clearRootProcess() {
+ if (mRootProcess != null) {
+ mRootProcess.recentTasks.remove(this);
+ mRootProcess = null;
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("userId="); pw.print(userId);
pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
@@ -2198,6 +2221,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
if (lastDescription != null) {
pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription);
}
+ if (mRootProcess != null) {
+ pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
+ }
pw.print(prefix); pw.print("stackId="); pw.println(getStackId());
pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
diff --git a/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
new file mode 100644
index 000000000000..600589a40bb8
--- /dev/null
+++ b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.am;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+
+import com.android.internal.R;
+import com.android.server.utils.AppInstallerUtil;
+
+public class UnsupportedCompileSdkDialog {
+ private final AlertDialog mDialog;
+ private final String mPackageName;
+
+ public UnsupportedCompileSdkDialog(final AppWarnings manager, Context context,
+ ApplicationInfo appInfo) {
+ mPackageName = appInfo.packageName;
+
+ final PackageManager pm = context.getPackageManager();
+ final CharSequence label = appInfo.loadSafeLabel(pm);
+ final CharSequence message = context.getString(R.string.unsupported_compile_sdk_message,
+ label, appInfo.compileSdkVersionCodename);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setPositiveButton(R.string.ok, null)
+ .setMessage(message)
+ .setView(R.layout.unsupported_compile_sdk_dialog_content);
+
+ // If we might be able to update the app, show a button.
+ final Intent installerIntent = AppInstallerUtil.createIntent(context, appInfo.packageName);
+ if (installerIntent != null) {
+ builder.setNeutralButton(R.string.unsupported_compile_sdk_check_update,
+ (dialog, which) -> context.startActivity(installerIntent));
+ }
+
+ // Ensure the content view is prepared.
+ mDialog = builder.create();
+ mDialog.create();
+
+ final Window window = mDialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+
+ // DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
+ window.getAttributes().setTitle("UnsupportedCompileSdkDialog");
+
+ final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
+ alwaysShow.setChecked(true);
+ alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
+ mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked));
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public void show() {
+ mDialog.show();
+ }
+
+ public void dismiss() {
+ mDialog.dismiss();
+ }
+}
diff --git a/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java b/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java
index 501cd6bbba6d..88506632d7c3 100644
--- a/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java
+++ b/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java
@@ -30,7 +30,7 @@ public class UnsupportedDisplaySizeDialog {
private final AlertDialog mDialog;
private final String mPackageName;
- public UnsupportedDisplaySizeDialog(final ActivityManagerService service, Context context,
+ public UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context,
ApplicationInfo appInfo) {
mPackageName = appInfo.packageName;
@@ -54,14 +54,10 @@ public class UnsupportedDisplaySizeDialog {
// DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
window.getAttributes().setTitle("UnsupportedDisplaySizeDialog");
- final CheckBox alwaysShow = (CheckBox) mDialog.findViewById(R.id.ask_checkbox);
+ final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
alwaysShow.setChecked(true);
- alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> {
- synchronized (service) {
- service.mCompatModePackages.setPackageNotifyUnsupportedZoomLocked(
- mPackageName, isChecked);
- }
- });
+ alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
+ mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked));
}
public String getPackageName() {
diff --git a/services/core/java/com/android/server/display/BrightnessIdleJob.java b/services/core/java/com/android/server/display/BrightnessIdleJob.java
new file mode 100644
index 000000000000..876acf45fda8
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessIdleJob.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * JobService used to persists brightness slider events when the device
+ * is idle and charging.
+ */
+public class BrightnessIdleJob extends JobService {
+
+ // Must be unique within the system server uid.
+ private static final int JOB_ID = 3923512;
+
+ public static void scheduleJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+
+ JobInfo pending = jobScheduler.getPendingJob(JOB_ID);
+ JobInfo jobInfo =
+ new JobInfo.Builder(JOB_ID, new ComponentName(context, BrightnessIdleJob.class))
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true)
+ .setPeriodic(TimeUnit.HOURS.toMillis(24)).build();
+
+ if (pending != null && !pending.equals(jobInfo)) {
+ jobScheduler.cancel(JOB_ID);
+ pending = null;
+ }
+
+ if (pending == null) {
+ jobScheduler.schedule(jobInfo);
+ }
+ }
+
+ public static void cancelJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ jobScheduler.cancel(JOB_ID);
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (BrightnessTracker.DEBUG) {
+ Slog.d(BrightnessTracker.TAG, "Scheduled write of brightness events");
+ }
+ DisplayManagerInternal dmi = LocalServices.getService(DisplayManagerInternal.class);
+ dmi.persistBrightnessSliderEvents();
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 361d92843de9..90888f0a528d 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -61,12 +61,12 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
-import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@@ -75,8 +75,8 @@ import java.util.concurrent.TimeUnit;
*/
public class BrightnessTracker {
- private static final String TAG = "BrightnessTracker";
- private static final boolean DEBUG = false;
+ static final String TAG = "BrightnessTracker";
+ static final boolean DEBUG = false;
private static final String EVENTS_FILE = "brightness_events.xml";
private static final int MAX_EVENTS = 100;
@@ -103,6 +103,8 @@ public class BrightnessTracker {
@GuardedBy("mEventsLock")
private RingBuffer<BrightnessChangeEvent> mEvents
= new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
+ @GuardedBy("mEventsLock")
+ private boolean mEventsDirty;
private final Runnable mEventsWriter = () -> writeEvents();
private volatile boolean mWriteEventsScheduled;
@@ -170,6 +172,8 @@ public class BrightnessTracker {
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
mBroadcastReceiver = new Receiver();
mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
+
+ mInjector.scheduleIdleJob(mContext);
}
/** Stop listening for events */
@@ -181,6 +185,7 @@ public class BrightnessTracker {
mInjector.unregisterSensorListener(mContext, mSensorListener);
mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
+ mInjector.cancelIdleJob(mContext);
}
/**
@@ -211,6 +216,10 @@ public class BrightnessTracker {
brightness, userId);
}
+ public void persistEvents() {
+ scheduleWriteEvents();
+ }
+
private void handleBrightnessChanged() {
if (DEBUG) {
Slog.d(TAG, "Brightness change");
@@ -278,6 +287,7 @@ public class BrightnessTracker {
Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
}
synchronized (mEventsLock) {
+ mEventsDirty = true;
mEvents.append(event);
}
}
@@ -291,8 +301,12 @@ public class BrightnessTracker {
private void writeEvents() {
mWriteEventsScheduled = false;
- // TODO kick off write on handler thread e.g. every 24 hours.
synchronized (mEventsLock) {
+ if (!mEventsDirty) {
+ // Nothing to write
+ return;
+ }
+
final AtomicFile writeTo = mInjector.getFile();
if (writeTo == null) {
return;
@@ -301,12 +315,14 @@ public class BrightnessTracker {
if (writeTo.exists()) {
writeTo.delete();
}
+ mEventsDirty = false;
} else {
FileOutputStream output = null;
try {
output = writeTo.startWrite();
writeEventsLocked(output);
writeTo.finishWrite(output);
+ mEventsDirty = false;
} catch (IOException e) {
writeTo.failWrite(output);
Slog.e(TAG, "Failed to write change mEvents.", e);
@@ -317,6 +333,8 @@ public class BrightnessTracker {
private void readEvents() {
synchronized (mEventsLock) {
+ // Read might prune events so mark as dirty.
+ mEventsDirty = true;
mEvents.clear();
final AtomicFile readFrom = mInjector.getFile();
if (readFrom != null && readFrom.exists()) {
@@ -344,13 +362,16 @@ public class BrightnessTracker {
out.startTag(null, TAG_EVENTS);
BrightnessChangeEvent[] toWrite = mEvents.toArray();
+ // Clear events, code below will add back the ones that are still within the time window.
+ mEvents.clear();
if (DEBUG) {
Slog.d(TAG, "Writing events " + toWrite.length);
}
- final long timeCutOff = System.currentTimeMillis() - MAX_EVENT_AGE;
+ final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
for (int i = 0; i < toWrite.length; ++i) {
int userSerialNo = mInjector.getUserSerialNumber(mUserManager, toWrite[i].userId);
if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) {
+ mEvents.append(toWrite[i]);
out.startTag(null, TAG_EVENT);
out.attribute(null, ATTR_BRIGHTNESS, Integer.toString(toWrite[i].brightness));
out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp));
@@ -465,6 +486,17 @@ public class BrightnessTracker {
}
}
+ public void dump(PrintWriter pw) {
+ synchronized (mEventsLock) {
+ pw.println("BrightnessTracker state:");
+ pw.println(" mEvents.size=" + mEvents.size());
+ pw.println(" mEventsDirty=" + mEventsDirty);
+ }
+ synchronized (mDataCollectionLock) {
+ pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size());
+ }
+ }
+
// Not allowed to keep the SensorEvent so used to copy the data we care about.
private static class LightData {
public float lux;
@@ -635,5 +667,13 @@ public class BrightnessTracker {
public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
return ActivityManager.getService().getFocusedStackInfo();
}
+
+ public void scheduleIdleJob(Context context) {
+ BrightnessIdleJob.scheduleJob(context);
+ }
+
+ public void cancelIdleJob(Context context) {
+ BrightnessIdleJob.cancelJob(context);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index f1e20116d4ed..7530f3ef942b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1285,6 +1285,9 @@ public final class DisplayManagerService extends SystemService {
pw.println();
mPersistentDataStore.dump(pw);
+
+ pw.println();
+ mBrightnessTracker.dump(pw);
}
}
@@ -1921,5 +1924,10 @@ public final class DisplayManagerService extends SystemService {
public boolean isUidPresentOnDisplay(int uid, int displayId) {
return isUidPresentOnDisplayInternal(uid, displayId);
}
+
+ @Override
+ public void persistBrightnessSliderEvents() {
+ mBrightnessTracker.persistEvents();
+ }
}
}
diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS
new file mode 100644
index 000000000000..7e7335d68d3b
--- /dev/null
+++ b/services/core/java/com/android/server/display/OWNERS
@@ -0,0 +1 @@
+michaelwr@google.com
diff --git a/services/core/java/com/android/server/input/OWNERS b/services/core/java/com/android/server/input/OWNERS
new file mode 100644
index 000000000000..0313a40f7270
--- /dev/null
+++ b/services/core/java/com/android/server/input/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+svv@google.com
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index b9777ec0d968..4a3becbe3e07 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -151,6 +151,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
StorageController mStorageController;
/** Need directly for sending uid state changes */
private BackgroundJobsController mBackgroundJobsController;
+ private DeviceIdleJobsController mDeviceIdleJobsController;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
* when ready to execute them.
@@ -622,15 +623,24 @@ public final class JobSchedulerService extends com.android.server.SystemService
if (disabled) {
cancelJobsForUid(uid, "uid gone");
}
+ synchronized (mLock) {
+ mDeviceIdleJobsController.setUidActiveLocked(uid, false);
+ }
}
@Override public void onUidActive(int uid) throws RemoteException {
+ synchronized (mLock) {
+ mDeviceIdleJobsController.setUidActiveLocked(uid, true);
+ }
}
@Override public void onUidIdle(int uid, boolean disabled) {
if (disabled) {
cancelJobsForUid(uid, "app uid idle");
}
+ synchronized (mLock) {
+ mDeviceIdleJobsController.setUidActiveLocked(uid, false);
+ }
}
@Override public void onUidCachedChanged(int uid, boolean cached) {
@@ -939,11 +949,11 @@ public final class JobSchedulerService extends com.android.server.SystemService
mControllers.add(mBatteryController);
mStorageController = StorageController.get(this);
mControllers.add(mStorageController);
- mBackgroundJobsController = BackgroundJobsController.get(this);
- mControllers.add(mBackgroundJobsController);
+ mControllers.add(BackgroundJobsController.get(this));
mControllers.add(AppIdleController.get(this));
mControllers.add(ContentObserverController.get(this));
- mControllers.add(DeviceIdleJobsController.get(this));
+ mDeviceIdleJobsController = DeviceIdleJobsController.get(this);
+ mControllers.add(mDeviceIdleJobsController);
// If the job store determined that it can't yet reschedule persisted jobs,
// we need to start watching the clock.
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 1af3b39e928b..28b60e3eadda 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -250,7 +250,7 @@ public final class JobStore {
/**
* @param userHandle User for whom we are querying the list of jobs.
- * @return A list of all the jobs scheduled by the provided user. Never null.
+ * @return A list of all the jobs scheduled for the provided user. Never null.
*/
public List<JobStatus> getJobsByUser(int userHandle) {
return mJobSet.getJobsByUser(userHandle);
@@ -287,6 +287,10 @@ public final class JobStore {
mJobSet.forEachJob(uid, functor);
}
+ public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+ mJobSet.forEachJobForSourceUid(sourceUid, functor);
+ }
+
public interface JobStatusFunctor {
public void process(JobStatus jobStatus);
}
@@ -979,9 +983,12 @@ public final class JobStore {
static final class JobSet {
// Key is the getUid() originator of the jobs in each sheaf
private SparseArray<ArraySet<JobStatus>> mJobs;
+ // Same data but with the key as getSourceUid() of the jobs in each sheaf
+ private SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid;
public JobSet() {
mJobs = new SparseArray<ArraySet<JobStatus>>();
+ mJobsPerSourceUid = new SparseArray<>();
}
public List<JobStatus> getJobsByUid(int uid) {
@@ -995,10 +1002,10 @@ public final class JobStore {
// By user, not by uid, so we need to traverse by key and check
public List<JobStatus> getJobsByUser(int userId) {
- ArrayList<JobStatus> result = new ArrayList<JobStatus>();
- for (int i = mJobs.size() - 1; i >= 0; i--) {
- if (UserHandle.getUserId(mJobs.keyAt(i)) == userId) {
- ArraySet<JobStatus> jobs = mJobs.valueAt(i);
+ final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
+ for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
+ final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
if (jobs != null) {
result.addAll(jobs);
}
@@ -1009,32 +1016,60 @@ public final class JobStore {
public boolean add(JobStatus job) {
final int uid = job.getUid();
+ final int sourceUid = job.getSourceUid();
ArraySet<JobStatus> jobs = mJobs.get(uid);
if (jobs == null) {
jobs = new ArraySet<JobStatus>();
mJobs.put(uid, jobs);
}
- return jobs.add(job);
+ ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
+ if (jobsForSourceUid == null) {
+ jobsForSourceUid = new ArraySet<>();
+ mJobsPerSourceUid.put(sourceUid, jobsForSourceUid);
+ }
+ return jobs.add(job) && jobsForSourceUid.add(job);
}
public boolean remove(JobStatus job) {
final int uid = job.getUid();
- ArraySet<JobStatus> jobs = mJobs.get(uid);
- boolean didRemove = (jobs != null) ? jobs.remove(job) : false;
- if (didRemove && jobs.size() == 0) {
- // no more jobs for this uid; let the now-empty set object be GC'd.
- mJobs.remove(uid);
+ final ArraySet<JobStatus> jobs = mJobs.get(uid);
+ final int sourceUid = job.getSourceUid();
+ final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
+ boolean didRemove = jobs != null && jobs.remove(job) && jobsForSourceUid.remove(job);
+ if (didRemove) {
+ if (jobs.size() == 0) {
+ // no more jobs for this uid; let the now-empty set object be GC'd.
+ mJobs.remove(uid);
+ }
+ if (jobsForSourceUid.size() == 0) {
+ mJobsPerSourceUid.remove(sourceUid);
+ }
+ return true;
}
- return didRemove;
+ return false;
}
- // Remove the jobs all users not specified by the whitelist of user ids
+ /**
+ * Removes the jobs of all users not specified by the whitelist of user ids.
+ * The jobs scheduled by non existent users will not be removed if they were
+ */
public void removeJobsOfNonUsers(int[] whitelist) {
- for (int jobIndex = mJobs.size() - 1; jobIndex >= 0; jobIndex--) {
- int jobUserId = UserHandle.getUserId(mJobs.keyAt(jobIndex));
- // check if job's user id is not in the whitelist
+ for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
+ final int jobUserId = UserHandle.getUserId(mJobsPerSourceUid.keyAt(jobSetIndex));
if (!ArrayUtils.contains(whitelist, jobUserId)) {
- mJobs.removeAt(jobIndex);
+ mJobsPerSourceUid.removeAt(jobSetIndex);
+ }
+ }
+ for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
+ final ArraySet<JobStatus> jobsForUid = mJobs.valueAt(jobSetIndex);
+ for (int jobIndex = jobsForUid.size() - 1; jobIndex >= 0; jobIndex--) {
+ final int jobUserId = jobsForUid.valueAt(jobIndex).getUserId();
+ if (!ArrayUtils.contains(whitelist, jobUserId)) {
+ jobsForUid.removeAt(jobIndex);
+ }
+ }
+ if (jobsForUid.size() == 0) {
+ mJobs.removeAt(jobSetIndex);
}
}
}
@@ -1077,6 +1112,7 @@ public final class JobStore {
public void clear() {
mJobs.clear();
+ mJobsPerSourceUid.clear();
}
public int size() {
@@ -1112,8 +1148,17 @@ public final class JobStore {
}
}
- public void forEachJob(int uid, JobStatusFunctor functor) {
- ArraySet<JobStatus> jobs = mJobs.get(uid);
+ public void forEachJob(int callingUid, JobStatusFunctor functor) {
+ ArraySet<JobStatus> jobs = mJobs.get(callingUid);
+ if (jobs != null) {
+ for (int i = jobs.size() - 1; i >= 0; i--) {
+ functor.process(jobs.valueAt(i));
+ }
+ }
+ }
+
+ public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+ final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
if (jobs != null) {
for (int i = jobs.size() - 1; i >= 0; i--) {
functor.process(jobs.valueAt(i));
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 374ab43ca736..b7eb9e063591 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -16,14 +16,19 @@
package com.android.server.job.controllers;
+import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import com.android.internal.util.ArrayUtils;
import com.android.server.DeviceIdleController;
@@ -42,11 +47,22 @@ public final class DeviceIdleJobsController extends StateController {
private static final String LOG_TAG = "DeviceIdleJobsController";
private static final boolean LOG_DEBUG = false;
+ private static final long BACKGROUND_JOBS_DELAY = 3000;
+
+ static final int PROCESS_BACKGROUND_JOBS = 1;
// Singleton factory
private static Object sCreationLock = new Object();
private static DeviceIdleJobsController sController;
+ /**
+ * These are jobs added with a special flag to indicate that they should be exempted from doze
+ * when the app is temp whitelisted or in the foreground.
+ */
+ private final ArraySet<JobStatus> mAllowInIdleJobs;
+ private final SparseBooleanArray mForegroundUids;
+ private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
+ private final DeviceIdleJobsDelayHandler mHandler;
private final JobSchedulerService mJobSchedulerService;
private final PowerManager mPowerManager;
private final DeviceIdleController.LocalService mLocalDeviceIdleController;
@@ -57,14 +73,6 @@ public final class DeviceIdleJobsController extends StateController {
private boolean mDeviceIdleMode;
private int[] mDeviceIdleWhitelistAppIds;
private int[] mPowerSaveTempWhitelistAppIds;
- // These jobs were added when the app was in temp whitelist, these should be exempted from doze
- private final ArraySet<JobStatus> mTempWhitelistedJobs;
-
- final JobStore.JobStatusFunctor mUpdateFunctor = new JobStore.JobStatusFunctor() {
- @Override public void process(JobStatus jobStatus) {
- updateTaskStateLocked(jobStatus);
- }
- };
/**
* Returns a singleton for the DeviceIdleJobsController
@@ -108,8 +116,8 @@ public final class DeviceIdleJobsController extends StateController {
+ Arrays.toString(mPowerSaveTempWhitelistAppIds));
}
boolean changed = false;
- for (int i = 0; i < mTempWhitelistedJobs.size(); i ++) {
- changed |= updateTaskStateLocked(mTempWhitelistedJobs.valueAt(i));
+ for (int i = 0; i < mAllowInIdleJobs.size(); i++) {
+ changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i));
}
if (changed) {
mStateChangedListener.onControllerStateChanged();
@@ -125,6 +133,7 @@ public final class DeviceIdleJobsController extends StateController {
super(jobSchedulerService, context, lock);
mJobSchedulerService = jobSchedulerService;
+ mHandler = new DeviceIdleJobsDelayHandler(context.getMainLooper());
// Register for device idle mode changes
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mLocalDeviceIdleController =
@@ -132,7 +141,9 @@ public final class DeviceIdleJobsController extends StateController {
mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
mPowerSaveTempWhitelistAppIds =
mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
- mTempWhitelistedJobs = new ArraySet<>();
+ mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
+ mAllowInIdleJobs = new ArraySet<>();
+ mForegroundUids = new SparseBooleanArray();
final IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
@@ -150,7 +161,20 @@ public final class DeviceIdleJobsController extends StateController {
}
mDeviceIdleMode = enabled;
if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
- mJobSchedulerService.getJobStore().forEachJob(mUpdateFunctor);
+ if (enabled) {
+ mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
+ mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+ } else {
+ // When coming out of doze, process all foreground uids immediately, while others
+ // will be processed after a delay of 3 seconds.
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ mJobSchedulerService.getJobStore().forEachJobForSourceUid(
+ mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
+ }
+ }
+ mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
+ }
}
// Inform the job scheduler service about idle mode changes
if (changed) {
@@ -159,11 +183,30 @@ public final class DeviceIdleJobsController extends StateController {
}
/**
+ * Called by jobscheduler service to report uid state changes between active and idle
+ */
+ public void setUidActiveLocked(int uid, boolean active) {
+ final boolean changed = (active != mForegroundUids.get(uid));
+ if (!changed) {
+ return;
+ }
+ if (LOG_DEBUG) {
+ Slog.d(LOG_TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
+ }
+ mForegroundUids.put(uid, active);
+ mDeviceIdleUpdateFunctor.mChanged = false;
+ mJobSchedulerService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
+ if (mDeviceIdleUpdateFunctor.mChanged) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+
+ /**
* Checks if the given job's scheduling app id exists in the device idle user whitelist.
*/
boolean isWhitelistedLocked(JobStatus job) {
- return ArrayUtils.contains(mDeviceIdleWhitelistAppIds,
- UserHandle.getAppId(job.getSourceUid()));
+ return Arrays.binarySearch(mDeviceIdleWhitelistAppIds,
+ UserHandle.getAppId(job.getSourceUid())) >= 0;
}
/**
@@ -175,31 +218,33 @@ public final class DeviceIdleJobsController extends StateController {
}
private boolean updateTaskStateLocked(JobStatus task) {
- final boolean whitelisted = isWhitelistedLocked(task)
- || (mTempWhitelistedJobs.contains(task) && isTempWhitelistedLocked(task));
- final boolean enableTask = !mDeviceIdleMode || whitelisted;
+ final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
+ && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
+ final boolean whitelisted = isWhitelistedLocked(task);
+ final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
}
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- if (isTempWhitelistedLocked(jobStatus)) {
- mTempWhitelistedJobs.add(jobStatus);
- jobStatus.setDeviceNotDozingConstraintSatisfied(true, true);
- } else {
- updateTaskStateLocked(jobStatus);
+ if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+ mAllowInIdleJobs.add(jobStatus);
}
+ updateTaskStateLocked(jobStatus);
}
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
- mTempWhitelistedJobs.remove(jobStatus);
+ if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+ mAllowInIdleJobs.remove(jobStatus);
+ }
}
@Override
public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
pw.println("DeviceIdleJobsController");
+ pw.println("mDeviceIdleMode=" + mDeviceIdleMode);
mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
@Override public void process(JobStatus jobStatus) {
if (!jobStatus.shouldDump(filterUid)) {
@@ -217,8 +262,42 @@ public final class DeviceIdleJobsController extends StateController {
if (jobStatus.dozeWhitelisted) {
pw.print(" WHITELISTED");
}
+ if (mAllowInIdleJobs.contains(jobStatus)) {
+ pw.print(" ALLOWED_IN_DOZE");
+ }
pw.println();
}
});
}
+
+ final class DeviceIdleUpdateFunctor implements JobStore.JobStatusFunctor {
+ boolean mChanged;
+
+ @Override
+ public void process(JobStatus jobStatus) {
+ mChanged |= updateTaskStateLocked(jobStatus);
+ }
+ }
+
+ final class DeviceIdleJobsDelayHandler extends Handler {
+ public DeviceIdleJobsDelayHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case PROCESS_BACKGROUND_JOBS:
+ // Just process all the jobs, the ones in foreground should already be running.
+ synchronized (mLock) {
+ mDeviceIdleUpdateFunctor.mChanged = false;
+ mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+ if (mDeviceIdleUpdateFunctor.mChanged) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+ break;
+ }
+ }
+ }
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/lights/OWNERS b/services/core/java/com/android/server/lights/OWNERS
new file mode 100644
index 000000000000..7e7335d68d3b
--- /dev/null
+++ b/services/core/java/com/android/server/lights/OWNERS
@@ -0,0 +1 @@
+michaelwr@google.com
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6ba1d8de79cf..08da568d5931 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -16,15 +16,21 @@
package com.android.server.notification;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.USER_NULL;
import static android.service.notification.NotificationListenerService
+ .HINT_HOST_DISABLE_CALL_EFFECTS;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
+import static android.service.notification.NotificationListenerService
+ .HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
+import static android.service.notification.NotificationListenerService
.NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
import static android.service.notification.NotificationListenerService
.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
@@ -32,12 +38,13 @@ import static android.service.notification.NotificationListenerService
.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static android.service.notification.NotificationListenerService.REASON_ERROR;
-import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService
+ .REASON_GROUP_SUMMARY_CANCELED;
import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
@@ -48,14 +55,10 @@ import static android.service.notification.NotificationListenerService.REASON_SN
import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.service.notification.NotificationListenerService.TRIM_FULL;
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
-
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -68,17 +71,17 @@ import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
-import android.app.NotificationChannelGroup;
-import android.app.backup.BackupManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationManager.Policy;
+import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.app.backup.BackupManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.companion.ICompanionDeviceManager;
@@ -119,8 +122,8 @@ import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.Vibrator;
import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
@@ -174,9 +177,9 @@ import com.android.server.SystemService;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
+import com.android.server.notification.ManagedServices.UserProfiles;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.notification.ManagedServices.UserProfiles;
import libcore.io.IoUtils;
@@ -196,7 +199,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
-import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -1520,7 +1522,11 @@ public class NotificationManagerService extends SystemService {
}
}
}
+ final NotificationChannel preUpdate =
+ mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+
mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
+ maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
if (!fromListener) {
final NotificationChannel modifiedChannel =
@@ -1533,12 +1539,40 @@ public class NotificationManagerService extends SystemService {
savePolicyFile();
}
+ private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate,
+ NotificationChannel update) {
+ try {
+ if ((preUpdate.getImportance() == IMPORTANCE_NONE
+ && update.getImportance() != IMPORTANCE_NONE)
+ || (preUpdate.getImportance() != IMPORTANCE_NONE
+ && update.getImportance() == IMPORTANCE_NONE)) {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID,
+ update.getId())
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE,
+ update.getImportance() == IMPORTANCE_NONE)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ }
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about channel change", e);
+ }
+ }
+
private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromApp, boolean fromListener) {
Preconditions.checkNotNull(group);
Preconditions.checkNotNull(pkg);
+
+ final NotificationChannelGroup preUpdate =
+ mRankingHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
mRankingHelper.createNotificationChannelGroup(pkg, uid, group,
fromApp);
+ if (!fromApp) {
+ maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group);
+ }
if (!fromListener) {
mListeners.notifyNotificationChannelGroupChanged(pkg,
UserHandle.of(UserHandle.getCallingUserId()), group,
@@ -1546,6 +1580,25 @@ public class NotificationManagerService extends SystemService {
}
}
+ private void maybeNotifyChannelGroupOwner(String pkg, int uid,
+ NotificationChannelGroup preUpdate, NotificationChannelGroup update) {
+ try {
+ if (preUpdate.isBlocked() != update.isBlocked()) {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID,
+ update.getId())
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE,
+ update.isBlocked())
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ }
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about group change", e);
+ }
+ }
+
private ArrayList<ComponentName> getSuppressors() {
ArrayList<ComponentName> names = new ArrayList<ComponentName>();
for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
@@ -1924,11 +1977,18 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
+ checkCallerIsSystemOrSameApp(pkg);
+ return mRankingHelper.getNotificationChannelGroupWithChannels(
+ pkg, Binder.getCallingUid(), groupId, false);
+ }
+
+ @Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(
String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return new ParceledListSlice<>(new ArrayList(
- mRankingHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid())));
+ return mRankingHelper.getNotificationChannelGroups(
+ pkg, Binder.getCallingUid(), false, false);
}
@Override
@@ -1998,7 +2058,7 @@ public class NotificationManagerService extends SystemService {
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
String pkg, int uid, boolean includeDeleted) {
checkCallerIsSystem();
- return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted);
+ return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true);
}
@Override
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index b9c0d90741f3..b1b0bf26d9ee 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -36,7 +36,7 @@ public interface RankingConfig {
void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromTargetApp);
ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid, boolean includeDeleted);
+ int uid, boolean includeDeleted, boolean includeNonGrouped);
void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp);
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index d566a45011d7..c0dccb53c08a 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -750,12 +750,15 @@ public class RankingHelper implements RankingConfig {
int uid) {
Preconditions.checkNotNull(pkg);
Record r = getRecord(pkg, uid);
+ if (r == null) {
+ return null;
+ }
return r.groups.get(groupId);
}
@Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid, boolean includeDeleted) {
+ int uid, boolean includeDeleted, boolean includeNonGrouped) {
Preconditions.checkNotNull(pkg);
Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
Record r = getRecord(pkg, uid);
@@ -783,7 +786,7 @@ public class RankingHelper implements RankingConfig {
}
}
}
- if (nonGrouped.getChannels().size() > 0) {
+ if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
groups.put(null, nonGrouped);
}
return new ParceledListSlice<>(new ArrayList<>(groups.values()));
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index bbe59eb84904..2e44e7973cab 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3585,7 +3585,7 @@ public class PackageManagerService extends IPackageManager.Stub
final int N = list.size();
for (int i = 0; i < N; i++) {
ResolveInfo info = list.get(i);
- if (packageName.equals(info.activityInfo.packageName)) {
+ if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index ee773a515b0f..44f36d1734c8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -264,7 +264,7 @@ class PackageManagerShellCommand extends ShellCommand {
PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
null, null);
params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
- pkgLite, params.sessionParams.abiOverride));
+ pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor()));
} catch (PackageParserException | IOException e) {
getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
throw new IllegalArgumentException(
@@ -1169,11 +1169,17 @@ class PackageManagerShellCommand extends ShellCommand {
}
List<String> failedPackages = new ArrayList<>();
+ int index = 0;
for (String packageName : packageNames) {
if (clearProfileData) {
mInterface.clearApplicationProfileData(packageName);
}
+ if (allPackages) {
+ pw.println(++index + "/" + packageNames.size() + ": " + packageName);
+ pw.flush();
+ }
+
boolean result = secondaryDex
? mInterface.performDexOptSecondary(packageName,
targetCompilerFilter, forceCompilation)
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7748ae4f3fe0..384070cf4777 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -304,6 +304,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
static final int LONG_PRESS_POWER_SHUT_OFF = 2;
static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3;
+ static final int LONG_PRESS_POWER_GO_TO_VOICE_ASSIST = 4;
+
+ static final int VERY_LONG_PRESS_POWER_NOTHING = 0;
+ static final int VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
static final int MULTI_PRESS_POWER_NOTHING = 0;
static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
@@ -569,6 +573,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
boolean mLidControlsSleep;
int mShortPressOnPowerBehavior;
int mLongPressOnPowerBehavior;
+ int mVeryLongPressOnPowerBehavior;
int mDoublePressOnPowerBehavior;
int mTriplePressOnPowerBehavior;
int mLongPressOnBackBehavior;
@@ -586,6 +591,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
boolean mHasSoftInput = false;
boolean mTranslucentDecorEnabled = true;
boolean mUseTvRouting;
+ int mVeryLongPressTimeout;
private boolean mHandleVolumeKeysInWM;
@@ -796,6 +802,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private static final int MSG_HANDLE_ALL_APPS = 26;
private static final int MSG_LAUNCH_ASSIST = 27;
private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28;
+ private static final int MSG_POWER_VERY_LONG_PRESS = 29;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -855,6 +862,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case MSG_POWER_LONG_PRESS:
powerLongPress();
break;
+ case MSG_POWER_VERY_LONG_PRESS:
+ powerVeryLongPress();
+ break;
case MSG_UPDATE_DREAMING_SLEEP_TOKEN:
updateDreamingSleepToken(msg.arg1 != 0);
break;
@@ -1299,6 +1309,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+
+ if (hasVeryLongPressOnPowerBehavior()) {
+ Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
+ longMsg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
+ }
}
} else {
wakeUpFromPowerKey(event.getDownTime());
@@ -1308,6 +1324,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+
+ if (hasVeryLongPressOnPowerBehavior()) {
+ Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
+ longMsg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
+ }
+
mBeganFromNonInteractive = true;
} else {
final int maxCount = getMaxMultiPressPowerCount();
@@ -1369,6 +1392,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mPowerKeyHandled = true;
mHandler.removeMessages(MSG_POWER_LONG_PRESS);
}
+ if (hasVeryLongPressOnPowerBehavior()) {
+ mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS);
+ }
}
private void cancelPendingBackKeyAction() {
@@ -1516,6 +1542,29 @@ public class PhoneWindowManager implements WindowManagerPolicy {
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
break;
+ case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
+ mPowerKeyHandled = true;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ final boolean keyguardActive = mKeyguardDelegate == null
+ ? false
+ : mKeyguardDelegate.isShowing();
+ if (!keyguardActive) {
+ Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+ startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ }
+ break;
+ }
+ }
+
+ private void powerVeryLongPress() {
+ switch (mVeryLongPressOnPowerBehavior) {
+ case VERY_LONG_PRESS_POWER_NOTHING:
+ break;
+ case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
+ mPowerKeyHandled = true;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ showGlobalActionsInternal();
+ break;
}
}
@@ -1574,6 +1623,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING;
}
+ private boolean hasVeryLongPressOnPowerBehavior() {
+ return mVeryLongPressOnPowerBehavior != VERY_LONG_PRESS_POWER_NOTHING;
+ }
+
private boolean hasLongPressOnBackBehavior() {
return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING;
}
@@ -1979,12 +2032,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_shortPressOnPowerBehavior);
mLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
+ mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressOnPowerBehavior);
mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_doublePressOnPowerBehavior);
mTriplePressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_triplePressOnPowerBehavior);
mShortPressOnSleepBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_shortPressOnSleepBehavior);
+ mVeryLongPressTimeout = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressTimeout);
mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION;
@@ -8193,6 +8250,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pw.print("mLongPressOnPowerBehavior=");
pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
pw.print(prefix);
+ pw.print("mVeryLongPressOnPowerBehavior=");
+ pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior));
+ pw.print(prefix);
pw.print("mDoublePressOnPowerBehavior=");
pw.println(multiPressOnPowerBehaviorToString(mDoublePressOnPowerBehavior));
pw.print(prefix);
@@ -8445,6 +8505,18 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return Integer.toString(behavior);
}
}
+
+ private static String veryLongPressOnPowerBehaviorToString(int behavior) {
+ switch (behavior) {
+ case VERY_LONG_PRESS_POWER_NOTHING:
+ return "VERY_LONG_PRESS_POWER_NOTHING";
+ case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
+ return "VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
private static String multiPressOnPowerBehaviorToString(int behavior) {
switch (behavior) {
case MULTI_PRESS_POWER_NOTHING:
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 3992f8a566ea..87c92744cdab 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -32,6 +32,7 @@ import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.power.batterysaver.CpuFrequencies;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -40,15 +41,14 @@ import java.util.List;
/**
* Class to decide whether to turn on battery saver mode for specific service
*
- * TODO: We should probably make {@link #mFilesForInteractive} and {@link #mFilesForNoninteractive}
- * less flexible and just take a list of "CPU number - frequency" pairs. Being able to write
- * anything under /sys/ and /proc/ is too loose.
- *
- * Test: atest BatterySaverPolicyTest
+ * Test:
+ atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
*/
public class BatterySaverPolicy extends ContentObserver {
private static final String TAG = "BatterySaverPolicy";
+ public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.
+
// Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode.
public static final int GPS_MODE_NO_CHANGE = 0;
// Value of batterySaverGpsMode such that GPS is disabled when battery saver mode
@@ -64,18 +64,26 @@ public class BatterySaverPolicy extends ContentObserver {
private static final String KEY_FIREWALL_DISABLED = "firewall_disabled";
private static final String KEY_ADJUST_BRIGHTNESS_DISABLED = "adjust_brightness_disabled";
private static final String KEY_DATASAVER_DISABLED = "datasaver_disabled";
+ private static final String KEY_LAUNCH_BOOST_DISABLED = "launch_boost_disabled";
private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor";
private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby";
private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
- private static final String KEY_FILE_FOR_INTERACTIVE_PREFIX = "file-on:";
- private static final String KEY_FILE_FOR_NONINTERACTIVE_PREFIX = "file-off:";
+ private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
+ private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n";
+
+ private final Object mLock = new Object();
- private static String mSettings;
- private static String mDeviceSpecificSettings;
- private static String mDeviceSpecificSettingsSource; // For dump() only.
+ @GuardedBy("mLock")
+ private String mSettings;
+
+ @GuardedBy("mLock")
+ private String mDeviceSpecificSettings;
+
+ @GuardedBy("mLock")
+ private String mDeviceSpecificSettingsSource; // For dump() only.
/**
* {@code true} if vibration is disabled in battery saver mode.
@@ -83,6 +91,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_VIBRATION_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mVibrationDisabled;
/**
@@ -91,6 +100,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ANIMATION_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mAnimationDisabled;
/**
@@ -100,6 +110,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_SOUNDTRIGGER_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mSoundTriggerDisabled;
/**
@@ -108,6 +119,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_FULLBACKUP_DEFERRED
*/
+ @GuardedBy("mLock")
private boolean mFullBackupDeferred;
/**
@@ -116,6 +128,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_KEYVALUE_DEFERRED
*/
+ @GuardedBy("mLock")
private boolean mKeyValueBackupDeferred;
/**
@@ -124,6 +137,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_FIREWALL_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mFireWallDisabled;
/**
@@ -132,6 +146,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ADJUST_BRIGHTNESS_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mAdjustBrightnessDisabled;
/**
@@ -140,14 +155,22 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_DATASAVER_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mDataSaverDisabled;
/**
+ * {@code true} if launch boost should be disabled on battery saver.
+ */
+ @GuardedBy("mLock")
+ private boolean mLaunchBoostDisabled;
+
+ /**
* This is the flag to decide the gps mode in battery saver mode.
*
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_GPS_MODE
*/
+ @GuardedBy("mLock")
private int mGpsMode;
/**
@@ -157,20 +180,21 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ADJUST_BRIGHTNESS_FACTOR
*/
+ @GuardedBy("mLock")
private float mAdjustBrightnessFactor;
/**
* Whether to put all apps in the stand-by mode.
*/
+ @GuardedBy("mLock")
private boolean mForceAllAppsStandby;
/**
* Weather to show non-essential sensors (e.g. edge sensors) or not.
*/
+ @GuardedBy("mLock")
private boolean mOptionalSensorsDisabled;
- private final Object mLock = new Object();
-
@GuardedBy("mLock")
private Context mContext;
@@ -227,7 +251,11 @@ public class BatterySaverPolicy extends ContentObserver {
@VisibleForTesting
String getGlobalSetting(String key) {
- return Settings.Global.getString(mContentResolver, key);
+ final ContentResolver cr;
+ synchronized (mLock) {
+ cr = mContentResolver;
+ }
+ return Settings.Global.getString(cr, key);
}
@VisibleForTesting
@@ -273,6 +301,11 @@ public class BatterySaverPolicy extends ContentObserver {
mSettings = setting;
mDeviceSpecificSettings = deviceSpecificSetting;
+ if (DEBUG) {
+ Slog.i(TAG, "mSettings=" + mSettings);
+ Slog.i(TAG, "mDeviceSpecificSettings=" + mDeviceSpecificSettings);
+ }
+
final KeyValueListParser parser = new KeyValueListParser(',');
// Non-device-specific parameters.
@@ -291,6 +324,7 @@ public class BatterySaverPolicy extends ContentObserver {
mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
+ mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true);
mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true);
mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
@@ -307,29 +341,11 @@ public class BatterySaverPolicy extends ContentObserver {
+ deviceSpecificSetting);
}
- mFilesForInteractive = collectParams(parser, KEY_FILE_FOR_INTERACTIVE_PREFIX);
- mFilesForNoninteractive = collectParams(parser, KEY_FILE_FOR_NONINTERACTIVE_PREFIX);
- }
+ mFilesForInteractive = (new CpuFrequencies()).parseString(
+ parser.getString(KEY_CPU_FREQ_INTERACTIVE, "")).toSysFileMap();
- private static ArrayMap<String, String> collectParams(
- KeyValueListParser parser, String prefix) {
- final ArrayMap<String, String> ret = new ArrayMap<>();
-
- for (int i = parser.size() - 1; i >= 0; i--) {
- final String key = parser.keyAt(i);
- if (!key.startsWith(prefix)) {
- continue;
- }
- final String path = key.substring(prefix.length());
-
- if (!(path.startsWith("/sys/") || path.startsWith("/proc/"))) {
- Slog.wtf(TAG, "Invalid path: " + path);
- continue;
- }
-
- ret.put(path, parser.getString(key, ""));
- }
- return ret;
+ mFilesForNoninteractive = (new CpuFrequencies()).parseString(
+ parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "")).toSysFileMap();
}
/**
@@ -395,14 +411,20 @@ public class BatterySaverPolicy extends ContentObserver {
}
}
+ public boolean isLaunchBoostDisabled() {
+ synchronized (mLock) {
+ return mLaunchBoostDisabled;
+ }
+ }
+
public void dump(PrintWriter pw) {
synchronized (mLock) {
pw.println();
pw.println("Battery saver policy");
- pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
- pw.println(" value: " + mSettings);
- pw.println(" Settings " + mDeviceSpecificSettingsSource);
- pw.println(" value: " + mDeviceSpecificSettings);
+ pw.println(" Settings: " + Settings.Global.BATTERY_SAVER_CONSTANTS);
+ pw.println(" value: " + mSettings);
+ pw.println(" Settings: " + mDeviceSpecificSettingsSource);
+ pw.println(" value: " + mDeviceSpecificSettings);
pw.println();
pw.println(" " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
@@ -411,6 +433,7 @@ public class BatterySaverPolicy extends ContentObserver {
pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
+ pw.println(" " + KEY_LAUNCH_BOOST_DISABLED + "=" + mLaunchBoostDisabled);
pw.println(" " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode);
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
new file mode 100644
index 000000000000..b4300a930cdf
--- /dev/null
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -0,0 +1,3 @@
+michaelwr@google.com
+
+per-file BatterySaverPolicy.java=omakoto@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 584761c3c0ef..2c87a4034300 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1457,6 +1457,10 @@ public final class PowerManagerService extends SystemService
case PowerManager.GO_TO_SLEEP_REASON_HDMI:
Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")...");
break;
+ case PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY:
+ Slog.i(TAG, "Going to sleep by an accessibility service request (uid "
+ + uid +")...");
+ break;
default:
Slog.i(TAG, "Going to sleep by application request (uid " + uid +")...");
reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
@@ -3105,7 +3109,16 @@ public final class PowerManagerService extends SystemService
mIsVrModeEnabled = enabled;
}
- private static void powerHintInternal(int hintId, int data) {
+ private void powerHintInternal(int hintId, int data) {
+ // Maybe filter the event.
+ switch (hintId) {
+ case PowerHint.LAUNCH: // 1: activate launch boost 0: deactivate.
+ if (data == 1 && mBatterySaverController.isLaunchBoostDisabled()) {
+ return;
+ }
+ break;
+ }
+
nativeSendPowerHint(hintId, data);
}
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 3db6a25f5413..ae01ea5721d8 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -49,7 +49,7 @@ import java.util.ArrayList;
public class BatterySaverController implements BatterySaverPolicyListener {
static final String TAG = "BatterySaverController";
- static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE
+ static final boolean DEBUG = BatterySaverPolicy.DEBUG;
private final Object mLock = new Object();
private final Context mContext;
@@ -174,6 +174,13 @@ public class BatterySaverController implements BatterySaverPolicyListener {
}
/**
+ * @return true if launch boost should currently be disabled.
+ */
+ public boolean isLaunchBoostDisabled() {
+ return isEnabled() && mBatterySaverPolicy.isLaunchBoostDisabled();
+ }
+
+ /**
* Dispatch power save events to the listeners.
*
* This method is always called on the handler thread.
diff --git a/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java b/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java
new file mode 100644
index 000000000000..1629486b9273
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.power.batterysaver;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Map;
+
+
+/**
+ * Helper to parse a list of "core-number:frequency" pairs concatenated with / as a separator,
+ * and convert them into a map of "filename -> value" that should be written to
+ * /sys/.../scaling_max_freq.
+ *
+ * Example input: "0:1900800/4:2500000", which will be converted into:
+ * "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" "1900800"
+ * "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq" "2500000"
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
+ */
+public class CpuFrequencies {
+ private static final String TAG = "CpuFrequencies";
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<Integer, Long> mCoreAndFrequencies = new ArrayMap<>();
+
+ public CpuFrequencies() {
+ }
+
+ /**
+ * Parse a string.
+ */
+ public CpuFrequencies parseString(String cpuNumberAndFrequencies) {
+ synchronized (mLock) {
+ mCoreAndFrequencies.clear();
+ try {
+ for (String pair : cpuNumberAndFrequencies.split("/")) {
+ final String[] coreAndFreq = pair.split(":", 2);
+
+ if (coreAndFreq.length != 2) {
+ throw new IllegalArgumentException("Wrong format");
+ }
+ final int core = Integer.parseInt(coreAndFreq[0]);
+ final long freq = Long.parseLong(coreAndFreq[1]);
+
+ mCoreAndFrequencies.put(core, freq);
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Invalid configuration: " + cpuNumberAndFrequencies, e);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Return a new map containing the filename-value pairs.
+ */
+ public ArrayMap<String, String> toSysFileMap() {
+ final ArrayMap<String, String> map = new ArrayMap<>();
+ addToSysFileMap(map);
+ return map;
+ }
+
+ /**
+ * Add the filename-value pairs to an existing map.
+ */
+ public void addToSysFileMap(Map<String, String> map) {
+ synchronized (mLock) {
+ final int size = mCoreAndFrequencies.size();
+
+ for (int i = 0; i < size; i++) {
+ final int core = mCoreAndFrequencies.keyAt(i);
+ final long freq = mCoreAndFrequencies.valueAt(i);
+
+ final String file = "/sys/devices/system/cpu/cpu" + Integer.toString(core) +
+ "/cpufreq/scaling_max_freq";
+
+ map.put(file, Long.toString(freq));
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
index cfe8fc490e0a..cc1b540e669b 100644
--- a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
+++ b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
@@ -16,40 +16,259 @@
package com.android.server.power.batterysaver;
import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IoThread;
+
+import libcore.io.IoUtils;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+
/**
* Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files
- * with retry and to restore the original values.
+ * with retries. It also support restoring to the file original values.
*
- * TODO Implement it
+ * Retries are needed because writing to "/sys/.../scaling_max_freq" returns EIO when the current
+ * frequency happens to be above the new max frequency.
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
*/
public class FileUpdater {
private static final String TAG = BatterySaverController.TAG;
private static final boolean DEBUG = BatterySaverController.DEBUG;
+ // Don't do disk access with this lock held.
private final Object mLock = new Object();
+
private final Context mContext;
+ private final Handler mHandler;
+
+ /**
+ * Filename -> value map that holds pending writes.
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<String, String> mPendingWrites = new ArrayMap<>();
+
+ /**
+ * Filename -> value that holds the original value of each file.
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<String, String> mDefaultValues = new ArrayMap<>();
+
+ /** Number of retries. We give up on writing after {@link #MAX_RETRIES} retries. */
+ @GuardedBy("mLock")
+ private int mRetries = 0;
+
+ private final int MAX_RETRIES;
+
+ private final long RETRY_INTERVAL_MS;
+
+ /**
+ * "Official" constructor. Don't use the other constructor in the production code.
+ */
public FileUpdater(Context context) {
+ this(context, IoThread.get().getLooper(), 10, 5000);
+ }
+
+ /**
+ * Constructor for test.
+ */
+ @VisibleForTesting
+ FileUpdater(Context context, Looper looper, int maxRetries, int retryIntervalMs) {
mContext = context;
+ mHandler = new Handler(looper);
+
+ MAX_RETRIES = maxRetries;
+ RETRY_INTERVAL_MS = retryIntervalMs;
}
+ /**
+ * Write values to files. (Note the actual writes happen ASAP but asynchronously.)
+ */
public void writeFiles(ArrayMap<String, String> fileValues) {
- if (DEBUG) {
- final int size = fileValues.size();
- for (int i = 0; i < size; i++) {
- Slog.d(TAG, "Writing '" + fileValues.valueAt(i)
- + "' to '" + fileValues.keyAt(i) + "'");
+ synchronized (mLock) {
+ for (int i = fileValues.size() - 1; i >= 0; i--) {
+ final String file = fileValues.keyAt(i);
+ final String value = fileValues.valueAt(i);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduling write: '" + value + "' to '" + file + "'");
+ }
+
+ mPendingWrites.put(file, value);
+
}
+ mRetries = 0;
+
+ mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+ mHandler.post(mHandleWriteOnHandlerRunnable);
}
}
+ /**
+ * Restore the default values.
+ */
public void restoreDefault() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Resetting file default values.");
+ }
+ mPendingWrites.clear();
+
+ writeFiles(mDefaultValues);
+ }
+ }
+
+ private Runnable mHandleWriteOnHandlerRunnable = () -> handleWriteOnHandler();
+
+ /** Convert map keys into a single string for debug messages. */
+ private String getKeysString(Map<String, String> source) {
+ return new ArrayList<>(source.keySet()).toString();
+ }
+
+ /** Clone an ArrayMap. */
+ private ArrayMap<String, String> cloneMap(ArrayMap<String, String> source) {
+ return new ArrayMap<>(source);
+ }
+
+ /**
+ * Called on the handler and writes {@link #mPendingWrites} to the disk.
+ *
+ * When it about to write to each file for the first time, it'll read the file and store
+ * the original value in {@link #mDefaultValues}.
+ */
+ private void handleWriteOnHandler() {
+ // We don't want to access the disk with the lock held, so copy the pending writes to
+ // a local map.
+ final ArrayMap<String, String> writes;
+ synchronized (mLock) {
+ if (mPendingWrites.size() == 0) {
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Writing files: (# retries=" + mRetries + ") " +
+ getKeysString(mPendingWrites));
+ }
+
+ writes = cloneMap(mPendingWrites);
+ }
+
+ // Then write.
+
+ boolean needRetry = false;
+
+ final int size = writes.size();
+ for (int i = 0; i < size; i++) {
+ final String file = writes.keyAt(i);
+ final String value = writes.valueAt(i);
+
+ // Make sure the default value is loaded.
+ if (!ensureDefaultLoaded(file)) {
+ continue;
+ }
+
+ // Write to the file. When succeeded, remove it from the pending list.
+ // Otherwise, schedule a retry.
+ try {
+ injectWriteToFile(file, value);
+
+ removePendingWrite(file);
+ } catch (IOException e) {
+ needRetry = true;
+ }
+ }
+ if (needRetry) {
+ scheduleRetry();
+ }
+ }
+
+ private void removePendingWrite(String file) {
+ synchronized (mLock) {
+ mPendingWrites.remove(file);
+ }
+ }
+
+ private void scheduleRetry() {
+ synchronized (mLock) {
+ if (mPendingWrites.size() == 0) {
+ return; // Shouldn't happen but just in case.
+ }
+
+ mRetries++;
+ if (mRetries > MAX_RETRIES) {
+ doWtf("Gave up writing files: " + getKeysString(mPendingWrites));
+ return;
+ }
+
+ mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+ mHandler.postDelayed(mHandleWriteOnHandlerRunnable, RETRY_INTERVAL_MS);
+ }
+ }
+
+ /**
+ * Make sure {@link #mDefaultValues} has the default value loaded for {@code file}.
+ *
+ * @return true if the default value is loaded. false if the file cannot be read.
+ */
+ private boolean ensureDefaultLoaded(String file) {
+ // Has the default already?
+ synchronized (mLock) {
+ if (mDefaultValues.containsKey(file)) {
+ return true;
+ }
+ }
+ final String originalValue;
+ try {
+ originalValue = injectReadFromFileTrimmed(file);
+ } catch (IOException e) {
+ // If the file is not readable, assume can't write too.
+ injectWtf("Unable to read from file", e);
+
+ removePendingWrite(file);
+ return false;
+ }
+ synchronized (mLock) {
+ mDefaultValues.put(file, originalValue);
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ String injectReadFromFileTrimmed(String file) throws IOException {
+ return IoUtils.readFileAsString(file).trim();
+ }
+
+ @VisibleForTesting
+ void injectWriteToFile(String file, String value) throws IOException {
if (DEBUG) {
- Slog.d(TAG, "Resetting file default values");
+ Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'");
+ }
+ try (FileWriter out = new FileWriter(file)) {
+ out.write(value);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage());
+ throw e;
}
}
+
+ private void doWtf(String message) {
+ injectWtf(message, null);
+ }
+
+ @VisibleForTesting
+ void injectWtf(String message, Throwable e) {
+ Slog.wtf(TAG, message, e);
+ }
}
diff --git a/services/core/java/com/android/server/power/batterysaver/OWNERS b/services/core/java/com/android/server/power/batterysaver/OWNERS
new file mode 100644
index 000000000000..09136dcc2feb
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/OWNERS
@@ -0,0 +1 @@
+omakoto@google.com
diff --git a/services/core/java/com/android/server/utils/AppInstallerUtil.java b/services/core/java/com/android/server/utils/AppInstallerUtil.java
new file mode 100644
index 000000000000..af7ff41f4455
--- /dev/null
+++ b/services/core/java/com/android/server/utils/AppInstallerUtil.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.util.Log;
+
+public class AppInstallerUtil {
+ private static final String LOG_TAG = "AppInstallerUtil";
+
+ private static Intent resolveIntent(Context context, Intent i) {
+ ResolveInfo result = context.getPackageManager().resolveActivity(i, 0);
+ return result != null ? new Intent(i.getAction())
+ .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
+ }
+
+ /**
+ * Returns the package name of the app which installed a given packageName, if available.
+ */
+ public static String getInstallerPackageName(Context context, String packageName) {
+ String installerPackageName = null;
+ try {
+ installerPackageName =
+ context.getPackageManager().getInstallerPackageName(packageName);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e);
+ }
+ if (installerPackageName == null) {
+ return null;
+ }
+ return installerPackageName;
+ }
+
+ /**
+ * Returns an intent to launcher the installer for a given package name.
+ */
+ public static Intent createIntent(Context context, String installerPackageName,
+ String packageName) {
+ Intent intent = new Intent(Intent.ACTION_SHOW_APP_INFO).setPackage(installerPackageName);
+ final Intent result = resolveIntent(context, intent);
+ if (result != null) {
+ result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ return result;
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method that looks up the installerPackageName.
+ */
+ public static Intent createIntent(Context context, String packageName) {
+ String installerPackageName = getInstallerPackageName(context, packageName);
+ return createIntent(context, installerPackageName, packageName);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 465653938672..e123bef133d6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7455,6 +7455,11 @@ public class WindowManagerService extends IWindowManager.Stub
public void registerDragDropControllerCallback(IDragDropCallback callback) {
mDragDropController.registerCallback(callback);
}
+
+ @Override
+ public void lockNow() {
+ WindowManagerService.this.lockNow(null);
+ }
}
void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 52b7a25684f1..730ec3795ac0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2109,7 +2109,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (task != null) {
return task.getDimmer();
}
- return getStack().getDimmer();
+ TaskStack taskStack = getStack();
+ if (taskStack != null) {
+ return taskStack.getDimmer();
+ }
+ return null;
}
/** Returns true if the replacement window was removed. */
@@ -4390,10 +4394,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mToken.makeChildSurface(this);
}
-
- @Override
- void prepareSurfaces() {
- mIsDimming = false;
+ private void applyDims(Dimmer dimmer) {
if (!mAnimatingExit && mAppDied) {
mIsDimming = true;
getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
@@ -4402,6 +4403,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mIsDimming = true;
getDimmer().dimBelow(getPendingTransaction(), this, mAttrs.dimAmount);
}
+ }
+
+ @Override
+ void prepareSurfaces() {
+ final Dimmer dimmer = getDimmer();
+ mIsDimming = false;
+ if (dimmer != null) {
+ applyDims(dimmer);
+ }
mWinAnimator.prepareSurfaceLocked(true);
super.prepareSurfaces();
diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
new file mode 100644
index 000000000000..54d233a94815
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.backup.IBackupTransport;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 23)
+@Presubmit
+public class TransportClientTest {
+ private static final String PACKAGE_NAME = "some.package.name";
+ private static final ComponentName TRANSPORT_COMPONENT =
+ new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport");
+
+ @Mock private Context mContext;
+ @Mock private TransportConnectionListener mTransportConnectionListener;
+ @Mock private TransportConnectionListener mTransportConnectionListener2;
+ @Mock private IBackupTransport.Stub mIBackupTransport;
+ private TransportClient mTransportClient;
+ private Intent mBindIntent;
+ private ShadowLooper mShadowLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ Looper mainLooper = Looper.getMainLooper();
+ mShadowLooper = shadowOf(mainLooper);
+ mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(TRANSPORT_COMPONENT);
+ mTransportClient =
+ new TransportClient(
+ mContext, mBindIntent, TRANSPORT_COMPONENT, "1", new Handler(mainLooper));
+
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(true);
+ }
+
+ // TODO: Testing implementation? Remove?
+ @Test
+ public void testConnectAsync_callsBindService() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ verify(mContext)
+ .bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class));
+ }
+
+ @Test
+ public void testConnectAsync_callsListenerWhenConnected() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ // Simulate framework connecting
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenPendingConnection_callsAllListenersWhenConnected()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
+
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenAlreadyConnected_callsListener() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenFrameworkDoesntBind_callsListener() throws Exception {
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(false);
+
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenFrameworkDoesntBind_releasesConnection() throws Exception {
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(false);
+
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ verify(mContext).unbindService(eq(connection));
+ }
+
+ @Test
+ public void testConnectAsync_afterServiceDisconnectedBeforeNewConnection_callsListener()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+ connection.onServiceDisconnected(TRANSPORT_COMPONENT);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
+
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_afterServiceDisconnectedAfterNewConnection_callsListener()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+ connection.onServiceDisconnected(TRANSPORT_COMPONENT);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
+
+ // Yes, it should return null because the object became unusable, check design doc
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ // TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests
+ /*@Test
+ public void testConnectAsync_callsListenerIfBindingDies() throws Exception {
+ mTransportClient.connectAsync(mTransportListener, "caller");
+
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onBindingDied(TRANSPORT_COMPONENT);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+
+ mTransportClient.connectAsync(mTransportListener2, "caller2");
+
+ connection.onBindingDied(TRANSPORT_COMPONENT);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+ verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient));
+ }*/
+
+ private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
+ ArgumentCaptor<ServiceConnection> connectionCaptor =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ verify(context)
+ .bindServiceAsUser(
+ any(Intent.class),
+ connectionCaptor.capture(),
+ anyInt(),
+ any(UserHandle.class));
+ return connectionCaptor.getValue();
+ }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index c145e828c49c..55ec133a29da 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -16,8 +16,10 @@
package com.android.server.notification;
+import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -940,6 +942,120 @@ public class NotificationManagerServiceTest extends NotificationTestCase {
}
@Test
+ public void testUpdateChannelNotifyCreatorBlock() throws Exception {
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(mTestNotificationChannel);
+
+ NotificationChannel updatedChannel =
+ new NotificationChannel(mTestNotificationChannel.getId(),
+ mTestNotificationChannel.getName(), IMPORTANCE_NONE);
+
+ mBinderService.updateNotificationChannelForPackage(PKG, 0, updatedChannel);
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertEquals(mTestNotificationChannel.getId(), captor.getValue().getStringExtra(
+ NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+ assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+ }
+
+ @Test
+ public void testUpdateChannelNotifyCreatorUnblock() throws Exception {
+ NotificationChannel existingChannel =
+ new NotificationChannel(mTestNotificationChannel.getId(),
+ mTestNotificationChannel.getName(), IMPORTANCE_NONE);
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(existingChannel);
+
+ mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel);
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertEquals(mTestNotificationChannel.getId(), captor.getValue().getStringExtra(
+ NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+ assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+ }
+
+ @Test
+ public void testUpdateChannelNoNotifyCreatorOtherChanges() throws Exception {
+ NotificationChannel existingChannel =
+ new NotificationChannel(mTestNotificationChannel.getId(),
+ mTestNotificationChannel.getName(), IMPORTANCE_MAX);
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(existingChannel);
+
+ mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel);
+ verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
+ }
+
+ @Test
+ public void testUpdateGroupNotifyCreatorBlock() throws Exception {
+ NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ .thenReturn(existing);
+
+ NotificationChannelGroup updated = new NotificationChannelGroup("id", "name");
+ updated.setBlocked(true);
+
+ mBinderService.updateNotificationChannelGroupForPackage(PKG, 0, updated);
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertEquals(existing.getId(), captor.getValue().getStringExtra(
+ NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+ assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+ }
+
+ @Test
+ public void testUpdateGroupNotifyCreatorUnblock() throws Exception {
+ NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
+ existing.setBlocked(true);
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ .thenReturn(existing);
+
+ mBinderService.updateNotificationChannelGroupForPackage(
+ PKG, 0, new NotificationChannelGroup("id", "name"));
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertEquals(existing.getId(), captor.getValue().getStringExtra(
+ NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+ assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+ }
+
+ @Test
+ public void testUpdateGroupNoNotifyCreatorOtherChanges() throws Exception {
+ NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ .thenReturn(existing);
+
+ mBinderService.updateNotificationChannelGroupForPackage(
+ PKG, 0, new NotificationChannelGroup("id", "new name"));
+ verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
+ }
+
+ @Test
public void testCreateChannelNotifyListener() throws Exception {
List<String> associations = new ArrayList<>();
associations.add("a");
@@ -1040,6 +1156,9 @@ public class NotificationManagerServiceTest extends NotificationTestCase {
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+ when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(mTestNotificationChannel);
mBinderService.updateNotificationChannelFromPrivilegedListener(
null, PKG, Process.myUserHandle(), mTestNotificationChannel);
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 3c02e23eed2a..2d03f111e528 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -372,7 +372,7 @@ public class RankingHelperTest extends NotificationTestCase {
mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
List<NotificationChannelGroup> actualGroups =
- mHelper.getNotificationChannelGroups(PKG, UID, false).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
boolean foundNcg = false;
for (NotificationChannelGroup actual : actualGroups) {
if (ncg.getId().equals(actual.getId())) {
@@ -442,7 +442,7 @@ public class RankingHelperTest extends NotificationTestCase {
mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
List<NotificationChannelGroup> actualGroups =
- mHelper.getNotificationChannelGroups(PKG, UID, false).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
boolean foundNcg = false;
for (NotificationChannelGroup actual : actualGroups) {
if (ncg.getId().equals(actual.getId())) {
@@ -1281,7 +1281,8 @@ public class RankingHelperTest extends NotificationTestCase {
mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(0, mHelper.getNotificationChannelGroups(PKG, UID, true).getList().size());
+ assertEquals(0,
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList().size());
}
@Test
@@ -1370,7 +1371,7 @@ public class RankingHelperTest extends NotificationTestCase {
mHelper.createNotificationChannel(PKG, UID, channel3, true);
List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
assertEquals(3, actual.size());
for (NotificationChannelGroup group : actual) {
if (group.getId() == null) {
@@ -1402,13 +1403,13 @@ public class RankingHelperTest extends NotificationTestCase {
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
mHelper.createNotificationChannel(PKG, UID, channel1, true);
- mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
channel1.setImportance(IMPORTANCE_LOW);
mHelper.updateNotificationChannel(PKG, UID, channel1, true);
List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
assertEquals(2, actual.size());
for (NotificationChannelGroup group : actual) {
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml
index 3ac56bb5d8bc..57da0af42a88 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/strings.xml
@@ -30,6 +30,6 @@
<string name="test_account_type2">com.android.server.accounts.account_manager_service_test.account.type2</string>
<string name="config_batterySaverDeviceSpecificConfig_1"></string>
- <string name="config_batterySaverDeviceSpecificConfig_2">file-off:/sys/a=1,file-off:/sys/b=2</string>
- <string name="config_batterySaverDeviceSpecificConfig_3">file-off:/sys/a=3,file-on:/proc/c=4,/abc=3</string>
+ <string name="config_batterySaverDeviceSpecificConfig_2">cpufreq-n=1:123/2:456</string>
+ <string name="config_batterySaverDeviceSpecificConfig_3">cpufreq-n=2:222,cpufreq-i=3:333/4:444</string>
</resources>
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index f9933fb63f12..bc503c49a99a 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -95,8 +95,7 @@ public class ActivityStarterTests extends ActivityTestsBase {
public void setUp() throws Exception {
super.setUp();
mService = createActivityManagerService();
- mPackageManager = mock(IPackageManager.class);
- mStarter = new ActivityStarter(mService, mPackageManager);
+ mStarter = new ActivityStarter(mService);
}
@Test
@@ -178,7 +177,7 @@ public class ActivityStarterTests extends ActivityTestsBase {
int expectedResult) {
final ActivityManagerService service = createActivityManagerService();
final IPackageManager packageManager = mock(IPackageManager.class);
- final ActivityStarter starter = new ActivityStarter(service, packageManager);
+ final ActivityStarter starter = new ActivityStarter(service);
final IApplicationThread caller = mock(IApplicationThread.class);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 9c949ad34844..9683e229d50f 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -36,6 +36,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
@@ -90,6 +91,7 @@ public class ActivityTestsBase {
protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) {
service = spy(service);
+ doReturn(mock(IPackageManager.class)).when(service).getPackageManager();
service.mWindowManager = prepareMockWindowManager();
return service;
}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index d9fac8768c6b..6938e0f681cd 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -99,10 +99,12 @@ public class BrightnessTrackerTest {
assertNotNull(mInjector.mSensorListener);
assertNotNull(mInjector.mSettingsObserver);
assertNotNull(mInjector.mBroadcastReceiver);
+ assertTrue(mInjector.mIdleScheduled);
mTracker.stop();
assertNull(mInjector.mSensorListener);
assertNull(mInjector.mSettingsObserver);
assertNull(mInjector.mBroadcastReceiver);
+ assertFalse(mInjector.mIdleScheduled);
}
@Test
@@ -399,6 +401,52 @@ public class BrightnessTrackerTest {
}
@Test
+ public void testWritePrunesOldEvents() throws Exception {
+ final int brightness = 20;
+
+ mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness);
+ mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
+ mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
+
+ startTracker(mTracker);
+ mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+ batteryChangeEvent(30, 100));
+ mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
+ mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
+ mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
+ final long sensorTime = mInjector.currentTimeMillis();
+ mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
+ Settings.System.SCREEN_BRIGHTNESS));
+
+ // 31 days later
+ mInjector.incrementTime(TimeUnit.DAYS.toMillis(31));
+ mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
+ mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
+ Settings.System.SCREEN_BRIGHTNESS));
+ final long eventTime = mInjector.currentTimeMillis();
+
+ List<BrightnessChangeEvent> events = mTracker.getEvents(0).getList();
+ assertEquals(2, events.size());
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mTracker.writeEventsLocked(baos);
+ events = mTracker.getEvents(0).getList();
+ mTracker.stop();
+
+ assertEquals(1, events.size());
+ BrightnessChangeEvent event = events.get(0);
+ assertEquals(eventTime, event.timeStamp);
+
+ // We will keep one of the old sensor events because we keep 1 event outside the window.
+ assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, 0.01f);
+ assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps);
+ assertEquals(brightness, event.brightness);
+ assertEquals(0.3, event.batteryLevel, 0.01f);
+ assertTrue(event.nightMode);
+ assertEquals(3339, event.colorTemperature);
+ }
+
+ @Test
public void testParcelUnParcel() {
Parcel parcel = Parcel.obtain();
BrightnessChangeEvent event = new BrightnessChangeEvent();
@@ -516,6 +564,7 @@ public class BrightnessTrackerTest {
long mCurrentTimeMillis = System.currentTimeMillis();
long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
Handler mHandler;
+ boolean mIdleScheduled;
public TestInjector(Handler handler) {
mHandler = handler;
@@ -636,5 +685,14 @@ public class BrightnessTrackerTest {
focusedStack.topActivity = new ComponentName("a.package", "a.class");
return focusedStack;
}
+
+ public void scheduleIdleJob(Context context) {
+ // Don't actually schedule jobs during unit tests.
+ mIdleScheduled = true;
+ }
+
+ public void cancelIdleJob(Context context) {
+ mIdleScheduled = false;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index fa8feb048fd6..1f9a243d0f22 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -197,6 +197,8 @@ public class PackageParserTest {
assertEquals(a.coreApp, b.coreApp);
assertEquals(a.mRequiredForAllUsers, b.mRequiredForAllUsers);
assertEquals(a.mTrustedOverlay, b.mTrustedOverlay);
+ assertEquals(a.mCompileSdkVersion, b.mCompileSdkVersion);
+ assertEquals(a.mCompileSdkVersionCodename, b.mCompileSdkVersionCodename);
assertEquals(a.use32bitAbi, b.use32bitAbi);
assertEquals(a.packageName, b.packageName);
assertTrue(Arrays.equals(a.splitNames, b.splitNames));
diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
index 0db19e452650..20cf733a860b 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
@@ -237,21 +237,27 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
mBatterySaverPolicy.onChange();
assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}");
assertThat(mBatterySaverPolicy.getFileValues(false).toString())
- .isEqualTo("{/sys/a=1, /sys/b=2}");
-
+ .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("{/proc/c=4}");
- assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{/sys/a=3}");
+ 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}");
+ assertThat(mBatterySaverPolicy.getFileValues(false).toString())
+ .isEqualTo("{/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=222}");
mMockGlobalSettings.put(Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
- "file-on:/proc/z=4");
+ "cpufreq-i=3:1234567890/4:014/5:015");
mBatterySaverPolicy.onChange();
- assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{/proc/z=4}");
+ 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}");
assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
new file mode 100644
index 000000000000..f72ec3411461
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.power.batterysaver;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CpuFrequenciesTest {
+ private void check(ArrayMap<String, String> expected, String config) {
+ assertEquals(expected, (new CpuFrequencies().parseString(config))
+ .toSysFileMap());
+ }
+
+ @Test
+ public void test() {
+ check(new ArrayMap<>(), "");
+
+ final ArrayMap<String, String> expected = new ArrayMap<>();
+
+ expected.clear();
+ expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "0");
+ check(expected, "0:0");
+
+ expected.clear();
+ expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "0");
+ expected.put("/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq", "1");
+ check(expected, "0:0/1:1");
+
+ expected.clear();
+ expected.put("/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq", "0");
+ expected.put("/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq", "1234567890");
+ check(expected, "2:0/1:1234567890");
+
+ expected.clear();
+ expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "1900800");
+ expected.put("/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq", "1958400");
+ check(expected, "0:1900800/4:1958400");
+
+ check(expected, "0:1900800/4:1958400/"); // Shouldn't crash.
+ check(expected, "0:1900800/4:1958400/1"); // Shouldn't crash.
+ check(expected, "0:1900800/4:1958400/a:1"); // Shouldn't crash.
+ check(expected, "0:1900800/4:1958400/1:"); // Shouldn't crash.
+ check(expected, "0:1900800/4:1958400/1:b"); // Shouldn't crash.
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
new file mode 100644
index 000000000000..7e2a7d221c22
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.batterysaver;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FileUpdaterTest {
+
+ private class FileUpdaterTestable extends FileUpdater {
+ FileUpdaterTestable(Context context, Looper looper, int maxRetries, int retryIntervalMs) {
+ super(context, looper, maxRetries, retryIntervalMs);
+ }
+
+ @Override
+ String injectReadFromFileTrimmed(String file) throws IOException {
+ return mInjector.injectReadFromFileTrimmed(file);
+ }
+
+ @Override
+ void injectWriteToFile(String file, String value) throws IOException {
+ mInjector.injectWriteToFile(file, value);
+ }
+
+ @Override
+ void injectWtf(String message, Throwable e) {
+ mInjector.injectWtf(message, e);
+ }
+ }
+
+ private interface Injector {
+ String injectReadFromFileTrimmed(String file) throws IOException;
+ void injectWriteToFile(String file, String value) throws IOException;
+ void injectWtf(String message, Throwable e);
+ }
+
+ private Handler mMainHandler;
+
+ @Mock
+ private Injector mInjector;
+
+ private static final int MAX_RETRIES = 3;
+
+ private FileUpdaterTestable mInstance;
+
+ public static <T> T anyOrNull(Class<T> clazz) {
+ return ArgumentMatchers.argThat(value -> true);
+ }
+
+ public static String anyOrNullString() {
+ return ArgumentMatchers.argThat(value -> true);
+ }
+
+ @Before
+ public void setUp() {
+ mMainHandler = new Handler(Looper.getMainLooper());
+
+ MockitoAnnotations.initMocks(this);
+
+ mInstance = newInstance();
+ }
+
+ private FileUpdaterTestable newInstance() {
+ return new FileUpdaterTestable(
+ InstrumentationRegistry.getContext(),
+ Looper.getMainLooper(),
+ MAX_RETRIES,
+ 0 /* retry with no delays*/);
+ }
+
+ private void waitUntilMainHandlerDrain() throws Exception {
+ final CountDownLatch l = new CountDownLatch(1);
+ mMainHandler.post(() -> l.countDown());
+ assertTrue(l.await(5, TimeUnit.SECONDS));
+ }
+
+ private void veriryWtf(int times) {
+ verify(mInjector, times(times)).injectWtf(anyOrNullString(), anyOrNull(Throwable.class));
+ }
+
+ @Test
+ public void testNoWrites() throws Exception {
+ doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+ doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(0)).injectWriteToFile(anyOrNullString(), anyOrNullString());
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(0)).injectWriteToFile(anyOrNullString(), anyOrNullString());
+
+ // No WTF should have happened.
+ veriryWtf(0);
+ }
+
+ @Test
+ public void testSimpleWrite() throws Exception {
+ doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+ doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+ values.put("file1", "11");
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+
+ // No WTF should have happened.
+ veriryWtf(0);
+ }
+
+ @Test
+ public void testMultiWrites() throws Exception {
+ doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+ doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+ values.put("file1", "11");
+ values.put("file2", "22");
+ values.put("file3", "33");
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+ verify(mInjector, times(1)).injectWriteToFile("file3", "33");
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+ verify(mInjector, times(1)).injectWriteToFile("file3", "333");
+
+ // No WTF should have happened.
+ veriryWtf(0);
+ }
+
+ @Test
+ public void testCantReadDefault() throws Exception {
+ doThrow(new IOException("can't read")).when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+ values.put("file1", "11");
+ values.put("file2", "22");
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(0)).injectWriteToFile("file1", "11");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+ veriryWtf(1);
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(0)).injectWriteToFile("file1", "111");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+
+ veriryWtf(1);
+ }
+
+ @Test
+ public void testWriteGiveUp() throws Exception {
+ doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+ doReturn("333").when(mInjector).injectReadFromFileTrimmed("fail1");
+
+ doThrow(new IOException("can't write")).when(mInjector).injectWriteToFile(
+ eq("fail1"), eq("33"));
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+ values.put("file1", "11");
+ values.put("file2", "22");
+ values.put("fail1", "33");
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+ verify(mInjector, times(MAX_RETRIES + 1)).injectWriteToFile("fail1", "33");
+
+ // 1 WTF.
+ veriryWtf(1);
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+
+ verify(mInjector, times(1)).injectWriteToFile("fail1", "333");
+
+ // No further WTF.
+ veriryWtf(1);
+ }
+
+ @Test
+ public void testSuccessWithRetry() throws Exception {
+ doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+ doReturn("333").when(mInjector).injectReadFromFileTrimmed("fail1");
+
+ final AtomicInteger counter = new AtomicInteger();
+ doAnswer((inv) -> {
+ if (counter.getAndIncrement() <= 1) {
+ throw new IOException();
+ }
+ return null;
+ }).when(mInjector).injectWriteToFile(eq("fail1"), eq("33"));
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+ values.put("file1", "11");
+ values.put("file2", "22");
+ values.put("fail1", "33");
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+ // Should succeed after 2 retries.
+ verify(mInjector, times(3)).injectWriteToFile("fail1", "33");
+
+ // No WTF.
+ veriryWtf(0);
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+ verify(mInjector, times(1)).injectWriteToFile("fail1", "333");
+
+ // Still no WTF.
+ veriryWtf(0);
+ }
+
+ @Test
+ public void testAll() throws Exception {
+ // Run multiple tests on the single target instance.
+
+ reset(mInjector);
+ testSimpleWrite();
+
+ reset(mInjector);
+ testWriteGiveUp();
+
+ reset(mInjector);
+ testMultiWrites();
+
+ reset(mInjector);
+ testSuccessWithRetry();
+
+ reset(mInjector);
+ testMultiWrites();
+ }
+}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
index 011817e99714..884ba70b90f4 100644
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
+++ b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
@@ -27,12 +27,11 @@ import android.util.Log;
public class TestJobActivity extends Activity {
private static final String TAG = TestJobActivity.class.getSimpleName();
- public static final String EXTRA_JOB_ID_KEY =
- "com.android.servicestests.apps.jobtestapp.extra.JOB_ID";
- public static final String ACTION_START_JOB =
- "com.android.servicestests.apps.jobtestapp.action.START_JOB";
- public static final String ACTION_CANCEL_JOBS =
- "com.android.servicestests.apps.jobtestapp.action.CANCEL_JOBS";
+ private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
+
+ public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
+ public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB";
+ public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
public static final int JOB_INITIAL_BACKOFF = 10_000;
public static final int JOB_MINIMUM_LATENCY = 5_000;
@@ -59,6 +58,8 @@ public class TestJobActivity extends Activity {
Log.d(TAG, "Successfully scheduled job with id " + jobId);
}
break;
+ default:
+ Log.e(TAG, "Unknown action " + intent.getAction());
}
finish();
}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 095fdc63975c..9bc9cd04957c 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -323,7 +323,8 @@ public class UsbHostManager {
}
/* Opens the specified USB device */
- public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) {
+ public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings,
+ String packageName, int uid) {
synchronized (mLock) {
if (isBlackListed(deviceName)) {
throw new SecurityException("USB device is on a restricted bus");
@@ -334,7 +335,7 @@ public class UsbHostManager {
throw new IllegalArgumentException(
"device " + deviceName + " does not exist or is restricted");
}
- settings.checkPermission(device);
+ settings.checkPermission(device, packageName, uid);
return nativeOpenDevice(deviceName);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index e4fcea77fa44..17de83f0cac9 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -232,7 +232,7 @@ public class UsbService extends IUsbManager.Stub {
/* Opens the specified USB device (host mode) */
@Override
- public ParcelFileDescriptor openDevice(String deviceName) {
+ public ParcelFileDescriptor openDevice(String deviceName, String packageName) {
ParcelFileDescriptor fd = null;
if (mHostManager != null) {
@@ -242,7 +242,8 @@ public class UsbService extends IUsbManager.Stub {
boolean isCurrentUser = isCallerInCurrentUserProfileGroupLocked();
if (isCurrentUser) {
- fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt));
+ fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt),
+ packageName, Binder.getCallingUid());
} else {
Slog.w(TAG, "Cannot open " + deviceName + " for user " + userIdInt +
" as user is not active.");
@@ -308,9 +309,10 @@ public class UsbService extends IUsbManager.Stub {
}
@Override
- public boolean hasDevicePermission(UsbDevice device) {
+ public boolean hasDevicePermission(UsbDevice device, String packageName) {
final int userId = UserHandle.getCallingUserId();
- return getSettingsForUser(userId).hasPermission(device);
+ return getSettingsForUser(userId).hasPermission(device, packageName,
+ Binder.getCallingUid());
}
@Override
@@ -322,7 +324,8 @@ public class UsbService extends IUsbManager.Stub {
@Override
public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
final int userId = UserHandle.getCallingUserId();
- getSettingsForUser(userId).requestPermission(device, packageName, pi);
+ getSettingsForUser(userId).requestPermission(device, packageName, pi,
+ Binder.getCallingUid());
}
@Override
diff --git a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
index 96c5211cecf4..11e43e308e8a 100644
--- a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
@@ -26,6 +26,8 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbManager;
import android.os.Binder;
import android.os.Process;
@@ -95,10 +97,70 @@ class UsbUserSettingsManager {
}
}
+ /**
+ * Check whether a particular device or any of its interfaces
+ * is of class VIDEO.
+ *
+ * @param device The device that needs to get scanned
+ * @return True in case a VIDEO device or interface is present,
+ * False otherwise.
+ */
+ private boolean isCameraDevicePresent(UsbDevice device) {
+ if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) {
+ return true;
+ }
+
+ for (int i = 0; i < device.getInterfaceCount(); i++) {
+ UsbInterface iface = device.getInterface(i);
+ if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check for camera permission of the calling process.
+ *
+ * @param packageName Package name of the caller.
+ * @param uid Linux uid of the calling process.
+ *
+ * @return True in case camera permission is available, False otherwise.
+ */
+ private boolean isCameraPermissionGranted(String packageName, int uid) {
+ int targetSdkVersion = android.os.Build.VERSION_CODES.P;
+ try {
+ ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
+ // compare uid with packageName to foil apps pretending to be someone else
+ if (aInfo.uid != uid) {
+ Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid);
+ return false;
+ }
+ targetSdkVersion = aInfo.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.i(TAG, "Package not found, likely due to invalid package name!");
+ return false;
+ }
+
+ if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) {
+ int allowed = mUserContext.checkCallingPermission(android.Manifest.permission.CAMERA);
+ if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) {
+ Slog.i(TAG, "Camera permission required for USB video class devices");
+ return false;
+ }
+ }
- public boolean hasPermission(UsbDevice device) {
+ return true;
+ }
+
+ public boolean hasPermission(UsbDevice device, String packageName, int uid) {
synchronized (mLock) {
- int uid = Binder.getCallingUid();
+ if (isCameraDevicePresent(device)) {
+ if (!isCameraPermissionGranted(packageName, uid)) {
+ return false;
+ }
+ }
if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
return true;
}
@@ -124,8 +186,8 @@ class UsbUserSettingsManager {
}
}
- public void checkPermission(UsbDevice device) {
- if (!hasPermission(device)) {
+ public void checkPermission(UsbDevice device, String packageName, int uid) {
+ if (!hasPermission(device, packageName, uid)) {
throw new SecurityException("User has not given permission to device " + device);
}
}
@@ -166,11 +228,11 @@ class UsbUserSettingsManager {
}
}
- public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
+ public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) {
Intent intent = new Intent();
// respond immediately if permission has already been granted
- if (hasPermission(device)) {
+ if (hasPermission(device, packageName, uid)) {
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
try {
@@ -180,6 +242,18 @@ class UsbUserSettingsManager {
}
return;
}
+ if (isCameraDevicePresent(device)) {
+ if (!isCameraPermissionGranted(packageName, uid)) {
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
+ try {
+ pi.send(mUserContext, 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
+ }
+ return;
+ }
+ }
// start UsbPermissionActivity so user can choose an activity
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 132b234ab1e4..ffcef8966654 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -346,22 +346,20 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) {
can_be_conditional &= CollectLocations(location, keep_set, &locations);
}
- for (const UsageLocation& location : entry.second) {
- printer.Print("# Referenced at ").Println(location.source.to_string());
- }
if (keep_set.conditional_keep_rules_ && can_be_conditional) {
- printer.Println("-if class **.R$layout {");
- printer.Indent();
for (const UsageLocation& location : locations) {
- printer.Print("int ")
+ printer.Print("# Referenced at ").Println(location.source.to_string());
+ printer.Print("-if class **.R$layout { int ")
.Print(JavaClassGenerator::TransformToFieldName(location.name.entry))
- .Println(";");
+ .Println("; }");
+ printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
}
- printer.Undent();
- printer.Println("}");
- printer.Println();
+ } else {
+ for (const UsageLocation& location : entry.second) {
+ printer.Print("# Referenced at ").Println(location.source.to_string());
+ }
+ printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
}
- printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
printer.Println();
}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index c2f11d96b9f2..a9e1e9d06470 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -66,11 +66,11 @@ interface IWifiManager
List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult);
- int addOrUpdateNetwork(in WifiConfiguration config, String packageName);
+ int addOrUpdateNetwork(in WifiConfiguration config);
- boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config, String packageName);
+ boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config);
- boolean removePasspointConfiguration(in String fqdn, String packageName);
+ boolean removePasspointConfiguration(in String fqdn);
List<PasspointConfiguration> getPasspointConfigurations();
@@ -80,21 +80,21 @@ interface IWifiManager
void deauthenticateNetwork(long holdoff, boolean ess);
- boolean removeNetwork(int netId, String packageName);
+ boolean removeNetwork(int netId);
- boolean enableNetwork(int netId, boolean disableOthers, String packageName);
+ boolean enableNetwork(int netId, boolean disableOthers);
- boolean disableNetwork(int netId, String packageName);
+ boolean disableNetwork(int netId);
- void startScan(in ScanSettings requested, in WorkSource ws, String packageName);
+ void startScan(in ScanSettings requested, in WorkSource ws, in String packageName);
List<ScanResult> getScanResults(String callingPackage);
- void disconnect(String packageName);
+ void disconnect();
- void reconnect(String packageName);
+ void reconnect();
- void reassociate(String packageName);
+ void reassociate();
WifiInfo getConnectionInfo(String callingPackage);
@@ -108,7 +108,7 @@ interface IWifiManager
boolean isDualBandSupported();
- boolean saveConfiguration(String packageName);
+ boolean saveConfiguration();
DhcpInfo getDhcpInfo();
@@ -134,9 +134,9 @@ interface IWifiManager
boolean stopSoftAp();
- int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, String packageName);
+ int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, in String packageName);
- void stopLocalOnlyHotspot(String packageName);
+ void stopLocalOnlyHotspot();
void startWatchLocalOnlyHotspot(in Messenger messenger, in IBinder binder);
@@ -146,9 +146,9 @@ interface IWifiManager
WifiConfiguration getWifiApConfiguration();
- void setWifiApConfiguration(in WifiConfiguration wifiConfig, String packageName);
+ void setWifiApConfiguration(in WifiConfiguration wifiConfig);
- Messenger getWifiServiceMessenger(String packageName);
+ Messenger getWifiServiceMessenger();
void enableTdls(String remoteIPAddress, boolean enable);
@@ -166,16 +166,16 @@ interface IWifiManager
void setAllowScansWithTraffic(int enabled);
int getAllowScansWithTraffic();
- boolean setEnableAutoJoinWhenAssociated(boolean enabled, String packageName);
+ boolean setEnableAutoJoinWhenAssociated(boolean enabled);
boolean getEnableAutoJoinWhenAssociated();
void enableWifiConnectivityManager(boolean enabled);
WifiConnectionStatistics getConnectionStatistics();
- void disableEphemeralNetwork(String SSID, String packageName);
+ void disableEphemeralNetwork(String SSID);
- void factoryReset(String packageName);
+ void factoryReset();
Network getCurrentNetwork();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 66fabf33ddbb..183cf0d10d49 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1128,7 +1128,7 @@ public class WifiManager {
*/
private int addOrUpdateNetwork(WifiConfiguration config) {
try {
- return mService.addOrUpdateNetwork(config, mContext.getOpPackageName());
+ return mService.addOrUpdateNetwork(config);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1149,7 +1149,7 @@ public class WifiManager {
*/
public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
try {
- if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) {
+ if (!mService.addOrUpdatePasspointConfiguration(config)) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1166,7 +1166,7 @@ public class WifiManager {
*/
public void removePasspointConfiguration(String fqdn) {
try {
- if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) {
+ if (!mService.removePasspointConfiguration(fqdn)) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1252,7 +1252,7 @@ public class WifiManager {
*/
public boolean removeNetwork(int netId) {
try {
- return mService.removeNetwork(netId, mContext.getOpPackageName());
+ return mService.removeNetwork(netId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1298,7 +1298,7 @@ public class WifiManager {
boolean success;
try {
- success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName());
+ success = mService.enableNetwork(netId, attemptConnect);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1324,7 +1324,7 @@ public class WifiManager {
*/
public boolean disableNetwork(int netId) {
try {
- return mService.disableNetwork(netId, mContext.getOpPackageName());
+ return mService.disableNetwork(netId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1337,7 +1337,7 @@ public class WifiManager {
*/
public boolean disconnect() {
try {
- mService.disconnect(mContext.getOpPackageName());
+ mService.disconnect();
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1352,7 +1352,7 @@ public class WifiManager {
*/
public boolean reconnect() {
try {
- mService.reconnect(mContext.getOpPackageName());
+ mService.reconnect();
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1367,7 +1367,7 @@ public class WifiManager {
*/
public boolean reassociate() {
try {
- mService.reassociate(mContext.getOpPackageName());
+ mService.reassociate();
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1740,7 +1740,7 @@ public class WifiManager {
@Deprecated
public boolean saveConfiguration() {
try {
- return mService.saveConfiguration(mContext.getOpPackageName());
+ return mService.saveConfiguration();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2056,7 +2056,7 @@ public class WifiManager {
}
mLOHSCallbackProxy = null;
try {
- mService.stopLocalOnlyHotspot(mContext.getOpPackageName());
+ mService.stopLocalOnlyHotspot();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2175,7 +2175,7 @@ public class WifiManager {
@RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) {
try {
- mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName());
+ mService.setWifiApConfiguration(wifiConfig);
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -2947,7 +2947,7 @@ public class WifiManager {
public void disableEphemeralNetwork(String SSID) {
if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
try {
- mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName());
+ mService.disableEphemeralNetwork(SSID);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2986,7 +2986,7 @@ public class WifiManager {
*/
public Messenger getWifiServiceMessenger() {
try {
- return mService.getWifiServiceMessenger(mContext.getOpPackageName());
+ return mService.getWifiServiceMessenger();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3516,7 +3516,7 @@ public class WifiManager {
*/
public void factoryReset() {
try {
- mService.factoryReset(mContext.getOpPackageName());
+ mService.factoryReset();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3543,7 +3543,7 @@ public class WifiManager {
*/
public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
try {
- return mService.setEnableAutoJoinWhenAssociated(enabled, mContext.getOpPackageName());
+ return mService.setEnableAutoJoinWhenAssociated(enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 3cad59053308..0df5615385fe 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -137,7 +137,7 @@ public class WifiManagerTest {
assertEquals(mApConfig, callback.mRes.getWifiConfiguration());
callback.mRes.close();
- verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME);
+ verify(mWifiService).stopLocalOnlyHotspot();
}
/**
@@ -157,7 +157,7 @@ public class WifiManagerTest {
assertEquals(mApConfig, res.getWifiConfiguration());
}
- verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME);
+ verify(mWifiService).stopLocalOnlyHotspot();
}
/**
@@ -548,7 +548,7 @@ public class WifiManagerTest {
anyString())).thenReturn(REQUEST_REGISTERED);
mWifiManager.startLocalOnlyHotspot(callback, mHandler);
mWifiManager.cancelLocalOnlyHotspotRequest();
- verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME);
+ verify(mWifiService).stopLocalOnlyHotspot();
}
/**
@@ -570,7 +570,7 @@ public class WifiManagerTest {
anyString())).thenReturn(REQUEST_REGISTERED);
mWifiManager.startLocalOnlyHotspot(callback, mHandler);
mWifiManager.cancelLocalOnlyHotspotRequest();
- verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME);
+ verify(mWifiService).stopLocalOnlyHotspot();
mLooper.dispatchAll();
assertEquals(ERROR_NOT_SET, callback.mFailureReason);
assertFalse(callback.mOnStartedCalled);
@@ -594,7 +594,7 @@ public class WifiManagerTest {
assertFalse(callback.mOnStoppedCalled);
assertEquals(null, callback.mRes);
mWifiManager.cancelLocalOnlyHotspotRequest();
- verify(mWifiService, never()).stopLocalOnlyHotspot(anyString());
+ verify(mWifiService, never()).stopLocalOnlyHotspot();
}
/**