Merge "Fix issue #67019445: Detect BATTERY_LOW/OKAY"
diff --git a/api/current.txt b/api/current.txt
index 29c13af..f59bbf7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5617,6 +5617,7 @@
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();
@@ -5629,8 +5630,12 @@
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
@@ -6372,6 +6377,7 @@
method public boolean installCaCert(android.content.ComponentName, byte[]);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
+ method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean, boolean);
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -6885,6 +6891,7 @@
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);
@@ -23046,6 +23053,7 @@
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
}
@@ -25855,7 +25863,6 @@
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 996f5de..67e4586 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5826,6 +5826,7 @@
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();
@@ -5838,8 +5839,12 @@
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
@@ -6600,6 +6605,7 @@
method public boolean installCaCert(android.content.ComponentName, byte[]);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
+ method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean, boolean);
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -7328,6 +7334,7 @@
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);
@@ -24940,6 +24947,7 @@
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
}
@@ -28101,7 +28109,6 @@
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 91be067..2eca72f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5648,6 +5648,7 @@
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();
@@ -5660,8 +5661,12 @@
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
@@ -6441,6 +6446,7 @@
method public boolean installCaCert(android.content.ComponentName, byte[]);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
+ method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean, boolean);
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -6959,6 +6965,7 @@
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);
@@ -23249,6 +23256,7 @@
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/statsd/Android.mk b/cmds/statsd/Android.mk
index 1b2f9da..fd9465d 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -55,7 +55,8 @@
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 @@
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_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 1a056df..65b3da1 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 @@
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 @@
"[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 @@
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 4d768f6..47f0bb6 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -160,6 +160,11 @@
*/
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 57a92b6..20e7c60 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -74,6 +74,7 @@
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 @@
}
/**
+ * 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 2125609..0c9252e 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -232,7 +232,7 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 0000000..e1947c4
--- /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 0000000..444ed92
--- /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 d47bd4f..a5ce389 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -133,7 +133,7 @@
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 b0a97b1..a32e0cb 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -163,6 +163,14 @@
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 @@
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 @@
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 42ac1a2..d7b7296 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -135,7 +135,7 @@
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 9cbe6f6..0dbdd29 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -153,7 +153,7 @@
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 2344cb4..4660263 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -43,12 +43,12 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 4f5df55..3fbcfee 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -125,7 +125,7 @@
}
message StatsLogReport {
- optional int32 metric_id = 1;
+ optional string metric_name = 1;
optional int64 start_report_nanos = 2;
diff --git a/cmds/statsd/tools/Android.mk b/cmds/statsd/tools/Android.mk
new file mode 100644
index 0000000..faf2d2c
--- /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 0000000..1bd5f30
--- /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 0000000..cd76c9d
--- /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 0000000..55621cc
--- /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 0000000..11ec206
--- /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 0000000..7c02b78
--- /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 0000000..915d914
--- /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 0000000..3997897
--- /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 0000000..d5b8fed
--- /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 0000000..7690df6
--- /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 0000000..f6dea42
--- /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 0000000..5e3160e
--- /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 1503445..d8c7929 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -3,7 +3,26 @@
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/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9e926bd..d1aacad 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -72,6 +72,7 @@
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 f931589..659cf16 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -93,6 +93,58 @@
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 @@
}
/**
+ * 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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f0226b7..0bca969 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3858,6 +3858,47 @@
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
@NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) {
+ return installKeyPair(admin, privKey, certs, alias, requestAccess, true);
+ }
+
+ /**
+ * Called by a device or profile owner, or delegated certificate installer, to install a
+ * certificate chain and corresponding private key for the leaf certificate. All apps within the
+ * profile will be able to access the certificate chain and use the private key, given direct
+ * user approval (if the user is allowed to select the private key).
+ *
+ * <p>The caller of this API may grant itself access to the certificate and private key
+ * immediately, without user approval. It is a best practice not to request this unless strictly
+ * necessary since it opens up additional security vulnerabilities.
+ *
+ * <p>Whether this key is offered to the user for approval at all or not depends on the
+ * {@code isUserSelectable} parameter.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if calling from a delegated certificate installer.
+ * @param privKey The private key to install.
+ * @param certs The certificate chain to install. The chain should start with the leaf
+ * certificate and include the chain of trust in order. This will be returned by
+ * {@link android.security.KeyChain#getCertificateChain}.
+ * @param alias The private key alias under which to install the certificate. If a certificate
+ * with that alias already exists, it will be overwritten.
+ * @param requestAccess {@code true} to request that the calling app be granted access to the
+ * credentials immediately. Otherwise, access to the credentials will be gated by user
+ * approval.
+ * @param isUserSelectable {@code true} to indicate that a user can select this key via the
+ * Certificate Selection prompt, false to indicate that this key can only be granted
+ * access by implementing
+ * {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+ * @return {@code true} if the keys were installed, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+ * owner.
+ * @see android.security.KeyChain#getCertificateChain
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
+ */
+ public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
+ @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess,
+ boolean isUserSelectable) {
throwIfParentInstance("installKeyPair");
try {
final byte[] pemCert = Credentials.convertToPem(certs[0]);
@@ -3868,7 +3909,7 @@
final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
.getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
return mService.installKeyPair(admin, mContext.getPackageName(), pkcs8Key, pemCert,
- pemChain, alias, requestAccess);
+ pemChain, alias, requestAccess, isUserSelectable);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index be0b920..b7740e9 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -162,7 +162,8 @@
boolean isCaCertApproved(in String alias, int userHandle);
boolean installKeyPair(in ComponentName who, in String callerPackage, in byte[] privKeyBuffer,
- in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess);
+ in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess,
+ boolean isUserSelectable);
boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 530d84b..7c40b4e 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -244,6 +244,13 @@
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 @@
}
/**
+ * 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 @@
"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/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2034280..edb27cd 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -19,6 +19,7 @@
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 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 @@
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 @@
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 ba488f6..f8889b6 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 @@
/** @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 @@
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 @@
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 1c5cf15..d9ac681 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -678,6 +678,8 @@
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 @@
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 boolean mIsStaticOverlay;
public boolean mTrustedOverlay;
+ public int mCompileSdkVersion;
+ public String mCompileSdkVersionCodename;
+
/**
* Data used to feed the KeySetManagerService
*/
@@ -6458,6 +6473,8 @@
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 @@
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/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 025d46d..151e62d 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -34,7 +34,7 @@
/* 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 @@
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 6ce9669..bdb90bc 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -344,7 +344,7 @@
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 @@
* 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 @@
return false;
}
try {
- return mService.hasDevicePermission(device);
+ return mService.hasDevicePermission(device, mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -450,6 +453,10 @@
* 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 64f8f39..d6e62cf 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.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 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 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 @@
*
* <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 @@
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.
*
- * @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).
+ * <p>This constructor only supports algorithms that use a truncation length. i.e.
+ * Authentication and Authenticated Encryption algorithms.
+ *
+ * @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 61b13a9..e6cd3fc 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -20,7 +20,12 @@
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 @@
// 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 eccd5f4..a9e60ec 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -19,6 +19,7 @@
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.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 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 @@
* 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 @@
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 @@
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 @@
mCloseGuard.close();
}
+ /** Check that the SPI was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -197,13 +216,13 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
* 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 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.
*
- * @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(Socket socket, IpSecTransform transform)
@@ -348,14 +410,19 @@
}
/**
- * 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 @@
}
/**
- * 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.
*
- * @param socket a socket file descriptor 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
*/
public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
throws IOException {
@@ -382,7 +454,7 @@
}
}
- /* 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 @@
* 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 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 @@
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 @@
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 @@
mCloseGuard.close();
}
+ /** Check that the socket was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -499,21 +577,14 @@
};
/**
- * Open a socket that is bound to a free UDP port on the system.
+ * Open a socket for UDP encapsulation and bind to the given port.
*
- * <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.
- *
- * @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 @@
}
/**
- * 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 @@
}
/**
- * 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 48b5bd5..cda4ec7 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -38,27 +38,29 @@
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 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 @@
}
/**
- * 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 @@
}
}
+ /** Check that the transform was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -264,65 +268,63 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
// 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.
*
- * @param intervalSeconds the maximum number of seconds between keepalive packets, no less
- * than 20s and no more than 3600s.
+ * <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.
+ *
* @hide
*/
@SystemApi
@@ -408,36 +412,29 @@
}
/**
- * 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 @@
}
/**
- * 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 a8bd940..af91e81 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -222,8 +222,10 @@
* - 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 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 @@
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 @@
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 @@
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 @@
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/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f2483c0..6ede72d 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -120,7 +120,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 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 @@
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 @@
}
@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 @@
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 @@
mChargeStepTracker.init();
mDischargeScreenOffCounter.reset(false);
mDischargeScreenDozeCounter.reset(false);
+ mDischargeLightDozeCounter.reset(false);
+ mDischargeDeepDozeCounter.reset(false);
mDischargeCounter.reset(false);
}
@@ -11263,6 +11279,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 4e88a83..d7300c4 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -36,7 +36,7 @@
#include <hwui/MinikinUtils.h>
#include <hwui/Paint.h>
#include <minikin/FontCollection.h>
-#include <minikin/LineBreaker.h>
+#include <minikin/AndroidLineBreakerHelper.h>
#include <minikin/MinikinFont.h>
namespace android {
@@ -52,63 +52,6 @@
static jclass gLineBreaks_class;
static JLineBreaksID gLineBreaks_fieldID;
-class JNILineBreakerLineWidth : public minikin::LineBreaker::LineWidthDelegate {
- public:
- JNILineBreakerLineWidth(float firstWidth, int32_t firstLineCount, float restWidth,
- const std::vector<float>& indents, const std::vector<float>& leftPaddings,
- const std::vector<float>& rightPaddings, int32_t indentsAndPaddingsOffset)
- : mFirstWidth(firstWidth), mFirstLineCount(firstLineCount), mRestWidth(restWidth),
- mIndents(indents), mLeftPaddings(leftPaddings),
- mRightPaddings(rightPaddings), mOffset(indentsAndPaddingsOffset) {}
-
- float getLineWidth(size_t lineNo) override {
- const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount)
- ? mFirstWidth : mRestWidth;
- return width - get(mIndents, lineNo);
- }
-
- float getMinLineWidth() override {
- // A simpler algorithm would have been simply looping until the larger of
- // mFirstLineCount and mIndents.size()-mOffset, but that does unnecessary calculations
- // when mFirstLineCount is large. Instead, we measure the first line, all the lines that
- // have an indent, and the first line after firstWidth ends and restWidth starts.
- float minWidth = std::min(getLineWidth(0), getLineWidth(mFirstLineCount));
- for (size_t lineNo = 1; lineNo + mOffset < mIndents.size(); lineNo++) {
- minWidth = std::min(minWidth, getLineWidth(lineNo));
- }
- return minWidth;
- }
-
- float getLeftPadding(size_t lineNo) override {
- return get(mLeftPaddings, lineNo);
- }
-
- float getRightPadding(size_t lineNo) override {
- return get(mRightPaddings, lineNo);
- }
-
- private:
- float get(const std::vector<float>& vec, size_t lineNo) {
- if (vec.empty()) {
- return 0;
- }
- const size_t index = lineNo + mOffset;
- if (index < vec.size()) {
- return vec[index];
- } else {
- return vec.back();
- }
- }
-
- const float mFirstWidth;
- const int32_t mFirstLineCount;
- const float mRestWidth;
- const std::vector<float>& mIndents;
- const std::vector<float>& mLeftPaddings;
- const std::vector<float>& mRightPaddings;
- const int32_t mOffset;
-};
-
static inline std::vector<float> jintArrayToFloatVector(JNIEnv* env, jintArray javaArray) {
if (javaArray == nullptr) {
return std::vector<float>();
@@ -118,109 +61,8 @@
}
}
-class Run {
- public:
- Run(int32_t start, int32_t end) : mRange(start, end) {}
- virtual ~Run() {}
-
- virtual void addTo(minikin::LineBreaker* lineBreaker) = 0;
-
- protected:
- minikin::Range mRange;
-
- private:
- // Forbid copy and assign.
- Run(const Run&) = delete;
- void operator=(const Run&) = delete;
-};
-
-class StyleRun : public Run {
- public:
- StyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
- std::shared_ptr<minikin::FontCollection>&& collection, bool isRtl)
- : Run(start, end), mPaint(std::move(paint)), mCollection(std::move(collection)),
- mIsRtl(isRtl) {}
-
- void addTo(minikin::LineBreaker* lineBreaker) override {
- lineBreaker->addStyleRun(&mPaint, mCollection, mRange, mIsRtl);
- }
-
- private:
- minikin::MinikinPaint mPaint;
- std::shared_ptr<minikin::FontCollection> mCollection;
- const bool mIsRtl;
-};
-
-class Replacement : public Run {
- public:
- Replacement(int32_t start, int32_t end, float width, uint32_t localeListId)
- : Run(start, end), mWidth(width), mLocaleListId(localeListId) {}
-
- void addTo(minikin::LineBreaker* lineBreaker) override {
- lineBreaker->addReplacement(mRange, mWidth, mLocaleListId);
- }
-
- private:
- const float mWidth;
- const uint32_t mLocaleListId;
-};
-
-class StaticLayoutNative {
- public:
- StaticLayoutNative(
- minikin::BreakStrategy strategy, minikin::HyphenationFrequency frequency,
- bool isJustified, std::vector<float>&& indents, std::vector<float>&& leftPaddings,
- std::vector<float>&& rightPaddings)
- : mStrategy(strategy), mFrequency(frequency), mIsJustified(isJustified),
- mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)),
- mRightPaddings(std::move(rightPaddings)) {}
-
- void addStyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
- std::shared_ptr<minikin::FontCollection> collection, bool isRtl) {
- mRuns.emplace_back(std::make_unique<StyleRun>(
- start, end, std::move(paint), std::move(collection), isRtl));
- }
-
- void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) {
- mRuns.emplace_back(std::make_unique<Replacement>(start, end, width, localeListId));
- }
-
- // Only valid while this instance is alive.
- inline std::unique_ptr<minikin::LineBreaker::LineWidthDelegate> buildLineWidthDelegate(
- float firstWidth, int32_t firstLineCount, float restWidth,
- int32_t indentsAndPaddingsOffset) {
- return std::make_unique<JNILineBreakerLineWidth>(
- firstWidth, firstLineCount, restWidth, mIndents, mLeftPaddings, mRightPaddings,
- indentsAndPaddingsOffset);
- }
-
- void addRuns(minikin::LineBreaker* lineBreaker) {
- for (const auto& run : mRuns) {
- run->addTo(lineBreaker);
- }
- }
-
- void clearRuns() {
- mRuns.clear();
- }
-
- inline minikin::BreakStrategy getStrategy() const { return mStrategy; }
- inline minikin::HyphenationFrequency getFrequency() const { return mFrequency; }
- inline bool isJustified() const { return mIsJustified; }
-
- private:
- const minikin::BreakStrategy mStrategy;
- const minikin::HyphenationFrequency mFrequency;
- const bool mIsJustified;
- const std::vector<float> mIndents;
- const std::vector<float> mLeftPaddings;
- const std::vector<float> mRightPaddings;
-
- std::vector<std::unique_ptr<Run>> mRuns;
-};
-
-static inline StaticLayoutNative* toNative(jlong ptr) {
- return reinterpret_cast<StaticLayoutNative*>(ptr);
+static inline minikin::android::StaticLayoutNative* toNative(jlong ptr) {
+ return reinterpret_cast<minikin::android::StaticLayoutNative*>(ptr);
}
// set text and set a number of parameters for creating a layout (width, tabstops, strategy,
@@ -228,7 +70,7 @@
static jlong nInit(JNIEnv* env, jclass /* unused */,
jint breakStrategy, jint hyphenationFrequency, jboolean isJustified,
jintArray indents, jintArray leftPaddings, jintArray rightPaddings) {
- return reinterpret_cast<jlong>(new StaticLayoutNative(
+ return reinterpret_cast<jlong>(new minikin::android::StaticLayoutNative(
static_cast<minikin::BreakStrategy>(breakStrategy),
static_cast<minikin::HyphenationFrequency>(hyphenationFrequency),
isJustified,
@@ -291,7 +133,7 @@
jintArray recycleFlags,
jfloatArray charWidths) {
- StaticLayoutNative* builder = toNative(nativePtr);
+ minikin::android::StaticLayoutNative* builder = toNative(nativePtr);
ScopedCharArrayRO text(env, javaText);
@@ -327,7 +169,7 @@
// Basically similar to Paint.getTextRunAdvances but with C++ interface
// CriticalNative
static void nAddStyleRun(jlong nativePtr, jlong nativePaint, jint start, jint end, jboolean isRtl) {
- StaticLayoutNative* builder = toNative(nativePtr);
+ minikin::android::StaticLayoutNative* builder = toNative(nativePtr);
Paint* paint = reinterpret_cast<Paint*>(nativePaint);
const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
@@ -337,7 +179,7 @@
// CriticalNative
static void nAddReplacementRun(jlong nativePtr, jlong nativePaint, jint start, jint end,
jfloat width) {
- StaticLayoutNative* builder = toNative(nativePtr);
+ minikin::android::StaticLayoutNative* builder = toNative(nativePtr);
Paint* paint = reinterpret_cast<Paint*>(nativePaint);
builder->addReplacementRun(start, end, width, paint->getMinikinLocaleListId());
}
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index c33c0a0..0a3344f 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -120,6 +120,14 @@
// 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 feef5ce..d15c075 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" />
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 0000000..89e58aa
--- /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 f26d6ed..e12f04a 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 be0f6d9..fe8ca56 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 edd793d..c104616 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 fdd56c4..d3533fe 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 7a3fa1a..999ba73 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 5497085..a115816 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 0000000..031a6c1
--- /dev/null
+++ b/data/keyboards/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+
+michaelwr@google.com
+svv@google.com
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index c475e12..306ed83 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -721,14 +721,16 @@
/**
* 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 91e57ee..02c71b2 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -258,12 +258,18 @@
* 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 @@
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 @@
}
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/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 700c01a..0f498bc 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 @@
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 @@
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/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index a45a4f0..dd29a04 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.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.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.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 @@
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 @@
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 @@
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 7a0173f..a2b5cb8 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.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 @@
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 @@
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 @@
@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 @@
mCurrentTransportName = defaultTransport;
mTransportBoundListener = listener;
mHandler = new RebindOnTimeoutHandler(looper);
+ mTransportClientManager = new TransportClientManager(context);
}
void onPackageAdded(String packageName) {
@@ -204,6 +212,67 @@
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 @@
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 @@
// 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 8f82300..9011b95 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.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.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 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 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 @@
// 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 @@
}
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 @@
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 0000000..e417f06
--- /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 c0caa557..1fa215a 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.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 @@
private RefactoredBackupManagerService backupManagerService;
private final Object mCancelLock = new Object();
- IBackupTransport mTransport;
ArrayList<BackupRequest> mQueue;
ArrayList<BackupRequest> mOriginalQueue;
File mStateDir;
@@ -122,6 +123,8 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
// 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 4fd7ddb..2ba8ec1 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 @@
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 0000000..9c39729
--- /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 0000000..1cbe7471
--- /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 0000000..1ccffd0
--- /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 0000000..a02f03c
--- /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 0000000..514717f
--- /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 0fd59ea..bdfccd6e 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -118,7 +118,7 @@
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 e5fbdd21..296fb32 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 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 0a6f976..ae91b82 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.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 @@
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 @@
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 @@
static KillHandler sKillHandler = null;
CompatModeDialog mCompatModeDialog;
- UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
long mLastMemUsageReportTime = 0;
/**
@@ -1927,23 +1926,6 @@
}
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 @@
GL_ES_VERSION = 0;
mActivityStarter = null;
mAppErrors = null;
+ mAppWarnings = null;
mAppOpsService = mInjector.getAppOpsService(null, null);
mBatteryStatsService = null;
mCompatModePackages = null;
@@ -2743,10 +2726,13 @@
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();
@@ -3310,15 +3296,18 @@
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,
@@ -14578,6 +14567,18 @@
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();
@@ -19362,13 +19363,7 @@
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);
}
@@ -19447,13 +19442,8 @@
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;
}
@@ -20643,8 +20633,7 @@
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);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 2e76471..5927666 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -2557,7 +2557,7 @@
}
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 c086c52..6985d6e 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 @@
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 b015bcf..ddde4bc 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1358,7 +1358,7 @@
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/AppWarnings.java b/services/core/java/com/android/server/am/AppWarnings.java
new file mode 100644
index 0000000..a3c0345
--- /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 bfc0456..82019fd 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 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 @@
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 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/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
new file mode 100644
index 0000000..600589a
--- /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 501cd6b..8850663 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 @@
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 @@
// 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/OWNERS b/services/core/java/com/android/server/display/OWNERS
new file mode 100644
index 0000000..7e7335d
--- /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 0000000..0313a40
--- /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 b9777ec..4a3becb 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 @@
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 @@
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 @@
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 1af3b39..28b60e3 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 @@
/**
* @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 @@
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 @@
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 @@
// 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 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 void clear() {
mJobs.clear();
+ mJobsPerSourceUid.clear();
}
public int size() {
@@ -1112,8 +1148,17 @@
}
}
- 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 374ab43..b7eb9e0 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 @@
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 @@
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 @@
+ 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 @@
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 @@
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 @@
}
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 @@
}
/**
+ * 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 @@
}
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 @@
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 0000000..7e7335d
--- /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 6ba1d8d..08da568 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 @@
.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_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.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.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.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.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 @@
}
}
}
+ 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 @@
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 @@
}
}
+ 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 @@
}
@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 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 b9c0d90..b1b0bf2 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 @@
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 d566a45..c0dccb5 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 @@
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 @@
}
}
}
- 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 bbe59eb..2e44e79 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 @@
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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7748ae4..384070c 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 @@
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 @@
boolean mLidControlsSleep;
int mShortPressOnPowerBehavior;
int mLongPressOnPowerBehavior;
+ int mVeryLongPressOnPowerBehavior;
int mDoublePressOnPowerBehavior;
int mTriplePressOnPowerBehavior;
int mLongPressOnBackBehavior;
@@ -586,6 +591,7 @@
boolean mHasSoftInput = false;
boolean mTranslucentDecorEnabled = true;
boolean mUseTvRouting;
+ int mVeryLongPressTimeout;
private boolean mHandleVolumeKeysInWM;
@@ -796,6 +802,7 @@
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 @@
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 @@
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 @@
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 @@
mPowerKeyHandled = true;
mHandler.removeMessages(MSG_POWER_LONG_PRESS);
}
+ if (hasVeryLongPressOnPowerBehavior()) {
+ mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS);
+ }
}
private void cancelPendingBackKeyAction() {
@@ -1516,6 +1542,29 @@
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 @@
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 @@
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 @@
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 @@
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 336df48..87c9274 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -64,6 +64,7 @@
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";
@@ -73,9 +74,16 @@
private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n";
- private static String mSettings;
- private static String mDeviceSpecificSettings;
- private static String mDeviceSpecificSettingsSource; // For dump() only.
+ private final Object mLock = new Object();
+
+ @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 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_VIBRATION_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mVibrationDisabled;
/**
@@ -91,6 +100,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ANIMATION_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mAnimationDisabled;
/**
@@ -100,6 +110,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_SOUNDTRIGGER_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mSoundTriggerDisabled;
/**
@@ -108,6 +119,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_FULLBACKUP_DEFERRED
*/
+ @GuardedBy("mLock")
private boolean mFullBackupDeferred;
/**
@@ -116,6 +128,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_KEYVALUE_DEFERRED
*/
+ @GuardedBy("mLock")
private boolean mKeyValueBackupDeferred;
/**
@@ -124,6 +137,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_FIREWALL_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mFireWallDisabled;
/**
@@ -132,6 +146,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ADJUST_BRIGHTNESS_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mAdjustBrightnessDisabled;
/**
@@ -140,14 +155,22 @@
* @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 @@
* @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 @@
@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
@@ -296,6 +324,7 @@
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);
@@ -382,6 +411,12 @@
}
}
+ public boolean isLaunchBoostDisabled() {
+ synchronized (mLock) {
+ return mLaunchBoostDisabled;
+ }
+ }
+
public void dump(PrintWriter pw) {
synchronized (mLock) {
pw.println();
@@ -398,6 +433,7 @@
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 0000000..b4300a9
--- /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 0ca0167..2c87a40 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3109,7 +3109,16 @@
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 b471c8d..ae01ea57 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -174,6 +174,13 @@
}
/**
+ * @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/OWNERS b/services/core/java/com/android/server/power/batterysaver/OWNERS
new file mode 100644
index 0000000..09136dc
--- /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 0000000..af7ff41
--- /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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 52b7a25..730ec37 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 @@
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 @@
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 @@
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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2d8a0ee..60c36d1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4917,7 +4917,8 @@
@Override
public boolean installKeyPair(ComponentName who, String callerPackage, byte[] privKey,
- byte[] cert, byte[] chain, String alias, boolean requestAccess) {
+ byte[] cert, byte[] chain, String alias, boolean requestAccess,
+ boolean isUserSelectable) {
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
DELEGATION_CERT_INSTALL);
@@ -4935,6 +4936,7 @@
if (requestAccess) {
keyChain.setGrant(callingUid, alias, true);
}
+ keyChain.setUserSelectable(alias, isUserSelectable);
return true;
} catch (RemoteException e) {
Log.e(LOG_TAG, "Installing certificate", e);
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 0000000..54d233a
--- /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 c145e82..55ec133 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 @@
}
@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 @@
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 3c02e23..2d03f11 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 @@
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 @@
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 @@
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 @@
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 @@
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/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index fa8feb0..1f9a243 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 @@
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/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 011817e..884ba70 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 @@
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 @@
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 095fdc6..9bc9cd0 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 @@
}
/* 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 @@
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 e4fcea7..17de83f 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 @@
/* 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 @@
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 @@
}
@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 @@
@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 96c5211..11e43e3 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.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 @@
}
}
+ /**
+ * 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;
+ }
- public boolean hasPermission(UsbDevice device) {
+ 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;
+ }
+ }
+
+ 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 @@
}
}
- 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 @@
}
}
- 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 @@
}
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 132b234..ffcef89 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -346,22 +346,20 @@
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();
}