diff options
108 files changed, 4296 insertions, 674 deletions
diff --git a/api/current.txt b/api/current.txt index 29c13af1655b..f59bbf7d90e2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5617,6 +5617,7 @@ package android.app { method public final int getCurrentInterruptionFilter(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(java.lang.String); + method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String); method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); method public android.app.NotificationManager.Policy getNotificationPolicy(); @@ -5629,8 +5630,12 @@ package android.app { method public void setNotificationPolicy(android.app.NotificationManager.Policy); method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule); field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED"; + field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE"; + field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID"; field public static final int IMPORTANCE_DEFAULT = 3; // 0x3 field public static final int IMPORTANCE_HIGH = 4; // 0x4 field public static final int IMPORTANCE_LOW = 2; // 0x2 @@ -6372,6 +6377,7 @@ package android.app.admin { 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 @@ package android.app.job { method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int); method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long); method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle); + method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean); method public android.app.job.JobInfo.Builder setMinimumLatency(long); method public android.app.job.JobInfo.Builder setOverrideDeadline(long); method public android.app.job.JobInfo.Builder setPeriodic(long); @@ -23046,6 +23053,7 @@ package android.media { public static final class MediaMuxer.OutputFormat { field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2 + field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3 field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0 field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1 } @@ -25855,7 +25863,6 @@ package android.net { method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException; method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException; method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; - field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException { diff --git a/api/system-current.txt b/api/system-current.txt index 996f5de047ab..67e45867bbc5 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5826,6 +5826,7 @@ package android.app { method public final int getCurrentInterruptionFilter(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(java.lang.String); + method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String); method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); method public android.app.NotificationManager.Policy getNotificationPolicy(); @@ -5838,8 +5839,12 @@ package android.app { method public void setNotificationPolicy(android.app.NotificationManager.Policy); method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule); field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED"; + field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE"; + field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID"; field public static final int IMPORTANCE_DEFAULT = 3; // 0x3 field public static final int IMPORTANCE_HIGH = 4; // 0x4 field public static final int IMPORTANCE_LOW = 2; // 0x2 @@ -6600,6 +6605,7 @@ package android.app.admin { 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 @@ package android.app.job { method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int); method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long); method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle); + method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean); method public android.app.job.JobInfo.Builder setMinimumLatency(long); method public android.app.job.JobInfo.Builder setOverrideDeadline(long); method public android.app.job.JobInfo.Builder setPeriodic(long); @@ -24940,6 +24947,7 @@ package android.media { public static final class MediaMuxer.OutputFormat { field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2 + field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3 field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0 field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1 } @@ -28101,7 +28109,6 @@ package android.net { method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException; method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException; method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; - field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException { diff --git a/api/test-current.txt b/api/test-current.txt index 91be067dfc19..2eca72f13c99 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5648,6 +5648,7 @@ package android.app { method public android.content.ComponentName getEffectsSuppressor(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(java.lang.String); + method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String); method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); method public android.app.NotificationManager.Policy getNotificationPolicy(); @@ -5660,8 +5661,12 @@ package android.app { method public void setNotificationPolicy(android.app.NotificationManager.Policy); method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule); field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED"; + field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE"; + field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID"; field public static final int IMPORTANCE_DEFAULT = 3; // 0x3 field public static final int IMPORTANCE_HIGH = 4; // 0x4 field public static final int IMPORTANCE_LOW = 2; // 0x2 @@ -6441,6 +6446,7 @@ package android.app.admin { 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 @@ package android.app.job { method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int); method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long); method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle); + method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean); method public android.app.job.JobInfo.Builder setMinimumLatency(long); method public android.app.job.JobInfo.Builder setOverrideDeadline(long); method public android.app.job.JobInfo.Builder setPeriodic(long); @@ -23249,6 +23256,7 @@ package android.media { public static final class MediaMuxer.OutputFormat { field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2 + field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3 field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0 field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1 } diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index 1b2f9da22ec1..fd9465d68b37 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -55,7 +55,8 @@ statsd_common_src := \ src/storage/DropboxWriter.cpp \ src/StatsLogProcessor.cpp \ src/StatsService.cpp \ - src/stats_util.cpp + src/stats_util.cpp \ + src/guardrail/MemoryLeakTrackUtil.cpp statsd_common_c_includes := \ $(LOCAL_PATH)/src \ @@ -82,7 +83,8 @@ statsd_common_shared_libraries := \ libhidltransport \ libhwbinder \ android.hardware.power@1.0 \ - android.hardware.power@1.1 + android.hardware.power@1.1 \ + libmemunreachable # ========= # statsd @@ -178,3 +180,7 @@ statsd_common_aidl_includes:= statsd_common_c_includes:= include $(BUILD_NATIVE_TEST) + +############################## + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 1a056df8e367..65b3da1cd258 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -17,10 +17,11 @@ #define DEBUG true #include "Log.h" -#include "android-base/stringprintf.h" #include "StatsService.h" +#include "android-base/stringprintf.h" #include "config/ConfigKey.h" #include "config/ConfigManager.h" +#include "guardrail/MemoryLeakTrackUtil.h" #include "storage/DropboxReader.h" #include <android-base/file.h> @@ -226,6 +227,10 @@ status_t StatsService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& if (!args[0].compare(String8("clear-config"))) { return cmd_remove_config_files(out); } + + if (!args[0].compare(String8("meminfo"))) { + return cmd_dump_memory_info(out); + } } print_cmd_help(out); @@ -238,6 +243,15 @@ void StatsService::print_cmd_help(FILE* out) { "[timestamp_nsec_optional]\n"); fprintf(out, "\n"); fprintf(out, "\n"); + fprintf(out, "usage: adb shell cmd stats meminfo\n"); + fprintf(out, "\n"); + fprintf(out, " Prints the malloc debug information. You need to run the following first: \n"); + fprintf(out, " # adb shell stop\n"); + fprintf(out, " # adb shell setprop libc.debug.malloc.program statsd \n"); + fprintf(out, " # adb shell setprop libc.debug.malloc.options backtrace \n"); + fprintf(out, " # adb shell start\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); fprintf(out, "usage: adb shell cmd stats print-uid-map \n"); fprintf(out, "\n"); fprintf(out, " Prints the UID, app name, version mapping.\n"); @@ -507,6 +521,13 @@ status_t StatsService::cmd_remove_config_files(FILE* out) { return NO_ERROR; } +status_t StatsService::cmd_dump_memory_info(FILE* out) { + std::string s = dumpMemInfo(100); + fprintf(out, "Memory Info\n"); + fprintf(out, "%s", s.c_str()); + return NO_ERROR; +} + Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version, const vector<String16>& app) { if (DEBUG) ALOGD("StatsService::informAllUidData was called"); diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 4d768f630362..47f0bb601fef 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -160,6 +160,11 @@ private: */ status_t cmd_remove_config_files(FILE* out); + /* + * Dump memory usage by statsd. + */ + status_t cmd_dump_memory_info(FILE* out); + /** * Update a configuration. */ diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 57a92b61a5c9..20e7c600938e 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -74,6 +74,7 @@ message Atom { ActivityForegroundStateChanged activity_foreground_state_changed = 42; IsolatedUidChanged isolated_uid_changed = 43; PacketWakeupOccurred packet_wakeup_occurred = 44; + DropboxErrorChanged dropbox_error_changed = 45; // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15. } @@ -716,6 +717,34 @@ message ActivityForegroundStateChanged { } /** + * Logs when an error is written to dropbox. + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + */ +message DropboxErrorChanged { + // The uid if available. -1 means not available. + optional int32 uid = 1; + + // Tag used when recording this error to dropbox. Contains data_ or system_ prefix. + optional string tag = 2; + + // The name of the process. + optional string process_name = 3; + + // The pid if available. -1 means not available. + optional int32 pid = 4; + + // 1 indicates is instant app. -1 indicates Not applicable. + optional int32 is_instant_app = 5; + + // The activity name if available. + optional string activity_name = 6; + + // 1 indicates in foreground. -1 indicates not available. + optional int32 is_foreground = 7; +} + +/** * Pulls bytes transferred via wifi (Sum of foreground and background usage). * * Pulled from: diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 21256097a766..0c9252e2095f 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -232,7 +232,7 @@ void ConfigManager::update_saved_configs(const ConfigKey& key, const StatsdConfi static StatsdConfig build_fake_config() { // HACK: Hard code a test metric for counting screen on events... StatsdConfig config; - config.set_name("12345"); + config.set_name("CONFIG_12345"); int WAKE_LOCK_TAG_ID = 1111; // put a fake id here to make testing easier. int WAKE_LOCK_UID_KEY_ID = 1; @@ -263,20 +263,21 @@ static StatsdConfig build_fake_config() { // Count Screen ON events. CountMetric* metric = config.add_count_metric(); - metric->set_name("1"); + metric->set_name("METRIC_1"); metric->set_what("SCREEN_TURNED_ON"); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); // Anomaly threshold for screen-on count. Alert* alert = config.add_alert(); - alert->set_name("1"); + alert->set_name("ALERT_1"); + alert->set_metric_name("METRIC_1"); alert->set_number_of_buckets(6); alert->set_trigger_if_sum_gt(10); alert->set_refractory_period_secs(30); // Count process state changes, slice by uid. metric = config.add_count_metric(); - metric->set_name("2"); + metric->set_name("METRIC_2"); metric->set_what("PROCESS_STATE_CHANGE"); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); KeyMatcher* keyMatcher = metric->add_dimension(); @@ -284,14 +285,15 @@ static StatsdConfig build_fake_config() { // Anomaly threshold for background count. alert = config.add_alert(); - alert->set_name("2"); + alert->set_name("ALERT_2"); + alert->set_metric_name("METRIC_2"); alert->set_number_of_buckets(4); alert->set_trigger_if_sum_gt(30); alert->set_refractory_period_secs(20); // Count process state changes, slice by uid, while SCREEN_IS_OFF metric = config.add_count_metric(); - metric->set_name("3"); + metric->set_name("METRIC_3"); metric->set_what("PROCESS_STATE_CHANGE"); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); keyMatcher = metric->add_dimension(); @@ -300,7 +302,7 @@ static StatsdConfig build_fake_config() { // Count wake lock, slice by uid, while SCREEN_IS_ON and app in background metric = config.add_count_metric(); - metric->set_name("4"); + metric->set_name("METRIC_4"); metric->set_what("APP_GET_WL"); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); keyMatcher = metric->add_dimension(); @@ -313,7 +315,7 @@ static StatsdConfig build_fake_config() { // Duration of an app holding any wl, while screen on and app in background, slice by uid DurationMetric* durationMetric = config.add_duration_metric(); - durationMetric->set_name("5"); + durationMetric->set_name("METRIC_5"); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); keyMatcher = durationMetric->add_dimension(); @@ -327,7 +329,7 @@ static StatsdConfig build_fake_config() { // max Duration of an app holding any wl, while screen on and app in background, slice by uid durationMetric = config.add_duration_metric(); - durationMetric->set_name("6"); + durationMetric->set_name("METRIC_6"); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); keyMatcher = durationMetric->add_dimension(); @@ -341,7 +343,7 @@ static StatsdConfig build_fake_config() { // Duration of an app holding any wl, while screen on and app in background durationMetric = config.add_duration_metric(); - durationMetric->set_name("7"); + durationMetric->set_name("METRIC_7"); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); durationMetric->set_what("WL_HELD_PER_APP_PER_NAME"); @@ -353,14 +355,14 @@ static StatsdConfig build_fake_config() { // Duration of screen on time. durationMetric = config.add_duration_metric(); - durationMetric->set_name("8"); + durationMetric->set_name("METRIC_8"); durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); durationMetric->set_what("SCREEN_IS_ON"); // Value metric to count KERNEL_WAKELOCK when screen turned on ValueMetric* valueMetric = config.add_value_metric(); - valueMetric->set_name("6"); + valueMetric->set_name("METRIC_6"); valueMetric->set_what("KERNEL_WAKELOCK"); valueMetric->set_value_field(1); valueMetric->set_condition("SCREEN_IS_ON"); @@ -371,12 +373,12 @@ static StatsdConfig build_fake_config() { // Add an EventMetric to log process state change events. EventMetric* eventMetric = config.add_event_metric(); - eventMetric->set_name("9"); + eventMetric->set_name("METRIC_9"); eventMetric->set_what("SCREEN_TURNED_ON"); // Add an GaugeMetric. GaugeMetric* gaugeMetric = config.add_gauge_metric(); - gaugeMetric->set_name("10"); + gaugeMetric->set_name("METRIC_10"); gaugeMetric->set_what("DEVICE_TEMPERATURE"); gaugeMetric->set_gauge_field(DEVICE_TEMPERATURE_KEY); gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L); diff --git a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp new file mode 100644 index 000000000000..e1947c4a5fe8 --- /dev/null +++ b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define DEBUG true // STOPSHIP if true +#include "Log.h" + +#include <sstream> +#include "MemoryLeakTrackUtil.h" + +/* + * The code here originally resided in MediaPlayerService.cpp + */ + +// Figure out the abi based on defined macros. +#if defined(__arm__) +#define ABI_STRING "arm" +#elif defined(__aarch64__) +#define ABI_STRING "arm64" +#elif defined(__mips__) && !defined(__LP64__) +#define ABI_STRING "mips" +#elif defined(__mips__) && defined(__LP64__) +#define ABI_STRING "mips64" +#elif defined(__i386__) +#define ABI_STRING "x86" +#elif defined(__x86_64__) +#define ABI_STRING "x86_64" +#else +#error "Unsupported ABI" +#endif + +extern std::string backtrace_string(const uintptr_t* frames, size_t frame_count); + +namespace android { +namespace os { +namespace statsd { + +extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize, size_t* infoSize, + size_t* totalMemory, size_t* backtraceSize); + +extern "C" void free_malloc_leak_info(uint8_t* info); + +std::string dumpMemInfo(size_t limit) { + uint8_t* info; + size_t overallSize; + size_t infoSize; + size_t totalMemory; + size_t backtraceSize; + get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory, &backtraceSize); + + size_t count; + if (info == nullptr || overallSize == 0 || infoSize == 0 || + (count = overallSize / infoSize) == 0) { + ALOGD("no malloc info, libc.debug.malloc.program property should be set"); + return std::string(); + } + + std::ostringstream oss; + oss << totalMemory << " bytes in " << count << " allocations\n"; + oss << " ABI: '" ABI_STRING "'" + << "\n\n"; + if (count > limit) count = limit; + + // The memory is sorted based on total size which is useful for finding + // worst memory offenders. For diffs, sometimes it is preferable to sort + // based on the backtrace. + for (size_t i = 0; i < count; i++) { + struct AllocEntry { + size_t size; // bit 31 is set if this is zygote allocated memory + size_t allocations; + uintptr_t backtrace[]; + }; + + const AllocEntry* const e = (AllocEntry*)(info + i * infoSize); + + oss << (e->size * e->allocations) << " bytes ( " << e->size << " bytes * " << e->allocations + << " allocations )\n"; + oss << backtrace_string(e->backtrace, backtraceSize) << "\n"; + } + oss << "\n"; + free_malloc_leak_info(info); + return oss.str(); +} + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h new file mode 100644 index 000000000000..444ed92cc9bb --- /dev/null +++ b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h @@ -0,0 +1,33 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <iostream> + +namespace android { +namespace os { +namespace statsd { +/* + * Dump the heap memory of the calling process, sorted by total size + * (allocation size * number of allocations). + * + * limit is the number of unique allocations to return. + */ +extern std::string dumpMemInfo(size_t limit); + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index d47bd4fe9e44..a5ce38915574 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -133,7 +133,7 @@ std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() { mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); if (kv.has_value_str()) { - mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str()); + mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); } else if (kv.has_value_int()) { mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); } else if (kv.has_value_bool()) { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index b0a97b143400..a32e0cb8b274 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -163,6 +163,14 @@ std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() { ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str()); continue; } + + // If there is no duration bucket info for this key, don't include it in the report. + // For example, duration started, but condition is never turned to true. + // TODO: Only add the key to the map when we add duration buckets info for it. + if (pair.second.size() == 0) { + continue; + } + long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); @@ -172,7 +180,7 @@ std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() { mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); if (kv.has_value_str()) { - mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str()); + mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); } else if (kv.has_value_int()) { mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); } else if (kv.has_value_bool()) { @@ -203,7 +211,6 @@ std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() { mProto->end(mProtoToken); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)mCurrentBucketStartTimeNs); - std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto(); startNewProtoOutputStream(endTime); // TODO: Properly clear the old buckets. diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 42ac1a2bbd7b..d7b72965ae1e 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -135,7 +135,7 @@ std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() { mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); if (kv.has_value_str()) { - mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str()); + mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); } else if (kv.has_value_int()) { mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); } else if (kv.has_value_bool()) { diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 9cbe6f6eac15..0dbdd2981307 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -153,7 +153,7 @@ std::unique_ptr<std::vector<uint8_t>> ValueMetricProducer::onDumpReport() { mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); if (kv.has_value_str()) { - mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str()); + mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); } else if (kv.has_value_int()) { mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); } else if (kv.has_value_bool()) { diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 2344cb4170a3..466026350ddd 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -43,12 +43,12 @@ bool handleMetricWithLogTrackers(const string what, const int metricIndex, int& logTrackerIndex) { auto logTrackerIt = logTrackerMap.find(what); if (logTrackerIt == logTrackerMap.end()) { - ALOGW("cannot find the LogEntryMatcher %s in config", what.c_str()); + ALOGW("cannot find the LogEntryMatcher \"%s\" in config", what.c_str()); return false; } if (usedForDimension && allLogEntryMatchers[logTrackerIt->second]->getTagIds().size() > 1) { - ALOGE("LogEntryMatcher %s has more than one tag ids. When a metric has dimension, the " - "\"what\" can only about one atom type.", + ALOGE("LogEntryMatcher \"%s\" has more than one tag ids. When a metric has dimension, " + "the \"what\" can only about one atom type.", what.c_str()); return false; } @@ -67,14 +67,14 @@ bool handleMetricWithConditions( unordered_map<int, std::vector<int>>& conditionToMetricMap) { auto condition_it = conditionTrackerMap.find(condition); if (condition_it == conditionTrackerMap.end()) { - ALOGW("cannot find the Condition %s in the config", condition.c_str()); + ALOGW("cannot find Condition \"%s\" in the config", condition.c_str()); return false; } for (const auto& link : links) { auto it = conditionTrackerMap.find(link.condition()); if (it == conditionTrackerMap.end()) { - ALOGW("cannot find the Condition %s in the config", link.condition().c_str()); + ALOGW("cannot find Condition \"%s\" in the config", link.condition().c_str()); return false; } allConditionTrackers[condition_it->second]->setSliced(true); @@ -110,7 +110,7 @@ bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& log new CombinationLogMatchingTracker(logMatcher.name(), index)); break; default: - ALOGE("Matcher %s malformed", logMatcher.name().c_str()); + ALOGE("Matcher \"%s\" malformed", logMatcher.name().c_str()); return false; // continue; } @@ -158,7 +158,7 @@ bool initConditions(const StatsdConfig& config, const unordered_map<string, int> break; } default: - ALOGE("Condition %s malformed", condition.name().c_str()); + ALOGE("Condition \"%s\" malformed", condition.name().c_str()); return false; } if (conditionTrackerMap.find(condition.name()) != conditionTrackerMap.end()) { @@ -204,7 +204,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l for (int i = 0; i < config.count_metric_size(); i++) { const CountMetric& metric = config.count_metric(i); if (!metric.has_what()) { - ALOGW("cannot find what in CountMetric %s", metric.name().c_str()); + ALOGW("cannot find \"what\" in CountMetric \"%s\"", metric.name().c_str()); return false; } @@ -341,7 +341,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l for (int i = 0; i < config.value_metric_size(); i++) { const ValueMetric& metric = config.value_metric(i); if (!metric.has_what()) { - ALOGW("cannot find what in ValueMetric %s", metric.name().c_str()); + ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str()); return false; } @@ -387,7 +387,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l for (int i = 0; i < config.gauge_metric_size(); i++) { const GaugeMetric& metric = config.gauge_metric(i); if (!metric.has_what()) { - ALOGW("cannot find what in ValueMetric %s", metric.name().c_str()); + ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str()); return false; } @@ -438,7 +438,7 @@ bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& me const Alert& alert = config.alert(i); const auto& itr = metricProducerMap.find(alert.metric_name()); if (itr == metricProducerMap.end()) { - ALOGW("alert has unknown metric name: %s %s", alert.name().c_str(), + ALOGW("alert \"%s\" has unknown metric name: \"%s\"", alert.name().c_str(), alert.metric_name().c_str()); return false; } diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 4f5df5512847..3fbcfeeee936 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -125,7 +125,7 @@ message UidMapping { } message StatsLogReport { - optional int32 metric_id = 1; + optional string metric_name = 1; optional int64 start_report_nanos = 2; diff --git a/cmds/statsd/tools/Android.mk b/cmds/statsd/tools/Android.mk new file mode 100644 index 000000000000..faf2d2c7d4e7 --- /dev/null +++ b/cmds/statsd/tools/Android.mk @@ -0,0 +1,20 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +# Include the sub-makefiles +include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk new file mode 100644 index 000000000000..1bd5f30b2f5c --- /dev/null +++ b/cmds/statsd/tools/dogfood/Android.mk @@ -0,0 +1,34 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_SRC_FILES += ../../src/stats_log.proto \ + ../../src/atoms_copy.proto + +LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../../src/ + +LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := StatsdDogfood +LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true +LOCAL_DEX_PREOPT := false +include $(BUILD_PACKAGE)
\ No newline at end of file diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml new file mode 100644 index 000000000000..cd76c9d379b5 --- /dev/null +++ b/cmds/statsd/tools/dogfood/AndroidManifest.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.statsd.dogfood" + android:sharedUserId="android.uid.system" + android:versionCode="1" + android:versionName="1.0" > + + <uses-permission android:name="android.permission.DUMP" /> + + <application + android:allowBackup="true" + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" > + <activity + android:name=".MainActivity" + android:label="@string/app_name" + android:launchMode="singleTop" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..55621cc1074f --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png diff --git a/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..11ec2068be19 --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png diff --git a/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..7c02b784aa5d --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png diff --git a/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..915d91441349 --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png diff --git a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml new file mode 100644 index 000000000000..39978971d6b4 --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <Button + android:id="@+id/push_config" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/holo_green_light" + android:text="@string/push_config"/> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/app_a_wake_lock_acquire1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_a_get_wl1"/> + <Button android:id="@+id/app_a_wake_lock_release1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_a_release_wl1"/> + </LinearLayout> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/app_a_wake_lock_acquire2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_a_get_wl2"/> + <Button android:id="@+id/app_a_wake_lock_release2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_a_release_wl2"/> + </LinearLayout> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/app_b_wake_lock_acquire1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_b_get_wl1"/> + <Button android:id="@+id/app_b_wake_lock_release1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_b_release_wl1"/> + </LinearLayout> + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/app_b_wake_lock_acquire2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_b_get_wl2"/> + <Button android:id="@+id/app_b_wake_lock_release2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_b_release_wl2"/> + </LinearLayout> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/plug" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/plug"/> + + <Button android:id="@+id/unplug" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/unplug"/> + </LinearLayout> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/screen_on" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/screen_on"/> + + <Button android:id="@+id/screen_off" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/screen_off"/> + </LinearLayout> + + <Button android:id="@+id/dump" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/holo_purple" + android:text="@string/dump"/> + + <TextView + android:id="@+id/header" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/report_header"/> + + <TextView + android:id="@+id/report_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + +</ScrollView>
\ No newline at end of file diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config Binary files differnew file mode 100644 index 000000000000..d5b8fedfb9ec --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config diff --git a/cmds/statsd/tools/dogfood/res/values/strings.xml b/cmds/statsd/tools/dogfood/res/values/strings.xml new file mode 100644 index 000000000000..7690df616ca5 --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/values/strings.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<resources> + + <string name="app_name">Statsd Dogfood</string> + + <string name="statsd_running">Statsd Running</string> + <string name="statsd_not_running">Statsd NOT Running</string> + + <string name="push_config">Push baseline config</string> + + <string name="app_a_foreground">App A foreground</string> + <string name="app_b_foreground">App B foreground</string> + + + <string name="app_a_get_wl1">App A get wl_1</string> + <string name="app_a_release_wl1">App A release wl_1</string> + + <string name="app_a_get_wl2">App A get wl_2</string> + <string name="app_a_release_wl2">App A release wl_2</string> + + <string name="app_b_get_wl1">App B get wl_1</string> + <string name="app_b_release_wl1">App B release wl_1</string> + + <string name="app_b_get_wl2">App B get wl_2</string> + <string name="app_b_release_wl2">App B release wl_2</string> + + <string name="plug">Plug</string> + <string name="unplug">Unplug</string> + + <string name="screen_on">Screen On</string> + <string name="screen_off">Screen Off</string> + + <string name="dump">DumpReport</string> + <string name="report_header">Report details</string> +</resources> diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java new file mode 100644 index 000000000000..f6dea42d448d --- /dev/null +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.statsd.dogfood; + +import android.text.format.DateFormat; + +import com.android.os.StatsLog; + +import java.util.List; + +public class DisplayProtoUtils { + public static void displayLogReport(StringBuilder sb, StatsLog.ConfigMetricsReport report) { + sb.append("ConfigKey: "); + if (report.hasConfigKey()) { + com.android.os.StatsLog.ConfigMetricsReport.ConfigKey key = report.getConfigKey(); + sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName()) + .append("\n"); + } + + sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n"); + for (StatsLog.StatsLogReport log : report.getMetricsList()) { + sb.append("\n\n"); + sb.append("metric id: ").append(log.getMetricName()).append("\n"); + sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n"); + sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n"); + + switch (log.getDataCase()) { + case DURATION_METRICS: + sb.append("Duration metric data\n"); + displayDurationMetricData(sb, log); + break; + case EVENT_METRICS: + sb.append("Event metric data\n"); + displayEventMetricData(sb, log); + break; + case COUNT_METRICS: + sb.append("Count metric data\n"); + displayCountMetricData(sb, log); + break; + case GAUGE_METRICS: + sb.append("Gauge metric data\n"); + displayGaugeMetricData(sb, log); + break; + case VALUE_METRICS: + sb.append("Value metric data\n"); + displayValueMetricData(sb, log); + break; + case DATA_NOT_SET: + sb.append("No metric data\n"); + break; + } + } + } + + public static String getDateStr(long nanoSec) { + return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString(); + } + + private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) { + for (com.android.os.StatsLog.KeyValuePair kv : pairs) { + sb.append(kv.getKey()).append(":"); + if (kv.hasValueBool()) { + sb.append(kv.getValueBool()); + } else if (kv.hasValueFloat()) { + sb.append(kv.getValueFloat()); + } else if (kv.hasValueInt()) { + sb.append(kv.getValueInt()); + } else if (kv.hasValueStr()) { + sb.append(kv.getValueStr()); + } + sb.append(" "); + } + } + + public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { + StatsLog.StatsLogReport.DurationMetricDataWrapper durationMetricDataWrapper + = log.getDurationMetrics(); + sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n"); + for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) { + sb.append("dimension: "); + displayDimension(sb, duration.getDimensionList()); + sb.append("\n"); + + for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) { + sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-") + .append(getDateStr(info.getEndBucketNanos())).append("] -> ") + .append(info.getDurationNanos()).append(" ns\n"); + } + } + } + + public static void displayEventMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { + sb.append("Contains ").append(log.getEventMetrics().getDataCount()).append(" events\n"); + StatsLog.StatsLogReport.EventMetricDataWrapper eventMetricDataWrapper = + log.getEventMetrics(); + for (StatsLog.EventMetricData event : eventMetricDataWrapper.getDataList()) { + sb.append(getDateStr(event.getTimestampNanos())).append(": "); + switch (event.getAtom().getPushedCase()) { + case SETTING_CHANGED: + sb.append("SETTING_CHANGED\n"); + break; + case SYNC_STATE_CHANGED: + sb.append("SYNC_STATE_CHANGED\n"); + break; + case AUDIO_STATE_CHANGED: + sb.append("AUDIO_STATE_CHANGED\n"); + break; + case CAMERA_STATE_CHANGED: + sb.append("CAMERA_STATE_CHANGED\n"); + break; + case ISOLATED_UID_CHANGED: + sb.append("ISOLATED_UID_CHANGED\n"); + break; + case SCREEN_STATE_CHANGED: + sb.append("SCREEN_STATE_CHANGED\n"); + break; + case SENSOR_STATE_CHANGED: + sb.append("SENSOR_STATE_CHANGED\n"); + break; + case BATTERY_LEVEL_CHANGED: + sb.append("BATTERY_LEVEL_CHANGED\n"); + break; + case PLUGGED_STATE_CHANGED: + sb.append("PLUGGED_STATE_CHANGED\n"); + break; + case WAKEUP_ALARM_OCCURRED: + sb.append("WAKEUP_ALARM_OCCURRED\n"); + break; + case BLE_SCAN_STATE_CHANGED: + sb.append("BLE_SCAN_STATE_CHANGED\n"); + break; + case CHARGING_STATE_CHANGED: + sb.append("CHARGING_STATE_CHANGED\n"); + break; + case GPS_SCAN_STATE_CHANGED: + sb.append("GPS_SCAN_STATE_CHANGED\n"); + break; + case KERNEL_WAKEUP_REPORTED: + sb.append("KERNEL_WAKEUP_REPORTED\n"); + break; + case WAKELOCK_STATE_CHANGED: + sb.append("WAKELOCK_STATE_CHANGED\n"); + break; + case WIFI_LOCK_STATE_CHANGED: + sb.append("WIFI_LOCK_STATE_CHANGED\n"); + break; + case WIFI_SCAN_STATE_CHANGED: + sb.append("WIFI_SCAN_STATE_CHANGED\n"); + break; + case BLE_SCAN_RESULT_RECEIVED: + sb.append("BLE_SCAN_RESULT_RECEIVED\n"); + break; + case DEVICE_ON_STATUS_CHANGED: + sb.append("DEVICE_ON_STATUS_CHANGED\n"); + break; + case FLASHLIGHT_STATE_CHANGED: + sb.append("FLASHLIGHT_STATE_CHANGED\n"); + break; + case SCREEN_BRIGHTNESS_CHANGED: + sb.append("SCREEN_BRIGHTNESS_CHANGED\n"); + break; + case UID_PROCESS_STATE_CHANGED: + sb.append("UID_PROCESS_STATE_CHANGED\n"); + break; + case UID_WAKELOCK_STATE_CHANGED: + sb.append("UID_WAKELOCK_STATE_CHANGED\n"); + break; + case DEVICE_TEMPERATURE_REPORTED: + sb.append("DEVICE_TEMPERATURE_REPORTED\n"); + break; + case SCHEDULED_JOB_STATE_CHANGED: + sb.append("SCHEDULED_JOB_STATE_CHANGED\n"); + break; + case MEDIA_CODEC_ACTIVITY_CHANGED: + sb.append("MEDIA_CODEC_ACTIVITY_CHANGED\n"); + break; + case WIFI_SIGNAL_STRENGTH_CHANGED: + sb.append("WIFI_SIGNAL_STRENGTH_CHANGED\n"); + break; + case PHONE_SIGNAL_STRENGTH_CHANGED: + sb.append("PHONE_SIGNAL_STRENGTH_CHANGED\n"); + break; + case DEVICE_IDLE_MODE_STATE_CHANGED: + sb.append("DEVICE_IDLE_MODE_STATE_CHANGED\n"); + break; + case BATTERY_SAVER_MODE_STATE_CHANGED: + sb.append("BATTERY_SAVER_MODE_STATE_CHANGED\n"); + break; + case PROCESS_LIFE_CYCLE_STATE_CHANGED: + sb.append("PROCESS_LIFE_CYCLE_STATE_CHANGED\n"); + break; + case ACTIVITY_FOREGROUND_STATE_CHANGED: + sb.append("ACTIVITY_FOREGROUND_STATE_CHANGED\n"); + break; + case BLE_UNOPTIMIZED_SCAN_STATE_CHANGED: + sb.append("BLE_UNOPTIMIZED_SCAN_STATE_CHANGED\n"); + break; + case LONG_PARTIAL_WAKELOCK_STATE_CHANGED: + sb.append("LONG_PARTIAL_WAKELOCK_STATE_CHANGED\n"); + break; + case PUSHED_NOT_SET: + sb.append("PUSHED_NOT_SET\n"); + break; + } + } + } + + public static void displayCountMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { + StatsLog.StatsLogReport.CountMetricDataWrapper countMetricDataWrapper + = log.getCountMetrics(); + sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n"); + for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) { + sb.append("dimension: "); + displayDimension(sb, count.getDimensionList()); + sb.append("\n"); + + for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) { + sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-") + .append(getDateStr(info.getEndBucketNanos())).append("] -> ") + .append(info.getCount()).append("\n"); + } + } + } + + public static void displayGaugeMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { + sb.append("Display me!"); + } + + public static void displayValueMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { + sb.append("Display me!"); + } +} diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java new file mode 100644 index 000000000000..5e3160e11849 --- /dev/null +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.statsd.dogfood; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Bundle; +import android.util.Log; +import android.util.StatsLog; +import android.util.StatsManager; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; +import android.os.IStatsManager; +import android.os.ServiceManager; + +import java.io.InputStream; + +import static com.android.statsd.dogfood.DisplayProtoUtils.displayLogReport; + +public class MainActivity extends Activity { + private final static String TAG = "StatsdDogfood"; + + final int[] mUids = {11111111, 2222222}; + StatsManager mStatsManager; + TextView mReportText; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_main); + + findViewById(R.id.app_a_wake_lock_acquire1).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockAcquire(0, "wl_1"); + } + }); + + findViewById(R.id.app_b_wake_lock_acquire1).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockAcquire(1, "wl_1"); + } + }); + + findViewById(R.id.app_a_wake_lock_acquire2).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockAcquire(0, "wl_2"); + } + }); + + findViewById(R.id.app_b_wake_lock_acquire2).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockAcquire(1, "wl_2"); + } + }); + + findViewById(R.id.app_a_wake_lock_release1).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockRelease(0, "wl_1"); + } + }); + + + findViewById(R.id.app_b_wake_lock_release1).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockRelease(1, "wl_1"); + } + }); + + findViewById(R.id.app_a_wake_lock_release2).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockRelease(0, "wl_2"); + } + }); + + + findViewById(R.id.app_b_wake_lock_release2).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockRelease(1, "wl_2"); + } + }); + + + findViewById(R.id.plug).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 1); + } + }); + + findViewById(R.id.unplug).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 0); + } + }); + + findViewById(R.id.screen_on).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 2); + } + }); + + findViewById(R.id.screen_off).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 1); + } + }); + + mReportText = (TextView) findViewById(R.id.report_text); + + findViewById(R.id.dump).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!statsdRunning()) { + return; + } + if (mStatsManager != null) { + byte[] data = mStatsManager.getData("fake"); + if (data != null) { + displayData(data); + } else { + mReportText.setText("Failed!"); + } + } + } + }); + + findViewById(R.id.push_config).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + try { + if (!statsdRunning()) { + return; + } + Resources res = getResources(); + InputStream inputStream = res.openRawResource(R.raw.statsd_baseline_config); + + byte[] config = new byte[inputStream.available()]; + inputStream.read(config); + if (mStatsManager != null) { + if (mStatsManager.addConfiguration("fake", + config, getPackageName(), MainActivity.this.getClass().getName())) { + Toast.makeText( + MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(MainActivity.this, "Config push FAILED!", + Toast.LENGTH_LONG).show(); + } + } + } catch (Exception e) { + Toast.makeText(MainActivity.this, "failed to read config", Toast.LENGTH_LONG); + } + } + }); + mStatsManager = (StatsManager) getSystemService("stats"); + } + + private boolean statsdRunning() { + if (IStatsManager.Stub.asInterface(ServiceManager.getService("stats")) == null) { + Log.d(TAG, "Statsd not running"); + Toast.makeText(MainActivity.this, "Statsd NOT running!", Toast.LENGTH_LONG).show(); + return false; + } + return true; + } + + @Override + public void onNewIntent(Intent intent) { + Log.d(TAG, "new intent: " + intent.getIntExtra("pkg", 0)); + int pkg = intent.getIntExtra("pkg", 0); + String name = intent.getStringExtra("name"); + if (intent.hasExtra("acquire")) { + onWakeLockAcquire(pkg, name); + } else if (intent.hasExtra("release")) { + onWakeLockRelease(pkg, name); + } + } + + private void displayData(byte[] data) { + com.android.os.StatsLog.ConfigMetricsReport report = null; + boolean good = false; + if (data != null) { + try { + report = com.android.os.StatsLog.ConfigMetricsReport.parseFrom(data); + good = true; + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + // display it in the text view. + } + } + int size = data == null ? 0 : data.length; + StringBuilder sb = new StringBuilder(); + sb.append(good ? "Proto parsing OK!" : "Proto parsing Error!"); + sb.append(" size:").append(size).append("\n"); + + if (good && report != null) { + displayLogReport(sb, report); + mReportText.setText(sb.toString()); + } + } + + + private void onWakeLockAcquire(int id, String name) { + if (id > 1) { + Log.d(TAG, "invalid pkg id"); + return; + } + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, mUids[id], 0, name, 1); + StringBuilder sb = new StringBuilder(); + sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) + .append(", ").append(name).append(", 1);"); + Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show(); + } + + private void onWakeLockRelease(int id, String name) { + if (id > 1) { + Log.d(TAG, "invalid pkg id"); + return; + } + StatsLog.write(10, mUids[id], 0, name, 0); + StringBuilder sb = new StringBuilder(); + sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) + .append(", ").append(name).append(", 0);"); + Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show(); + } +} diff --git a/core/java/Android.bp b/core/java/Android.bp index 1503445a1b02..d8c79293f9ba 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -3,7 +3,26 @@ filegroup { srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"], } -filegroup { - name: "IKeystoreService.aidl", +// only used by key_store_service +cc_library_static { + name: "libkeystore_aidl", srcs: ["android/security/IKeystoreService.aidl"], + aidl: { + export_aidl_headers: true, + include_dirs: ["frameworks/base/core/java/"], + }, + header_libs: [ + "libkeystore_headers", + ], + shared_libs: [ + "libbinder", + "libcutils", + "libhardware", + "libhidlbase", + "libhidltransport", + "libhwbinder", + "liblog", + "libselinux", + "libutils", + ], } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 9e926bd63a6a..d1aacad30a64 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -72,6 +72,7 @@ interface INotificationManager int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); int getDeletedChannelCount(String pkg, int uid); void deleteNotificationChannelGroup(String pkg, String channelGroupId); + NotificationChannelGroup getNotificationChannelGroup(String pkg, String channelGroupId); ParceledListSlice getNotificationChannelGroups(String pkg); boolean onlyHasDefaultChannel(String pkg, int uid); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index f931589b50a2..659cf169e994 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -93,6 +93,58 @@ public class NotificationManager { private static boolean localLOGV = false; /** + * Intent that is broadcast when a {@link NotificationChannel} is blocked + * (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked + * (when {@link NotificationChannel#getImportance()} is anything other than + * {@link #IMPORTANCE_NONE}). + * + * This broadcast is only sent to the app that owns the channel that has changed. + * + * Input: nothing + * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID} + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = + "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"; + + /** + * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or + * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the id of the + * object which has a new blocked state. + * + * The value will be the {@link NotificationChannel#getId()} of the channel for + * {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} and + * the {@link NotificationChannelGroup#getId()} of the group for + * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED}. + */ + public static final String EXTRA_BLOCK_STATE_CHANGED_ID = + "android.app.extra.BLOCK_STATE_CHANGED_ID"; + + /** + * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or + * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the new blocked + * state as a boolean. + * + * The value will be {@code true} if this channel or group is now blocked and {@code false} if + * this channel or group is now unblocked. + */ + public static final String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE"; + + + /** + * Intent that is broadcast when a {@link NotificationChannelGroup} is + * {@link NotificationChannelGroup#isBlocked() blocked} or unblocked. + * + * This broadcast is only sent to the app that owns the channel group that has changed. + * + * Input: nothing + * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID} + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = + "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"; + + /** * Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes. * This broadcast is only sent to registered receivers. * @@ -504,6 +556,20 @@ public class NotificationManager { } /** + * Returns the notification channel group settings for a given channel group id. + * + * The channel group must belong to your package, or null will be returned. + */ + public NotificationChannelGroup getNotificationChannelGroup(String channelGroupId) { + INotificationManager service = getService(); + try { + return service.getNotificationChannelGroup(mContext.getPackageName(), channelGroupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns all notification channel groups belonging to the calling app. */ public List<NotificationChannelGroup> getNotificationChannelGroups() { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index f0226b7e848f..0bca96907988 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3858,6 +3858,47 @@ public class DevicePolicyManager { */ 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 @@ public class DevicePolicyManager { 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 be0b9205bcdd..b7740e9e7314 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -162,7 +162,8 @@ interface IDevicePolicyManager { 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 530d84b420a3..7c40b4eaf375 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -244,6 +244,13 @@ public class JobInfo implements Parcelable { public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0; /** + * Allows this job to run despite doze restrictions as long as the app is in the foreground + * or on the temporary whitelist + * @hide + */ + public static final int FLAG_IMPORTANT_WHILE_FOREGROUND = 1 << 1; + + /** * @hide */ public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0; @@ -1333,6 +1340,30 @@ public class JobInfo implements Parcelable { } /** + * Setting this to true indicates that this job is important while the scheduling app + * is in the foreground or on the temporary whitelist for background restrictions. + * This means that the system will relax doze restrictions on this job during this time. + * + * Apps should use this flag only for short jobs that are essential for the app to function + * properly in the foreground. + * + * Note that once the scheduling app is no longer whitelisted from background restrictions + * and in the background, or the job failed due to unsatisfied constraints, + * this job should be expected to behave like other jobs without this flag. + * + * @param importantWhileForeground whether to relax doze restrictions for this job when the + * app is in the foreground. False by default. + */ + public Builder setImportantWhileForeground(boolean importantWhileForeground) { + if (importantWhileForeground) { + mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND; + } else { + mFlags &= (~FLAG_IMPORTANT_WHILE_FOREGROUND); + } + return this; + } + + /** * Set whether or not to persist this job across device reboots. * * @param isPersisted True to indicate that the job will be written to @@ -1395,6 +1426,10 @@ public class JobInfo implements Parcelable { "persisted job"); } } + if ((mFlags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && mHasEarlyConstraint) { + throw new IllegalArgumentException("An important while foreground job cannot " + + "have a time delay"); + } if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { throw new IllegalArgumentException("An idle mode job will not respect any" + " back-off policy, so calling setBackoffCriteria with" + diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 20342807f75f..edb27cd4ecf1 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import static android.os.Build.VERSION_CODES.DONUT; import android.annotation.IntDef; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.content.Context; @@ -890,6 +891,29 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public int versionCode; /** + * The user-visible SDK version (ex. 26) of the framework against which the application claims + * to have been compiled, or {@code 0} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#CODENAME Build.VERSION.SDK_INT}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + public int compileSdkVersion; + + /** + * The development codename (ex. "O", "REL") of the framework against which the application + * claims to have been compiled, or {@code null} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + @Nullable + public String compileSdkVersionCodename; + + /** * When false, indicates that all components within this application are * considered disabled, regardless of their individually set enabled status. */ @@ -1305,6 +1329,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(targetSandboxVersion); dest.writeString(classLoaderName); dest.writeStringArray(splitClassLoaderNames); + dest.writeInt(compileSdkVersion); + dest.writeString(compileSdkVersionCodename); } public static final Parcelable.Creator<ApplicationInfo> CREATOR @@ -1372,6 +1398,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { targetSandboxVersion = source.readInt(); classLoaderName = source.readString(); splitClassLoaderNames = source.readStringArray(); + compileSdkVersion = source.readInt(); + compileSdkVersionCodename = source.readString(); } /** diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index ba488f6a0518..f8889b6853d6 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -289,6 +290,29 @@ public class PackageInfo implements Parcelable { /** @hide */ public boolean isStaticOverlay; + /** + * The user-visible SDK version (ex. 26) of the framework against which the application claims + * to have been compiled, or {@code 0} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + public int compileSdkVersion; + + /** + * The development codename (ex. "O", "REL") of the framework against which the application + * claims to have been compiled, or {@code null} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + @Nullable + public String compileSdkVersionCodename; + public PackageInfo() { } @@ -344,6 +368,8 @@ public class PackageInfo implements Parcelable { dest.writeString(overlayTarget); dest.writeInt(isStaticOverlay ? 1 : 0); dest.writeInt(overlayPriority); + dest.writeInt(compileSdkVersion); + dest.writeString(compileSdkVersionCodename); } public static final Parcelable.Creator<PackageInfo> CREATOR @@ -396,6 +422,8 @@ public class PackageInfo implements Parcelable { overlayTarget = source.readString(); isStaticOverlay = source.readInt() != 0; overlayPriority = source.readInt(); + compileSdkVersion = source.readInt(); + compileSdkVersionCodename = source.readString(); // The component lists were flattened with the redundant ApplicationInfo // instances omitted. Distribute the canonical one here as appropriate. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 1c5cf15da062..d9ac6818975c 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -678,6 +678,8 @@ public class PackageParser { pi.overlayTarget = p.mOverlayTarget; pi.overlayPriority = p.mOverlayPriority; pi.isStaticOverlay = p.mIsStaticOverlay; + pi.compileSdkVersion = p.mCompileSdkVersion; + pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename; pi.firstInstallTime = firstInstallTime; pi.lastUpdateTime = lastUpdateTime; if ((flags&PackageManager.GET_GIDS) != 0) { @@ -2076,6 +2078,16 @@ public class PackageParser { pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false); + pkg.mCompileSdkVersion = sa.getInteger( + com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0); + pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion; + pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString( + com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0); + if (pkg.mCompileSdkVersionCodename != null) { + pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern(); + } + pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename; + sa.recycle(); return parseBaseApkCommon(pkg, null, res, parser, flags, outError); @@ -5967,6 +5979,9 @@ public class PackageParser { public boolean mIsStaticOverlay; public boolean mTrustedOverlay; + public int mCompileSdkVersion; + public String mCompileSdkVersionCodename; + /** * Data used to feed the KeySetManagerService */ @@ -6458,6 +6473,8 @@ public class PackageParser { mOverlayPriority = dest.readInt(); mIsStaticOverlay = (dest.readInt() == 1); mTrustedOverlay = (dest.readInt() == 1); + mCompileSdkVersion = dest.readInt(); + mCompileSdkVersionCodename = dest.readString(); mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot); mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot); @@ -6581,6 +6598,8 @@ public class PackageParser { dest.writeInt(mOverlayPriority); dest.writeInt(mIsStaticOverlay ? 1 : 0); dest.writeInt(mTrustedOverlay ? 1 : 0); + dest.writeInt(mCompileSdkVersion); + dest.writeString(mCompileSdkVersionCodename); dest.writeArraySet(mSigningKeys); dest.writeArraySet(mUpgradeKeySets); writeKeySetMapping(dest, mKeySetMapping); diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 025d46d12567..151e62de7b70 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -34,7 +34,7 @@ interface IUsbManager /* Returns a file descriptor for communicating with the USB device. * The native fd can be passed to usb_device_new() in libusbhost. */ - ParcelFileDescriptor openDevice(String deviceName); + ParcelFileDescriptor openDevice(String deviceName, String packageName); /* Returns the currently attached USB accessory */ UsbAccessory getCurrentAccessory(); @@ -55,7 +55,7 @@ interface IUsbManager void setAccessoryPackage(in UsbAccessory accessory, String packageName, int userId); /* Returns true if the caller has permission to access the device. */ - boolean hasDevicePermission(in UsbDevice device); + boolean hasDevicePermission(in UsbDevice device, String packageName); /* Returns true if the caller has permission to access the accessory. */ boolean hasAccessoryPermission(in UsbAccessory accessory); diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 6ce96698e444..bdb90bcca4f8 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -344,7 +344,7 @@ public class UsbManager { public UsbDeviceConnection openDevice(UsbDevice device) { try { String deviceName = device.getDeviceName(); - ParcelFileDescriptor pfd = mService.openDevice(deviceName); + ParcelFileDescriptor pfd = mService.openDevice(deviceName, mContext.getPackageName()); if (pfd != null) { UsbDeviceConnection connection = new UsbDeviceConnection(device); boolean result = connection.open(deviceName, pfd, mContext); @@ -400,6 +400,9 @@ public class UsbManager { * Permission might have been granted temporarily via * {@link #requestPermission(UsbDevice, PendingIntent)} or * by the user choosing the caller as the default application for the device. + * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that + * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they + * have additionally the {@link android.Manifest.permission#CAMERA} permission. * * @param device to check permissions for * @return true if caller has permission @@ -409,7 +412,7 @@ public class UsbManager { return false; } try { - return mService.hasDevicePermission(device); + return mService.hasDevicePermission(device, mContext.getPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -450,6 +453,10 @@ public class UsbManager { * permission was granted by the user * </ul> * + * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that + * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they + * have additionally the {@link android.Manifest.permission#CAMERA} permission. + * * @param device to request permissions for * @param pi PendingIntent for returning result */ diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index 64f8f39e2bca..d6e62cf1f88d 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -15,6 +15,7 @@ */ package android.net; +import android.annotation.NonNull; import android.annotation.StringDef; import android.os.Build; import android.os.Parcel; @@ -27,8 +28,10 @@ import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** - * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to - * RFC 4301. + * This class represents a single algorithm that can be used by an {@link IpSecTransform}. + * + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> */ public final class IpSecAlgorithm implements Parcelable { /** @@ -39,16 +42,16 @@ public final class IpSecAlgorithm implements Parcelable { public static final String CRYPT_AES_CBC = "cbc(aes)"; /** - * MD5 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in new - * applications and is provided for legacy compatibility with 3gpp infrastructure. + * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in + * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> * * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128. */ public static final String AUTH_HMAC_MD5 = "hmac(md5)"; /** - * SHA1 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in - * new applications and is provided for legacy compatibility with 3gpp infrastructure. + * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in + * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> * * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160. */ @@ -69,7 +72,7 @@ public final class IpSecAlgorithm implements Parcelable { public static final String AUTH_HMAC_SHA384 = "hmac(sha384)"; /** - * SHA512 HMAC Authentication/Integrity Algorithm + * SHA512 HMAC Authentication/Integrity Algorithm. * * <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512. */ @@ -80,9 +83,9 @@ public final class IpSecAlgorithm implements Parcelable { * * <p>Valid lengths for keying material are {160, 224, 288}. * - * <p>As per RFC4106 (Section 8.1), keying material consists of a 128, 192, or 256 bit AES key - * followed by a 32-bit salt. RFC compliance requires that the salt must be unique per - * invocation with the same key. + * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section + * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit + * salt. RFC compliance requires that the salt must be unique per invocation with the same key. * * <p>Valid ICV (truncation) lengths are {64, 96, 128}. */ @@ -105,48 +108,47 @@ public final class IpSecAlgorithm implements Parcelable { private final int mTruncLenBits; /** - * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the - * algorithm + * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are + * defined as constants in this class. * - * @param algorithm type for IpSec. - * @param key non-null Key padded to a multiple of 8 bits. + * @param algorithm name of the algorithm. + * @param key key padded to a multiple of 8 bits. */ - public IpSecAlgorithm(String algorithm, byte[] key) { + public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key) { this(algorithm, key, key.length * 8); } /** - * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the - * algorithm + * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are + * defined as constants in this class. + * + * <p>This constructor only supports algorithms that use a truncation length. i.e. + * Authentication and Authenticated Encryption algorithms. * - * @param algoName precise name of the algorithm to be used. - * @param key non-null Key padded to a multiple of 8 bits. - * @param truncLenBits the number of bits of output hash to use; only meaningful for - * Authentication or Authenticated Encryption (equivalent to ICV length). + * @param algorithm name of the algorithm. + * @param key key padded to a multiple of 8 bits. + * @param truncLenBits number of bits of output hash to use. */ - public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) { - if (!isTruncationLengthValid(algoName, truncLenBits)) { + public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) { + if (!isTruncationLengthValid(algorithm, truncLenBits)) { throw new IllegalArgumentException("Unknown algorithm or invalid length"); } - mName = algoName; + mName = algorithm; mKey = key.clone(); mTruncLenBits = Math.min(truncLenBits, key.length * 8); } - /** Retrieve the algorithm name */ + /** Get the algorithm name */ public String getName() { return mName; } - /** Retrieve the key for this algorithm */ + /** Get the key for this algorithm */ public byte[] getKey() { return mKey.clone(); } - /** - * Retrieve the truncation length, in bits, for the key in this algo. By default this will be - * the length in bits of the key. - */ + /** Get the truncation length of this algorithm, in bits */ public int getTruncationLengthBits() { return mTruncLenBits; } diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java index 61b13a922dd4..e6cd3fc130a0 100644 --- a/core/java/android/net/IpSecConfig.java +++ b/core/java/android/net/IpSecConfig.java @@ -20,7 +20,12 @@ import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; -/** @hide */ +/** + * This class encapsulates all the configuration parameters needed to create IPsec transforms and + * policies. + * + * @hide + */ public final class IpSecConfig implements Parcelable { private static final String TAG = "IpSecConfig"; @@ -38,6 +43,9 @@ public final class IpSecConfig implements Parcelable { // for outbound packets. It may also be used to select packets. private Network mNetwork; + /** + * This class captures the parameters that specifically apply to inbound or outbound traffic. + */ public static class Flow { // Minimum requirements for identifying a transform // SPI identifying the IPsec flow in packet processing diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index eccd5f47f2dd..a9e60ec88a8e 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -19,6 +19,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.os.Binder; import android.os.ParcelFileDescriptor; @@ -37,22 +38,28 @@ import java.net.InetAddress; import java.net.Socket; /** - * This class contains methods for managing IPsec sessions, which will perform kernel-space - * encryption and decryption of socket or Network traffic. + * This class contains methods for managing IPsec sessions. Once configured, the kernel will apply + * confidentiality (encryption) and integrity (authentication) to IP traffic. * - * <p>An IpSecManager may be obtained by calling {@link - * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link - * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE} + * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create + * transport mode security associations and apply them to individual sockets. Applications looking + * to create a VPN should use {@link VpnService}. + * + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> */ @SystemService(Context.IPSEC_SERVICE) public final class IpSecManager { private static final String TAG = "IpSecManager"; /** - * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index. + * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index. * * <p>No IPsec packet may contain an SPI of 0. + * + * @hide */ + @TestApi public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; /** @hide */ @@ -66,10 +73,12 @@ public final class IpSecManager { public static final int INVALID_RESOURCE_ID = 0; /** - * Indicates that the combination of remote InetAddress and SPI was non-unique for a given - * request. If encountered, selection of a new SPI is required before a transform may be - * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random - * or reserved using reserveSecurityParameterIndex. + * Thrown to indicate that a requested SPI is in use. + * + * <p>The combination of remote {@code InetAddress} and SPI must be unique across all apps on + * one device. If this error is encountered, a new SPI is required before a transform may be + * created. This error can be avoided by calling {@link + * IpSecManager#reserveSecurityParameterIndex}. */ public static final class SpiUnavailableException extends AndroidException { private final int mSpi; @@ -78,24 +87,26 @@ public final class IpSecManager { * Construct an exception indicating that a transform with the given SPI is already in use * or otherwise unavailable. * - * @param msg Description indicating the colliding SPI + * @param msg description indicating the colliding SPI * @param spi the SPI that could not be used due to a collision */ SpiUnavailableException(String msg, int spi) { - super(msg + "(spi: " + spi + ")"); + super(msg + " (spi: " + spi + ")"); mSpi = spi; } - /** Retrieve the SPI that caused a collision */ + /** Get the SPI that caused a collision. */ public int getSpi() { return mSpi; } } /** - * Indicates that the requested system resource for IPsec, such as a socket or other system - * resource is unavailable. If this exception is thrown, try releasing allocated objects of the - * type requested. + * Thrown to indicate that an IPsec resource is unavailable. + * + * <p>This could apply to resources such as sockets, {@link SecurityParameterIndex}, {@link + * IpSecTransform}, or other system resources. If this exception is thrown, users should release + * allocated objects of the type requested. */ public static final class ResourceUnavailableException extends AndroidException { @@ -106,6 +117,13 @@ public final class IpSecManager { private final IIpSecService mService; + /** + * This class represents a reserved SPI. + * + * <p>Objects of this type are used to track reserved security parameter indices. They can be + * obtained by calling {@link IpSecManager#reserveSecurityParameterIndex} and must be released + * by calling {@link #close()} when they are no longer needed. + */ public static final class SecurityParameterIndex implements AutoCloseable { private final IIpSecService mService; private final InetAddress mRemoteAddress; @@ -113,7 +131,7 @@ public final class IpSecManager { private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; private int mResourceId; - /** Return the underlying SPI held by this object */ + /** Get the underlying SPI held by this object. */ public int getSpi() { return mSpi; } @@ -135,6 +153,7 @@ public final class IpSecManager { mCloseGuard.close(); } + /** Check that the SPI was closed properly. */ @Override protected void finalize() throws Throwable { if (mCloseGuard != null) { @@ -197,13 +216,13 @@ public final class IpSecManager { } /** - * Reserve an SPI for traffic bound towards the specified remote address. + * Reserve a random SPI for traffic bound to or from the specified remote address. * * <p>If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress. + * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress * @return the reserved SecurityParameterIndex * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated * for this user @@ -223,17 +242,18 @@ public final class IpSecManager { } /** - * Reserve an SPI for traffic bound towards the specified remote address. + * Reserve the requested SPI for traffic bound to or from the specified remote address. * * <p>If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress. - * @param requestedSpi the requested SPI, or '0' to allocate a random SPI. + * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress + * @param requestedSpi the requested SPI, or '0' to allocate a random SPI * @return the reserved SecurityParameterIndex * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated * for this user + * @throws SpiUnavailableException indicating that the requested SPI could not be reserved */ public SecurityParameterIndex reserveSecurityParameterIndex( int direction, InetAddress remoteAddress, int requestedSpi) @@ -245,16 +265,28 @@ public final class IpSecManager { } /** - * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec - * encapsulation of the traffic flowing between the socket and the remote InetAddress of that - * transform. For security reasons, attempts to send traffic to any IP address other than the - * address associated with that transform will throw an IOException. In addition, if the - * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to - * send() or receive() until the transform is removed from the socket by calling {@link - * #removeTransportModeTransform(Socket, IpSecTransform)}; + * Apply an IPsec transform to a stream socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * + * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform + * will be removed. However, inbound traffic on the old transform will continue to be decrypted + * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap + * allows rekey procedures where both transforms are valid until both endpoints are using the + * new transform and all in-flight packets have been received. * * @param socket a stream socket - * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied * @hide */ public void applyTransportModeTransform(Socket socket, IpSecTransform transform) @@ -265,16 +297,28 @@ public final class IpSecManager { } /** - * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec - * encapsulation of the traffic flowing between the socket and the remote InetAddress of that - * transform. For security reasons, attempts to send traffic to any IP address other than the - * address associated with that transform will throw an IOException. In addition, if the - * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to - * send() or receive() until the transform is removed from the socket by calling {@link - * #removeTransportModeTransform(DatagramSocket, IpSecTransform)}; + * Apply an IPsec transform to a datagram socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * + * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform + * will be removed. However, inbound traffic on the old transform will continue to be decrypted + * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap + * allows rekey procedures where both transforms are valid until both endpoints are using the + * new transform and all in-flight packets have been received. * * @param socket a datagram socket - * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied * @hide */ public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform) @@ -285,16 +329,28 @@ public final class IpSecManager { } /** - * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec - * encapsulation of the traffic flowing between the socket and the remote InetAddress of that - * transform. For security reasons, attempts to send traffic to any IP address other than the - * address associated with that transform will throw an IOException. In addition, if the - * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to - * send() or receive() until the transform is removed from the socket by calling {@link - * #removeTransportModeTransform(FileDescriptor, IpSecTransform)}; + * Apply an IPsec transform to a socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * + * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform + * will be removed. However, inbound traffic on the old transform will continue to be decrypted + * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap + * allows rekey procedures where both transforms are valid until both endpoints are using the + * new transform and all in-flight packets have been received. * * @param socket a socket file descriptor - * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied */ public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform) throws IOException { @@ -323,6 +379,7 @@ public final class IpSecManager { * Applications should probably not use this API directly. Instead, they should use {@link * VpnService} to provide VPN capability in a more generic fashion. * + * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked. * @param net a {@link Network} that will be tunneled via IP Sec. * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform. * @hide @@ -330,14 +387,19 @@ public final class IpSecManager { public void applyTunnelModeTransform(Network net, IpSecTransform transform) {} /** - * Remove a transform from a given stream socket. Once removed, traffic on the socket will not - * be encypted. This allows sockets that have been used for IPsec to be reclaimed for - * communication in the clear in the event socket reuse is desired. This operation will succeed - * regardless of the underlying state of a transform. If a transform is removed, communication - * on all sockets to which that transform was applied will fail until this method is called. + * Remove an IPsec transform from a stream socket. + * + * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed + * regardless of the state of the transform. Removing a transform from a socket allows the + * socket to be reused for communication in the clear. + * + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. * - * @param socket a socket that previously had a transform applied to it. + * @param socket a socket that previously had a transform applied to it * @param transform the IPsec Transform that was previously applied to the given socket + * @throws IOException indicating that the transform could not be removed from the socket * @hide */ public void removeTransportModeTransform(Socket socket, IpSecTransform transform) @@ -348,14 +410,19 @@ public final class IpSecManager { } /** - * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not - * be encypted. This allows sockets that have been used for IPsec to be reclaimed for - * communication in the clear in the event socket reuse is desired. This operation will succeed - * regardless of the underlying state of a transform. If a transform is removed, communication - * on all sockets to which that transform was applied will fail until this method is called. + * Remove an IPsec transform from a datagram socket. * - * @param socket a socket that previously had a transform applied to it. + * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed + * regardless of the state of the transform. Removing a transform from a socket allows the + * socket to be reused for communication in the clear. + * + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. + * + * @param socket a socket that previously had a transform applied to it * @param transform the IPsec Transform that was previously applied to the given socket + * @throws IOException indicating that the transform could not be removed from the socket * @hide */ public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) @@ -366,14 +433,19 @@ public final class IpSecManager { } /** - * Remove a transform from a given stream socket. Once removed, traffic on the socket will not - * be encypted. This allows sockets that have been used for IPsec to be reclaimed for - * communication in the clear in the event socket reuse is desired. This operation will succeed - * regardless of the underlying state of a transform. If a transform is removed, communication - * on all sockets to which that transform was applied will fail until this method is called. + * Remove an IPsec transform from a socket. + * + * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed + * regardless of the state of the transform. Removing a transform from a socket allows the + * socket to be reused for communication in the clear. * - * @param socket a socket file descriptor that previously had a transform applied to it. + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. + * + * @param socket a socket that previously had a transform applied to it * @param transform the IPsec Transform that was previously applied to the given socket + * @throws IOException indicating that the transform could not be removed from the socket */ public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform) throws IOException { @@ -382,7 +454,7 @@ public final class IpSecManager { } } - /* Call down to activate a transform */ + /* Call down to remove a transform */ private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { try { mService.removeTransportModeTransform(pfd, transform.getResourceId()); @@ -397,6 +469,7 @@ public final class IpSecManager { * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is * lost, all traffic will drop. * + * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked. * @param net a network that currently has transform applied to it. * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given * network @@ -405,11 +478,18 @@ public final class IpSecManager { public void removeTunnelModeTransform(Network net, IpSecTransform transform) {} /** - * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for - * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic. + * This class provides access to a UDP encapsulation Socket. * - * <p>The socket provided by this class cannot be re-bound or closed via the inner - * FileDescriptor. Instead, disposing of this socket requires a call to close(). + * <p>{@code UdpEncapsulationSocket} wraps a system-provided datagram socket intended for IKEv2 + * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link + * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the + * caller. The caller should not close the {@code FileDescriptor} returned by {@link + * #getSocket}, but should use {@link #close} instead. + * + * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic + * of the next user who binds to that port. To prevent this scenario, these sockets are held + * open by the system so that they may only be closed by calling {@link #close} or when the user + * process exits. */ public static final class UdpEncapsulationSocket implements AutoCloseable { private final ParcelFileDescriptor mPfd; @@ -443,7 +523,7 @@ public final class IpSecManager { mCloseGuard.open("constructor"); } - /** Access the inner UDP Encapsulation Socket */ + /** Get the wrapped socket. */ public FileDescriptor getSocket() { if (mPfd == null) { return null; @@ -451,22 +531,19 @@ public final class IpSecManager { return mPfd.getFileDescriptor(); } - /** Retrieve the port number of the inner encapsulation socket */ + /** Get the bound port of the wrapped socket. */ public int getPort() { return mPort; } - @Override /** - * Release the resources that have been reserved for this Socket. + * Close this socket. * - * <p>This method closes the underlying socket, reducing a user's allocated sockets in the - * system. This must be done as part of cleanup following use of a socket. Failure to do so - * will cause the socket to count against a total allocation limit for IpSec and eventually - * fail due to resource limits. - * - * @param fd a file descriptor previously returned as a UDP Encapsulation socket. + * <p>This closes the wrapped socket. Open encapsulation sockets count against a user's + * resource limits, and forgetting to close them eventually will result in {@link + * ResourceUnavailableException} being thrown. */ + @Override public void close() throws IOException { try { mService.closeUdpEncapsulationSocket(mResourceId); @@ -483,6 +560,7 @@ public final class IpSecManager { mCloseGuard.close(); } + /** Check that the socket was closed properly. */ @Override protected void finalize() throws Throwable { if (mCloseGuard != null) { @@ -499,21 +577,14 @@ public final class IpSecManager { }; /** - * Open a socket that is bound to a free UDP port on the system. - * - * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by - * the caller. This provides safe access to a socket on a port that can later be used as a UDP - * Encapsulation port. + * Open a socket for UDP encapsulation and bind to the given port. * - * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the - * socket port. Explicitly opening this port is only necessary if communication is desired on - * that port. + * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket. * - * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this - * method will bind to the specified port or fail. To retrieve the port number, call {@link - * android.system.Os#getsockname(FileDescriptor)}. - * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime - * of the object. + * @param port a local UDP port + * @return a socket that is bound to the given port + * @throws IOException indicating that the socket could not be opened or bound + * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open */ // Returning a socket in this fashion that has been created and bound by the system // is the only safe way to ensure that a socket is both accessible to the user and @@ -533,17 +604,16 @@ public final class IpSecManager { } /** - * Open a socket that is bound to a port selected by the system. + * Open a socket for UDP encapsulation. * - * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by - * the caller. This provides safe access to a socket on a port that can later be used as a UDP - * Encapsulation port. + * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket. * - * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the - * socket port. Explicitly opening this port is only necessary if communication is desired on - * that port. + * <p>The local port of the returned socket can be obtained by calling {@link + * UdpEncapsulationSocket#getPort()}. * - * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port + * @return a socket that is bound to a local port + * @throws IOException indicating that the socket could not be opened or bound + * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open */ // Returning a socket in this fashion that has been created and bound by the system // is the only safe way to ensure that a socket is both accessible to the user and @@ -556,7 +626,7 @@ public final class IpSecManager { } /** - * Retrieve an instance of an IpSecManager within you application context + * Construct an instance of IpSecManager within an application context. * * @param context the application context for this manager * @hide diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 48b5bd5c3d5b..cda4ec762caf 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -38,27 +38,29 @@ import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; /** - * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec. + * This class represents an IPsec transform, which comprises security associations in one or both + * directions. * - * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout - * the lifetime of the underlying transform. If a transform object leaves scope, the underlying - * transform may be disabled automatically, with likely undesirable results. + * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform} + * object encapsulates the properties and state of an inbound and outbound IPsec security + * association. That includes, but is not limited to, algorithm choice, key material, and allocated + * system resources. * - * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array - * of traffic or may represent a transport mode transform operating on a Socket or Sockets. + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> */ public final class IpSecTransform implements AutoCloseable { private static final String TAG = "IpSecTransform"; /** - * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies - * to traffic towards the host. + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic towards the host. */ public static final int DIRECTION_IN = 0; /** - * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies - * to traffic from the host. + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic from the host. */ public static final int DIRECTION_OUT = 1; @@ -77,16 +79,16 @@ public final class IpSecTransform implements AutoCloseable { public static final int ENCAP_NONE = 0; /** - * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad - * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP. + * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP + * header and payload. This prevents traffic from being interpreted as ESP or IKEv2. * * @hide */ public static final int ENCAP_ESPINUDP_NON_IKE = 1; /** - * IpSec traffic will be encapsulated within UDP as per <a - * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>. + * IPsec traffic will be encapsulated within UDP as per + * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>. * * @hide */ @@ -165,13 +167,14 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Deactivate an IpSecTransform and free all resources for that transform that are managed by - * the system for this Transform. + * Deactivate this {@code IpSecTransform} and free allocated resources. * - * <p>Deactivating a transform while it is still applied to any Socket will result in sockets - * refusing to send or receive data. This method will silently succeed if the specified - * transform has already been removed; thus, it is always safe to attempt cleanup when a - * transform is no longer needed. + * <p>Deactivating a transform while it is still applied to a socket will result in errors on + * that socket. Make sure to remove transforms by calling {@link + * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a + * socket will not deactivate it (because one transform may be applied to multiple sockets). + * + * <p>It is safe to call this method on a transform that has already been deactivated. */ public void close() { Log.d(TAG, "Removing Transform with Id " + mResourceId); @@ -197,6 +200,7 @@ public final class IpSecTransform implements AutoCloseable { } } + /** Check that the transform was closed properly. */ @Override protected void finalize() throws Throwable { if (mCloseGuard != null) { @@ -264,65 +268,63 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Builder object to facilitate the creation of IpSecTransform objects. - * - * <p>Apply additional properties to the transform and then call a build() method to return an - * IpSecTransform object. - * - * @see Builder#buildTransportModeTransform(InetAddress) + * This class is used to build {@link IpSecTransform} objects. */ public static class Builder { private Context mContext; private IpSecConfig mConfig; /** - * Add an encryption algorithm to the transform for the given direction. + * Set the encryption algorithm for the given direction. * - * <p>If encryption is set for a given direction without also providing an SPI for that - * direction, creation of an IpSecTransform will fail upon calling a build() method. + * <p>If encryption is set for a direction without also providing an SPI for that direction, + * creation of an {@code IpSecTransform} will fail when attempting to build the transform. * - * <p>Authenticated encryption is mutually exclusive with encryption and authentication. + * <p>Encryption is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. */ public IpSecTransform.Builder setEncryption( @TransformDirection int direction, IpSecAlgorithm algo) { + // TODO: throw IllegalArgumentException if algo is not an encryption algorithm. mConfig.setEncryption(direction, algo); return this; } /** - * Add an authentication/integrity algorithm to the transform. + * Set the authentication (integrity) algorithm for the given direction. * - * <p>If authentication is set for a given direction without also providing an SPI for that - * direction, creation of an IpSecTransform will fail upon calling a build() method. + * <p>If authentication is set for a direction without also providing an SPI for that + * direction, creation of an {@code IpSecTransform} will fail when attempting to build the + * transform. * - * <p>Authenticated encryption is mutually exclusive with encryption and authentication. + * <p>Authentication is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. */ public IpSecTransform.Builder setAuthentication( @TransformDirection int direction, IpSecAlgorithm algo) { + // TODO: throw IllegalArgumentException if algo is not an authentication algorithm. mConfig.setAuthentication(direction, algo); return this; } /** - * Add an authenticated encryption algorithm to the transform for the given direction. + * Set the authenticated encryption algorithm for the given direction. * * <p>If an authenticated encryption algorithm is set for a given direction without also - * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling - * a build() method. + * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when + * attempting to build the transform. * * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as - * referred to in RFC 4301) + * referred to in <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>). * * <p>Authenticated encryption is mutually exclusive with encryption and authentication. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to * be applied. */ @@ -333,19 +335,16 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Set the SPI, which uniquely identifies a particular IPsec session from others. Because - * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a - * given destination address. + * Set the SPI for the given direction. * - * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as - * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int, - * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being - * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}. + * <p>Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies + * packets to a given destination address. To prevent SPI collisions, values should be + * reserved by calling {@link IpSecManager#reserveSecurityParameterIndex}. * - * <p>Unless an SPI is set for a given direction, traffic in that direction will be - * sent/received without any IPsec applied. + * <p>If the SPI and algorithms are omitted for one direction, traffic in that direction + * will not be encrypted or authenticated. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed * traffic */ @@ -356,11 +355,10 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Specify the network on which this transform will emit its traffic; (otherwise it will - * emit on the default network). + * Set the {@link Network} which will carry tunneled traffic. * - * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in - * tunnel mode. + * <p>Restricts the transformed traffic to a particular {@link Network}. This is required + * for tunnel mode, otherwise tunneled traffic would be sent on the default network. * * @hide */ @@ -371,15 +369,18 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Add UDP encapsulation to an IPv4 transform + * Add UDP encapsulation to an IPv4 transform. * - * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for - * details on how UDP should be applied to IPsec. + * <p>This allows IPsec traffic to pass through a NAT. * - * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and - * receiving encapsulating traffic. - * @param remotePort the UDP port number of the remote that will send and receive - * encapsulated traffic. In the case of IKE, this is likely port 4500. + * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec + * ESP Packets</a> + * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23, + * NAT Traversal of IKEv2</a> + * + * @param localSocket a socket for sending and receiving encapsulated traffic + * @param remotePort the UDP port number of the remote host that will send and receive + * encapsulated traffic. In the case of IKEv2, this should be port 4500. */ public IpSecTransform.Builder setIpv4Encapsulation( IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { @@ -393,12 +394,15 @@ public final class IpSecTransform implements AutoCloseable { // TODO: Probably a better exception to throw for NATTKeepalive failure // TODO: Specify the needed NATT keepalive permission. /** - * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded - * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot - * be activated, then the transform will fail to activate and throw an IOException. + * Set NAT-T keepalives to be sent with a given interval. + * + * <p>This will set power-efficient keepalive packets to be sent by the system. If NAT-T + * keepalive is requested but cannot be activated, then creation of an {@link + * IpSecTransform} will fail when calling the build method. + * + * @param intervalSeconds the maximum number of seconds between keepalive packets. Must be + * between 20s and 3600s. * - * @param intervalSeconds the maximum number of seconds between keepalive packets, no less - * than 20s and no more than 3600s. * @hide */ @SystemApi @@ -408,36 +412,29 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform. - * Some parameters have interdependencies that are checked at build time. If a well-formed - * transform cannot be created from the supplied parameters, this method will throw an - * Exception. + * Build a transport mode {@link IpSecTransform}. * - * <p>Upon a successful return from this call, the provided IpSecTransform will be active - * and may be applied to sockets. If too many IpSecTransform objects are active for a given - * user this operation will fail and throw ResourceUnavailableException. To avoid these - * exceptions, unused Transform objects must be cleaned up by calling {@link - * IpSecTransform#close()} when they are no longer needed. + * <p>This builds and activates a transport mode transform. Note that an active transform + * will not affect any network traffic until it has been applied to one or more sockets. * - * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this - * socket will cause the transform to be applied. - * <p>Note that an active transform will not impact any network traffic until it has - * been applied to one or more Sockets. Calling this method is a necessary precondition - * for applying it to a socket, but is not sufficient to actually apply IPsec. + * @see IpSecManager#applyTransportModeTransform + * + * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use + * this transform * @throws IllegalArgumentException indicating that a particular combination of transform - * properties is invalid. - * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms - * may be allocated - * @throws SpiUnavailableException if the SPI collides with an existing transform - * (unlikely). - * @throws ResourceUnavailableException if the current user currently has exceeded the - * number of allowed active transforms. + * properties is invalid + * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are + * active + * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI + * collides with an existing transform + * @throws IOException indicating other errors */ public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress) throws IpSecManager.ResourceUnavailableException, IpSecManager.SpiUnavailableException, IOException { mConfig.setMode(MODE_TRANSPORT); mConfig.setRemoteAddress(remoteAddress.getHostAddress()); + // FIXME: modifying a builder after calling build can change the built transform. return new IpSecTransform(mContext, mConfig).activate(); } @@ -465,9 +462,9 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Create a new IpSecTransform.Builder to construct an IpSecTransform + * Create a new IpSecTransform.Builder. * - * @param context current Context + * @param context current context */ public Builder(@NonNull Context context) { Preconditions.checkNotNull(context); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index a8bd940326d6..af91e81959d1 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -222,8 +222,10 @@ public abstract class BatteryStats implements Parcelable { * - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly] * New in version 27: * - Always On Display (screen doze mode) time and power + * New in version 28: + * - Light/Deep Doze power */ - static final int CHECKIN_VERSION = 27; + static final int CHECKIN_VERSION = 28; /** * Old version, we hit 9 and ran out of room, need to remove. @@ -2696,6 +2698,18 @@ public abstract class BatteryStats implements Parcelable { public abstract long getUahDischarge(int which); /** + * @return the amount of battery discharge while the device is in light idle mode, measured in + * micro-Ampere-hours. + */ + public abstract long getUahDischargeLightDoze(int which); + + /** + * @return the amount of battery discharge while the device is in deep idle mode, measured in + * micro-Ampere-hours. + */ + public abstract long getUahDischargeDeepDoze(int which); + + /** * Returns the estimated real battery capacity, which may be less than the capacity * declared by the PowerProfile. * @return The estimated battery capacity in mAh. @@ -3327,6 +3341,8 @@ public abstract class BatteryStats implements Parcelable { final long dischargeCount = getUahDischarge(which); final long dischargeScreenOffCount = getUahDischargeScreenOff(which); final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which); + final long dischargeLightDozeCount = getUahDischargeLightDoze(which); + final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which); final StringBuilder sb = new StringBuilder(128); @@ -3497,14 +3513,16 @@ public abstract class BatteryStats implements Parcelable { getDischargeStartLevel()-getDischargeCurrentLevel(), getDischargeAmountScreenOn(), getDischargeAmountScreenOff(), dischargeCount / 1000, dischargeScreenOffCount / 1000, - getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000); + getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000, + dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000); } else { dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA, getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(), getDischargeAmountScreenOnSinceCharge(), getDischargeAmountScreenOffSinceCharge(), dischargeCount / 1000, dischargeScreenOffCount / 1000, - getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000); + getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000, + dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000); } if (reqUid < 0) { @@ -4169,6 +4187,26 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } + final long dischargeLightDozeCount = getUahDischargeLightDoze(which); + if (dischargeLightDozeCount >= 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Device light doze discharge: "); + sb.append(BatteryStatsHelper.makemAh(dischargeLightDozeCount / 1000.0)); + sb.append(" mAh"); + pw.println(sb.toString()); + } + + final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which); + if (dischargeDeepDozeCount >= 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Device deep doze discharge: "); + sb.append(BatteryStatsHelper.makemAh(dischargeDeepDozeCount / 1000.0)); + sb.append(" mAh"); + pw.println(sb.toString()); + } + pw.print(" Start clock time: "); pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString()); @@ -7131,6 +7169,10 @@ public abstract class BatteryStats implements Parcelable { getUahDischargeScreenOff(which) / 1000); proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE, getUahDischargeScreenDoze(which) / 1000); + proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_LIGHT_DOZE, + getUahDischargeLightDoze(which) / 1000); + proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_DEEP_DOZE, + getUahDischargeDeepDoze(which) / 1000); proto.end(bdToken); // Time remaining diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index f2483c0a71e5..6ede72d49806 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -120,7 +120,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 169 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS; @@ -599,6 +599,8 @@ public class BatteryStatsImpl extends BatteryStats { private LongSamplingCounter mDischargeScreenOffCounter; private LongSamplingCounter mDischargeScreenDozeCounter; private LongSamplingCounter mDischargeCounter; + private LongSamplingCounter mDischargeLightDozeCounter; + private LongSamplingCounter mDischargeDeepDozeCounter; static final int MAX_LEVEL_STEPS = 200; @@ -697,6 +699,16 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getUahDischargeLightDoze(int which) { + return mDischargeLightDozeCounter.getCountLocked(which); + } + + @Override + public long getUahDischargeDeepDoze(int which) { + return mDischargeDeepDozeCounter.getCountLocked(which); + } + + @Override public int getEstimatedBatteryCapacity() { return mEstimatedBatteryCapacity; } @@ -9085,6 +9097,8 @@ public class BatteryStatsImpl extends BatteryStats { mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase); mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase); mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase); + mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase); + mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase); mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase); mOnBattery = mOnBatteryInternal = false; long uptime = mClocks.uptimeMillis() * 1000; @@ -9664,6 +9678,8 @@ public class BatteryStatsImpl extends BatteryStats { mChargeStepTracker.init(); mDischargeScreenOffCounter.reset(false); mDischargeScreenDozeCounter.reset(false); + mDischargeLightDozeCounter.reset(false); + mDischargeDeepDozeCounter.reset(false); mDischargeCounter.reset(false); } @@ -11263,6 +11279,11 @@ public class BatteryStatsImpl extends BatteryStats { if (isScreenDoze(mScreenState)) { mDischargeScreenDozeCounter.addCountLocked(chargeDiff); } + if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) { + mDischargeLightDozeCounter.addCountLocked(chargeDiff); + } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) { + mDischargeDeepDozeCounter.addCountLocked(chargeDiff); + } } mHistoryCur.batteryChargeUAh = chargeUAh; setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh); @@ -11308,6 +11329,11 @@ public class BatteryStatsImpl extends BatteryStats { if (isScreenDoze(mScreenState)) { mDischargeScreenDozeCounter.addCountLocked(chargeDiff); } + if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) { + mDischargeLightDozeCounter.addCountLocked(chargeDiff); + } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) { + mDischargeDeepDozeCounter.addCountLocked(chargeDiff); + } } mHistoryCur.batteryChargeUAh = chargeUAh; changed = true; @@ -12069,6 +12095,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.readSummaryFromParcelLocked(in); mDischargeScreenOffCounter.readSummaryFromParcelLocked(in); mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in); + mDischargeLightDozeCounter.readSummaryFromParcelLocked(in); + mDischargeDeepDozeCounter.readSummaryFromParcelLocked(in); int NPKG = in.readInt(); if (NPKG > 0) { mDailyPackageChanges = new ArrayList<>(NPKG); @@ -12493,6 +12521,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.writeSummaryFromParcelLocked(out); mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out); mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out); + mDischargeLightDozeCounter.writeSummaryFromParcelLocked(out); + mDischargeDeepDozeCounter.writeSummaryFromParcelLocked(out); if (mDailyPackageChanges != null) { final int NPKG = mDailyPackageChanges.size(); out.writeInt(NPKG); @@ -13044,6 +13074,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in); mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); + mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); + mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); mLastWriteTime = in.readLong(); mRpmStats.clear(); @@ -13230,6 +13262,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.writeToParcel(out); mDischargeScreenOffCounter.writeToParcel(out); mDischargeScreenDozeCounter.writeToParcel(out); + mDischargeLightDozeCounter.writeToParcel(out); + mDischargeDeepDozeCounter.writeToParcel(out); out.writeLong(mLastWriteTime); out.writeInt(mRpmStats.size()); diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp index 4e88a83a92ea..d7300c4947d9 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 @@ struct JLineBreaksID { 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 @@ static inline std::vector<float> jintArrayToFloatVector(JNIEnv* env, jintArray j } } -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 inline StaticLayoutNative* toNative(jlong ptr) { 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 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, jintArray recycleFlags, jfloatArray charWidths) { - StaticLayoutNative* builder = toNative(nativePtr); + minikin::android::StaticLayoutNative* builder = toNative(nativePtr); ScopedCharArrayRO text(env, javaText); @@ -327,7 +169,7 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, // 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 @@ static void nAddStyleRun(jlong nativePtr, jlong nativePaint, jint start, jint en // 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 c33c0a0e0518..0a3344fe13f1 100644 --- a/core/proto/android/os/batterystats.proto +++ b/core/proto/android/os/batterystats.proto @@ -120,6 +120,14 @@ message SystemProto { // via a coulomb counter. For historical reasons, total_mah_screen_doze is // a subset of total_mah_screen_off. optional int64 total_mah_screen_doze = 8; + // Total amount of battery discharged in mAh while the device was in light doze mode. + // This will only be non-zero for devices that report battery discharge + // via a coulomb counter. + optional int64 total_mah_light_doze = 9; + // Total amount of battery discharged in mAh while the device was in deep doze mode. + // This will only be non-zero for devices that report battery discharge + // via a coulomb counter. + optional int64 total_mah_deep_doze = 10; }; optional BatteryDischarge battery_discharge = 2; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index feef5ce28f6c..d15c0752d5cc 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -485,7 +485,6 @@ <protected-broadcast android:name="android.content.jobscheduler.JOB_DEADLINE_EXPIRED" /> <protected-broadcast android:name="android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW" /> <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_SUPL" /> - <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" /> <protected-broadcast android:name="android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED" /> <protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" /> <protected-broadcast android:name="android.os.storage.action.DISK_SCANNED" /> @@ -504,6 +503,8 @@ <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_CHANGED" /> <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED" /> <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" /> + <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" /> + <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" /> <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" /> <protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" /> diff --git a/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml b/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml new file mode 100644 index 000000000000..89e58aae6c34 --- /dev/null +++ b/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="?attr/dialogPreferredPadding" + android:paddingLeft="?attr/dialogPreferredPadding" + android:paddingRight="?attr/dialogPreferredPadding"> + + <CheckBox + android:id="@+id/ask_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:text="@string/unsupported_compile_sdk_show" /> +</FrameLayout> diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index f26d6ed10c0c..e12f04af773b 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -21,9 +21,10 @@ for watch products. Do not translate. --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Only show settings item due to smaller real estate. --> + <!-- Show smaller list of items due to smaller real estate. --> <string-array translatable="false" name="config_globalActionsList"> - <item>assist</item> + <item>power</item> + <item>restart</item> </string-array> <!-- Base "touch slop" value used by ViewConfiguration as a diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index be0f6d9ac647..fe8ca560e5bf 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1340,6 +1340,23 @@ <p>The default value of this attribute is <code>1</code>. --> <attr name="targetSandboxVersion" format="integer" /> + <!-- The user-visible SDK version (ex. 26) of the framework against which the application was + compiled. This attribute is automatically specified by the Android build tools and should + NOT be manually specified. + <p> + This attribute is the compile-time equivalent of + {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}. --> + <attr name="compileSdkVersion" format="integer" /> + + <!-- The development codename (ex. "O") of the framework against which the application was + compiled, or "REL" if the application was compiled against a release build. This attribute + is automatically specified by the Android build tools and should NOT be manually + specified. + <p> + This attribute is the compile-time equivalent of + {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. --> + <attr name="compileSdkVersionCodename" format="string" /> + <!-- The <code>manifest</code> tag is the root of an <code>AndroidManifest.xml</code> file, describing the contents of an Android package (.apk) file. One @@ -1369,6 +1386,8 @@ <attr name="isolatedSplits" /> <attr name="isFeatureSplit" /> <attr name="targetSandboxVersion" /> + <attr name="compileSdkVersion" /> + <attr name="compileSdkVersionCodename" /> </declare-styleable> <!-- The <code>application</code> tag describes application-level components diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index edd793d89afa..c10461643406 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -940,9 +940,16 @@ 1 - Global actions menu 2 - Power off (with confirmation) 3 - Power off (without confirmation) + 4 - Go to voice assist --> <integer name="config_longPressOnPowerBehavior">1</integer> + <!-- Control the behavior when the user long presses the power button for a long time. + 0 - Nothing + 1 - Global actions menu + --> + <integer name="config_veryLongPressOnPowerBehavior">0</integer> + <!-- Control the behavior when the user long presses the back button. Non-zero values are only valid for watches as part of CDD/CTS. 0 - Nothing @@ -986,6 +993,9 @@ --> <integer name="config_shortPressOnSleepBehavior">0</integer> + <!-- Time to wait while a button is pressed before triggering a very long press. --> + <integer name="config_veryLongPressTimeout">6000</integer> + <!-- Package name for default keyguard appwidget [DO NOT TRANSLATE] --> <string name="widget_default_package_name" translatable="false"></string> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index fdd56c410ed2..d3533fe6e79f 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2848,6 +2848,10 @@ <public name="ttcIndex" /> <public name="fontVariationSettings" /> <public name="dialogCornerRadius" /> + <!-- @hide For use by platform and tools only. Developers should not specify this value. --> + <public name="compileSdkVersion" /> + <!-- @hide For use by platform and tools only. Developers should not specify this value. --> + <public name="compileSdkVersionCodename" /> </public-group> <public-group type="style" first-id="0x010302e0"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 7a3fa1a7b512..999ba7392fab 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2852,6 +2852,13 @@ <!-- [CHAR LIMIT=50] Unsupported display size dialog: check box label. --> <string name="unsupported_display_size_show">Always show</string> + <!-- [CHAR LIMIT=200] Unsupported compile SDK dialog: message. Shown when an app may not be compatible with the device's current version of Android. --> + <string name="unsupported_compile_sdk_message"><xliff:g id="app_name">%1$s</xliff:g> was built for preview version %2$s of the Android OS and may behave unexpectedly. An updated version of the app may be available.</string> + <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: check box label. --> + <string name="unsupported_compile_sdk_show">Always show</string> + <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: label for button to check for an app update. --> + <string name="unsupported_compile_sdk_check_update">Check for update</string> + <!-- Text of the alert that is displayed when an application has violated StrictMode. --> <string name="smv_application">The app <xliff:g id="application">%1$s</xliff:g> (process <xliff:g id="process">%2$s</xliff:g>) has violated its self-enforced StrictMode policy.</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 54970851f3c1..a1158162ee73 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -419,6 +419,8 @@ <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" /> <java-symbol type="integer" name="config_immersive_mode_confirmation_panic" /> <java-symbol type="integer" name="config_longPressOnPowerBehavior" /> + <java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" /> + <java-symbol type="integer" name="config_veryLongPressTimeout" /> <java-symbol type="integer" name="config_longPressOnBackBehavior" /> <java-symbol type="integer" name="config_backPanicBehavior" /> <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" /> @@ -3151,4 +3153,9 @@ <!-- From media projection --> <java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" /> <java-symbol type="string" name="config_batterySaverDeviceSpecificConfig" /> + + <!-- Compile SDK check --> + <java-symbol type="layout" name="unsupported_compile_sdk_dialog_content" /> + <java-symbol type="string" name="unsupported_compile_sdk_message" /> + <java-symbol type="string" name="unsupported_compile_sdk_check_update" /> </resources> diff --git a/data/keyboards/OWNERS b/data/keyboards/OWNERS new file mode 100644 index 000000000000..031a6c1c7a89 --- /dev/null +++ b/data/keyboards/OWNERS @@ -0,0 +1,4 @@ +set noparent + +michaelwr@google.com +svv@google.com diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index c475e122833a..306ed83c426b 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -721,14 +721,16 @@ public final class MediaFormat { /** * A key for boolean DEFAULT behavior for the track. The track with DEFAULT=true is * selected in the absence of a specific user choice. - * This is currently only used for subtitle tracks, when the user selected - * 'Default' for the captioning locale. + * This is currently used in two scenarios: + * 1) for subtitle tracks, when the user selected 'Default' for the captioning locale. + * 2) for a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track, indicating the image is the + * primary item in the file. + * The associated value is an integer, where non-0 means TRUE. This is an optional * field; if not specified, DEFAULT is considered to be FALSE. */ public static final String KEY_IS_DEFAULT = "is-default"; - /** * A key for the FORCED field for subtitle tracks. True if it is a * forced subtitle track. Forced subtitle tracks are essential for the diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java index 91e57ee073b0..02c71b283b21 100644 --- a/media/java/android/media/MediaMuxer.java +++ b/media/java/android/media/MediaMuxer.java @@ -258,12 +258,18 @@ final public class MediaMuxer { * in include/media/stagefright/MediaMuxer.h! */ private OutputFormat() {} + /** @hide */ + public static final int MUXER_OUTPUT_FIRST = 0; /** MPEG4 media file format*/ - public static final int MUXER_OUTPUT_MPEG_4 = 0; + public static final int MUXER_OUTPUT_MPEG_4 = MUXER_OUTPUT_FIRST; /** WEBM media file format*/ - public static final int MUXER_OUTPUT_WEBM = 1; + public static final int MUXER_OUTPUT_WEBM = MUXER_OUTPUT_FIRST + 1; /** 3GPP media file format*/ - public static final int MUXER_OUTPUT_3GPP = 2; + public static final int MUXER_OUTPUT_3GPP = MUXER_OUTPUT_FIRST + 2; + /** HEIF media file format*/ + public static final int MUXER_OUTPUT_HEIF = MUXER_OUTPUT_FIRST + 3; + /** @hide */ + public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_HEIF; }; /** @hide */ @@ -271,6 +277,7 @@ final public class MediaMuxer { OutputFormat.MUXER_OUTPUT_MPEG_4, OutputFormat.MUXER_OUTPUT_WEBM, OutputFormat.MUXER_OUTPUT_3GPP, + OutputFormat.MUXER_OUTPUT_HEIF, }) @Retention(RetentionPolicy.SOURCE) public @interface Format {} @@ -347,8 +354,7 @@ final public class MediaMuxer { } private void setUpMediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException { - if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 && format != OutputFormat.MUXER_OUTPUT_WEBM - && format != OutputFormat.MUXER_OUTPUT_3GPP) { + if (format < OutputFormat.MUXER_OUTPUT_FIRST || format > OutputFormat.MUXER_OUTPUT_LAST) { throw new IllegalArgumentException("format: " + format + " is invalid"); } mNativeObject = nativeSetup(fd, format); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 700c01a55ad6..0f498bcfbd58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -910,6 +910,7 @@ public class UserSwitcherController { context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.guest_exit_guest_dialog_remove), this); + SystemUIDialog.setWindowOnTop(this); setCanceledOnTouchOutside(false); mGuestId = guestId; mTargetId = targetId; @@ -937,6 +938,7 @@ public class UserSwitcherController { context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this); + SystemUIDialog.setWindowOnTop(this); } @Override diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java index a45a4f0c35d3..dd29a04e5f54 100644 --- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java @@ -48,7 +48,6 @@ import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreSession; import android.app.backup.ISelectBackupTransportCallback; -import android.app.backup.SelectBackupTransportCallback; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -103,6 +102,7 @@ import com.android.server.backup.fullbackup.PerformFullTransportBackupTask; import com.android.server.backup.internal.BackupHandler; import com.android.server.backup.internal.BackupRequest; import com.android.server.backup.internal.ClearDataObserver; +import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.internal.Operation; import com.android.server.backup.internal.PerformInitializeTask; import com.android.server.backup.internal.ProvisionedObserver; @@ -117,6 +117,7 @@ import com.android.server.backup.params.ClearRetryParams; import com.android.server.backup.params.RestoreParams; import com.android.server.backup.restore.ActiveRestoreSession; import com.android.server.backup.restore.PerformUnifiedRestoreTask; +import com.android.server.backup.transport.TransportClient; import com.android.server.backup.utils.AppBackupUtils; import com.android.server.backup.utils.BackupManagerMonitorUtils; import com.android.server.backup.utils.BackupObserverUtils; @@ -1585,8 +1586,27 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter return BackupManager.ERROR_BACKUP_NOT_ALLOWED; } + // We're using pieces of the new binding on-demand infra-structure and the old always-bound + // infra-structure below this comment. The TransportManager.getCurrentTransportClient() line + // is using the new one and TransportManager.getCurrentTransportBinder() is using the old. + // This is weird but there is a reason. + // This is the natural place to put TransportManager.getCurrentTransportClient() because of + // the null handling below that should be the same for TransportClient. + // TransportClient.connect() would return a IBackupTransport for us (instead of using the + // old infra), but it may block and we don't want this in this thread. + // The only usage of transport in this method is for transport.transportDirName(). When the + // push-from-transport part of binding on-demand is in place we will replace the calls for + // IBackupTransport.transportDirName() with calls for + // TransportManager.transportDirName(transportName) or similar. So we'll leave the old piece + // here until we implement that. + // TODO(brufino): Remove always-bound code mTransportManager.getCurrentTransportBinder() + TransportClient transportClient = + mTransportManager.getCurrentTransportClient("BMS.requestBackup()"); IBackupTransport transport = mTransportManager.getCurrentTransportBinder(); - if (transport == null) { + if (transportClient == null || transport == null) { + if (transportClient != null) { + mTransportManager.disposeOfTransportClient(transportClient, "BMS.requestBackup()"); + } BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); monitor = BackupManagerMonitorUtils.monitorEvent(monitor, BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, @@ -1594,6 +1614,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter return BackupManager.ERROR_TRANSPORT_ABORTED; } + OnTaskFinishedListener listener = + caller -> mTransportManager.disposeOfTransportClient(transportClient, caller); + ArrayList<String> fullBackupList = new ArrayList<>(); ArrayList<String> kvBackupList = new ArrayList<>(); for (String packageName : packages) { @@ -1640,8 +1663,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0; Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP); - msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer, - monitor, true, nonIncrementalBackup); + msg.obj = new BackupParams(transportClient, dirName, kvBackupList, fullBackupList, observer, + monitor, listener, true, nonIncrementalBackup); mBackupHandler.sendMessage(msg); return BackupManager.SUCCESS; } diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java index 7a0173f669af..a2b5cb883b8a 100644 --- a/services/backup/java/com/android/server/backup/TransportManager.java +++ b/services/backup/java/com/android/server/backup/TransportManager.java @@ -16,8 +16,9 @@ package com.android.server.backup; +import android.annotation.Nullable; import android.app.backup.BackupManager; -import android.app.backup.SelectBackupTransportCallback; +import android.app.backup.BackupTransport; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -44,9 +45,11 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; import com.android.server.EventLogTags; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportClientManager; +import com.android.server.backup.transport.TransportConnectionListener; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -60,8 +63,7 @@ public class TransportManager { private static final String TAG = "BackupTransportManager"; @VisibleForTesting - /* package */ static final String SERVICE_ACTION_TRANSPORT_HOST = - "android.backup.TRANSPORT_HOST"; + public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins @@ -72,6 +74,7 @@ public class TransportManager { private final PackageManager mPackageManager; private final Set<ComponentName> mTransportWhitelist; private final Handler mHandler; + private final TransportClientManager mTransportClientManager; /** * This listener is called after we bind to any transport. If it returns true, this is a valid @@ -95,6 +98,10 @@ public class TransportManager { @GuardedBy("mTransportLock") private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>(); + /** Names of transports we've bound to at least once */ + @GuardedBy("mTransportLock") + private final Map<String, ComponentName> mTransportsByName = new ArrayMap<>(); + /** * Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}. */ @@ -123,6 +130,7 @@ public class TransportManager { mCurrentTransportName = defaultTransport; mTransportBoundListener = listener; mHandler = new RebindOnTimeoutHandler(looper); + mTransportClientManager = new TransportClientManager(context); } void onPackageAdded(String packageName) { @@ -204,6 +212,67 @@ public class TransportManager { return null; } + /** + * Returns the transport name associated with {@param transportClient} or {@code null} if not + * found. + */ + @Nullable + public String getTransportName(TransportClient transportClient) { + ComponentName transportComponent = transportClient.getTransportComponent(); + synchronized (mTransportLock) { + for (Map.Entry<String, ComponentName> transportEntry : mTransportsByName.entrySet()) { + if (transportEntry.getValue().equals(transportComponent)) { + return transportEntry.getKey(); + } + } + return null; + } + } + + /** + * Returns a {@link TransportClient} for {@param transportName} or {@code null} if not found. + * + * @param transportName The name of the transport as returned by {@link BackupTransport#name()}. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient} or null if not found. + */ + @Nullable + public TransportClient getTransportClient(String transportName, String caller) { + ComponentName transportComponent = mTransportsByName.get(transportName); + if (transportComponent == null) { + Slog.w(TAG, "Transport " + transportName + " not registered"); + return null; + } + return mTransportClientManager.getTransportClient(transportComponent, caller); + } + + /** + * Returns a {@link TransportClient} for the current transport or null if not found. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient} or null if not found. + */ + @Nullable + public TransportClient getCurrentTransportClient(String caller) { + return getTransportClient(mCurrentTransportName, caller); + } + + /** + * Disposes of the {@link TransportClient}. + * + * @param transportClient The {@link TransportClient} to be disposed of. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + */ + public void disposeOfTransportClient(TransportClient transportClient, String caller) { + mTransportClientManager.disposeOfTransportClient(transportClient, caller); + } + String[] getBoundTransportNames() { synchronized (mTransportLock) { return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]); @@ -374,6 +443,7 @@ public class TransportManager { String componentShortString = component.flattenToShortString().intern(); if (success) { Slog.d(TAG, "Bound to transport: " + componentShortString); + mTransportsByName.put(mTransportName, component); mBoundTransports.put(mTransportName, component); for (TransportReadyCallback listener : mListeners) { listener.onSuccess(mTransportName); @@ -528,7 +598,7 @@ public class TransportManager { // These only exists to make it testable with Robolectric, which is not updated to API level 24 // yet. // TODO: Get rid of this once Robolectric is updated. - private static UserHandle createSystemUserHandle() { + public static UserHandle createSystemUserHandle() { return new UserHandle(UserHandle.USER_SYSTEM); } } diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index 8f823004d993..9011b95cf614 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -38,6 +38,8 @@ import com.android.server.EventLogTags; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.DataChangedJournal; import com.android.server.backup.RefactoredBackupManagerService; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.TransportManager; import com.android.server.backup.fullbackup.PerformAdbBackupTask; import com.android.server.backup.fullbackup.PerformFullTransportBackupTask; import com.android.server.backup.params.AdbBackupParams; @@ -51,10 +53,8 @@ import com.android.server.backup.params.RestoreParams; import com.android.server.backup.restore.PerformAdbRestoreTask; import com.android.server.backup.restore.PerformUnifiedRestoreTask; -import java.io.File; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; /** * Asynchronous backup/restore handler thread. @@ -81,7 +81,7 @@ public class BackupHandler extends Handler { public static final int MSG_BACKUP_RESTORE_STEP = 20; public static final int MSG_OP_COMPLETE = 21; - private RefactoredBackupManagerService backupManagerService; + private final RefactoredBackupManagerService backupManagerService; public BackupHandler( RefactoredBackupManagerService backupManagerService, Looper looper) { @@ -91,13 +91,23 @@ public class BackupHandler extends Handler { public void handleMessage(Message msg) { + TransportManager transportManager = backupManagerService.getTransportManager(); switch (msg.what) { case MSG_RUN_BACKUP: { backupManagerService.setLastBackupPass(System.currentTimeMillis()); + String callerLogString = "BH/MSG_RUN_BACKUP"; + TransportClient transportClient = + transportManager.getCurrentTransportClient(callerLogString); IBackupTransport transport = - backupManagerService.getTransportManager().getCurrentTransportBinder(); + transportClient != null + ? transportClient.connect(callerLogString) + : null; if (transport == null) { + if (transportClient != null) { + transportManager + .disposeOfTransportClient(transportClient, callerLogString); + } Slog.v(TAG, "Backup requested but no transport available"); synchronized (backupManagerService.getQueueLock()) { backupManagerService.setBackupRunning(false); @@ -138,9 +148,13 @@ public class BackupHandler extends Handler { // Spin up a backup state sequence and set it running try { String dirName = transport.transportDirName(); + OnTaskFinishedListener listener = + caller -> + transportManager + .disposeOfTransportClient(transportClient, caller); PerformBackupTask pbt = new PerformBackupTask( - backupManagerService, transport, dirName, queue, - oldJournal, null, null, Collections.<String>emptyList(), false, + backupManagerService, transportClient, dirName, queue, + oldJournal, null, null, listener, Collections.emptyList(), false, false /* nonIncremental */); Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt); sendMessage(pbtMessage); @@ -157,6 +171,7 @@ public class BackupHandler extends Handler { } if (!staged) { + transportManager.disposeOfTransportClient(transportClient, callerLogString); // if we didn't actually hand off the wakelock, rewind until next time synchronized (backupManagerService.getQueueLock()) { backupManagerService.setBackupRunning(false); @@ -382,9 +397,9 @@ public class BackupHandler extends Handler { PerformBackupTask pbt = new PerformBackupTask( backupManagerService, - params.transport, params.dirName, - kvQueue, null, params.observer, params.monitor, params.fullPackages, true, - params.nonIncrementalBackup); + params.transportClient, params.dirName, + kvQueue, null, params.observer, params.monitor, params.listener, + params.fullPackages, true, params.nonIncrementalBackup); Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt); sendMessage(pbtMessage); break; diff --git a/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java b/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java new file mode 100644 index 000000000000..e417f06c8a05 --- /dev/null +++ b/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.internal; + +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnectionListener; + +/** Listener to be called when a task finishes, successfully or not. */ +public interface OnTaskFinishedListener { + OnTaskFinishedListener NOP = caller -> {}; + + /** + * Called when a task finishes, successfully or not. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + */ + void onFinished(String caller); +} diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java index c0caa557b4ae..1fa215a1664a 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java @@ -63,6 +63,8 @@ import com.android.server.backup.KeyValueBackupJob; import com.android.server.backup.PackageManagerBackupAgent; import com.android.server.backup.RefactoredBackupManagerService; import com.android.server.backup.fullbackup.PerformFullTransportBackupTask; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportUtils; import com.android.server.backup.utils.AppBackupUtils; import com.android.server.backup.utils.BackupManagerMonitorUtils; import com.android.server.backup.utils.BackupObserverUtils; @@ -112,7 +114,6 @@ public class PerformBackupTask implements BackupRestoreTask { private RefactoredBackupManagerService backupManagerService; private final Object mCancelLock = new Object(); - IBackupTransport mTransport; ArrayList<BackupRequest> mQueue; ArrayList<BackupRequest> mOriginalQueue; File mStateDir; @@ -122,6 +123,8 @@ public class PerformBackupTask implements BackupRestoreTask { IBackupObserver mObserver; IBackupManagerMonitor mMonitor; + private final TransportClient mTransportClient; + private final OnTaskFinishedListener mListener; private final PerformFullTransportBackupTask mFullBackupTask; private final int mCurrentOpToken; private volatile int mEphemeralOpToken; @@ -143,17 +146,19 @@ public class PerformBackupTask implements BackupRestoreTask { private volatile boolean mCancelAll; public PerformBackupTask(RefactoredBackupManagerService backupManagerService, - IBackupTransport transport, String dirName, + TransportClient transportClient, String dirName, ArrayList<BackupRequest> queue, @Nullable DataChangedJournal journal, IBackupObserver observer, IBackupManagerMonitor monitor, - List<String> pendingFullBackups, boolean userInitiated, boolean nonIncremental) { + @Nullable OnTaskFinishedListener listener, List<String> pendingFullBackups, + boolean userInitiated, boolean nonIncremental) { this.backupManagerService = backupManagerService; - mTransport = transport; + mTransportClient = transportClient; mOriginalQueue = queue; mQueue = new ArrayList<>(); mJournal = journal; mObserver = observer; mMonitor = monitor; + mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP; mPendingFullBackups = pendingFullBackups; mUserInitiated = userInitiated; mNonIncremental = nonIncremental; @@ -289,10 +294,10 @@ public class PerformBackupTask implements BackupRestoreTask { if (DEBUG) { Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets"); } - File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL); try { - final String transportName = mTransport.transportDirName(); + IBackupTransport transport = mTransportClient.connectOrThrow("PBT.beginBackup()"); + final String transportName = transport.transportDirName(); EventLog.writeEvent(EventLogTags.BACKUP_START, transportName); // If we haven't stored package manager metadata yet, we must init the transport. @@ -300,7 +305,7 @@ public class PerformBackupTask implements BackupRestoreTask { Slog.i(TAG, "Initializing (wiping) backup state and transport storage"); backupManagerService.addBackupTrace("initializing transport " + transportName); backupManagerService.resetBackupState(mStateDir); // Just to make sure. - mStatus = mTransport.initializeDevice(); + mStatus = transport.initializeDevice(); backupManagerService.addBackupTrace("transport.initializeDevice() == " + mStatus); if (mStatus == BackupTransport.TRANSPORT_OK) { @@ -324,7 +329,7 @@ public class PerformBackupTask implements BackupRestoreTask { PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent(); mStatus = invokeAgentForBackup( PACKAGE_MANAGER_SENTINEL, - IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport); + IBackupAgent.Stub.asInterface(pmAgent.onBind())); backupManagerService.addBackupTrace("PMBA invoke: " + mStatus); // Because the PMBA is a local instance, it has already executed its @@ -445,7 +450,7 @@ public class PerformBackupTask implements BackupRestoreTask { backupManagerService.addBackupTrace("agent bound; a? = " + (agent != null)); if (agent != null) { mAgentBinder = agent; - mStatus = invokeAgentForBackup(request.packageName, agent, mTransport); + mStatus = invokeAgentForBackup(request.packageName, agent); // at this point we'll either get a completion callback from the // agent, or a timeout message on the main handler. either way, we're // done here as long as we're successful so far. @@ -526,11 +531,14 @@ public class PerformBackupTask implements BackupRestoreTask { // If everything actually went through and this is the first time we've // done a backup, we can now record what the current backup dataset token // is. + String callerLogString = "PBT.finalizeBackup()"; if ((backupManagerService.getCurrentToken() == 0) && (mStatus == BackupTransport.TRANSPORT_OK)) { backupManagerService.addBackupTrace("success; recording token"); try { - backupManagerService.setCurrentToken(mTransport.getCurrentRestoreSet()); + IBackupTransport transport = + mTransportClient.connectOrThrow(callerLogString); + backupManagerService.setCurrentToken(transport.getCurrentRestoreSet()); backupManagerService.writeRestoreTokens(); } catch (Exception e) { // nothing for it at this point, unfortunately, but this will be @@ -553,13 +561,13 @@ public class PerformBackupTask implements BackupRestoreTask { backupManagerService.addBackupTrace("init required; rerunning"); try { final String name = backupManagerService.getTransportManager().getTransportName( - mTransport); + mTransportClient); if (name != null) { backupManagerService.getPendingInits().add(name); } else { if (DEBUG) { - Slog.w(TAG, "Couldn't find name of transport " + mTransport - + " for init"); + Slog.w(TAG, "Couldn't find name of transport " + + mTransportClient.getTransportComponent() + " for init"); } } } catch (Exception e) { @@ -577,17 +585,21 @@ public class PerformBackupTask implements BackupRestoreTask { if (!mCancelAll && mStatus == BackupTransport.TRANSPORT_OK && mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) { + // TODO(brufino): Move the onFinish() call to the full-backup task + mListener.onFinished(callerLogString); Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups); // Acquiring wakelock for PerformFullTransportBackupTask before its start. backupManagerService.getWakelock().acquire(); (new Thread(mFullBackupTask, "full-transport-requested")).start(); } else if (mCancelAll) { + mListener.onFinished(callerLogString); if (mFullBackupTask != null) { mFullBackupTask.unregisterTask(); } BackupObserverUtils.sendBackupFinished(mObserver, BackupManager.ERROR_BACKUP_CANCELLED); } else { + mListener.onFinished(callerLogString); mFullBackupTask.unregisterTask(); switch (mStatus) { case BackupTransport.TRANSPORT_OK: @@ -619,8 +631,7 @@ public class PerformBackupTask implements BackupRestoreTask { // Invoke an agent's doBackup() and start a timeout message spinning on the main // handler in case it doesn't get back to us. - int invokeAgentForBackup(String packageName, IBackupAgent agent, - IBackupTransport transport) { + int invokeAgentForBackup(String packageName, IBackupAgent agent) { if (DEBUG) { Slog.d(TAG, "invokeAgentForBackup on " + packageName); } @@ -671,7 +682,10 @@ public class PerformBackupTask implements BackupRestoreTask { ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE); - final long quota = mTransport.getBackupQuota(packageName, false /* isFullBackup */); + IBackupTransport transport = + mTransportClient.connectOrThrow("PBT.invokeAgentForBackup()"); + + final long quota = transport.getBackupQuota(packageName, false /* isFullBackup */); callingAgent = true; // Initiate the target's backup pass @@ -888,10 +902,12 @@ public class PerformBackupTask implements BackupRestoreTask { clearAgentState(); backupManagerService.addBackupTrace("operation complete"); + IBackupTransport transport = mTransportClient.connect("PBT.operationComplete()"); ParcelFileDescriptor backupData = null; mStatus = BackupTransport.TRANSPORT_OK; long size = 0; try { + TransportUtils.checkTransport(transport); size = mBackupDataName.length(); if (size > 0) { if (mStatus == BackupTransport.TRANSPORT_OK) { @@ -899,7 +915,7 @@ public class PerformBackupTask implements BackupRestoreTask { ParcelFileDescriptor.MODE_READ_ONLY); backupManagerService.addBackupTrace("sending data to transport"); int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0; - mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags); + mStatus = transport.performBackup(mCurrentPackage, backupData, flags); } // TODO - We call finishBackup() for each application backed up, because @@ -910,7 +926,7 @@ public class PerformBackupTask implements BackupRestoreTask { backupManagerService.addBackupTrace("data delivered: " + mStatus); if (mStatus == BackupTransport.TRANSPORT_OK) { backupManagerService.addBackupTrace("finishing op on transport"); - mStatus = mTransport.finishBackup(); + mStatus = transport.finishBackup(); backupManagerService.addBackupTrace("finished: " + mStatus); } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) { backupManagerService.addBackupTrace("transport rejected package"); @@ -981,8 +997,8 @@ public class PerformBackupTask implements BackupRestoreTask { } if (mAgentBinder != null) { try { - long quota = mTransport.getBackupQuota(mCurrentPackage.packageName, - false); + TransportUtils.checkTransport(transport); + long quota = transport.getBackupQuota(mCurrentPackage.packageName, false); mAgentBinder.doQuotaExceeded(size, quota); } catch (Exception e) { Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage()); @@ -1052,7 +1068,9 @@ public class PerformBackupTask implements BackupRestoreTask { // by way of retry/backoff time. long delay; try { - delay = mTransport.requestBackupTime(); + IBackupTransport transport = + mTransportClient.connectOrThrow("PBT.revertAndEndBackup()"); + delay = transport.requestBackupTime(); } catch (Exception e) { Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e.getMessage()); delay = 0; // use the scheduler's default diff --git a/services/backup/java/com/android/server/backup/params/BackupParams.java b/services/backup/java/com/android/server/backup/params/BackupParams.java index 4fd7ddbc28f7..2ba8ec16a45c 100644 --- a/services/backup/java/com/android/server/backup/params/BackupParams.java +++ b/services/backup/java/com/android/server/backup/params/BackupParams.java @@ -19,30 +19,34 @@ package com.android.server.backup.params; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; -import com.android.internal.backup.IBackupTransport; +import com.android.server.backup.internal.OnTaskFinishedListener; +import com.android.server.backup.transport.TransportClient; import java.util.ArrayList; public class BackupParams { - public IBackupTransport transport; + public TransportClient transportClient; public String dirName; public ArrayList<String> kvPackages; public ArrayList<String> fullPackages; public IBackupObserver observer; public IBackupManagerMonitor monitor; + public OnTaskFinishedListener listener; public boolean userInitiated; public boolean nonIncrementalBackup; - public BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages, - ArrayList<String> fullPackages, IBackupObserver observer, - IBackupManagerMonitor monitor, boolean userInitiated, boolean nonIncrementalBackup) { - this.transport = transport; + public BackupParams(TransportClient transportClient, String dirName, + ArrayList<String> kvPackages, ArrayList<String> fullPackages, IBackupObserver observer, + IBackupManagerMonitor monitor, OnTaskFinishedListener listener, boolean userInitiated, + boolean nonIncrementalBackup) { + this.transportClient = transportClient; this.dirName = dirName; this.kvPackages = kvPackages; this.fullPackages = fullPackages; this.observer = observer; this.monitor = monitor; + this.listener = listener; this.userInitiated = userInitiated; this.nonIncrementalBackup = nonIncrementalBackup; } diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java new file mode 100644 index 000000000000..9c39729f0dda --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.DeadObjectException; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.backup.IBackupTransport; +import com.android.internal.util.Preconditions; +import com.android.server.backup.TransportManager; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +/** + * A {@link TransportClient} manages the connection to an {@link IBackupTransport} service, obtained + * via the {@param bindIntent} parameter provided in the constructor. A {@link TransportClient} is + * responsible for only one connection to the transport service, not more. + * + * <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can + * call either {@link #connect(String)}, if you can block your thread, or {@link + * #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link + * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport. + * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly + * via {@link TransportManager#disposeOfTransportClient(TransportClient, String)}. + * + * <p>DO NOT forget to unbind otherwise there will be dangling connections floating around. + * + * <p>This class is thread-safe. + * + * @see TransportManager + */ +public class TransportClient { + private static final String TAG = "TransportClient"; + + private final Context mContext; + private final Intent mBindIntent; + private final String mIdentifier; + private final ComponentName mTransportComponent; + private final Handler mListenerHandler; + private final String mPrefixForLog; + private final Object mStateLock = new Object(); + + @GuardedBy("mStateLock") + private final Map<TransportConnectionListener, String> mListeners = new ArrayMap<>(); + + @GuardedBy("mStateLock") + @State + private int mState = State.IDLE; + + @GuardedBy("mStateLock") + private volatile IBackupTransport mTransport; + + TransportClient( + Context context, + Intent bindIntent, + ComponentName transportComponent, + String identifier) { + this(context, bindIntent, transportComponent, identifier, Handler.getMain()); + } + + @VisibleForTesting + TransportClient( + Context context, + Intent bindIntent, + ComponentName transportComponent, + String identifier, + Handler listenerHandler) { + mContext = context; + mTransportComponent = transportComponent; + mBindIntent = bindIntent; + mIdentifier = identifier; + mListenerHandler = listenerHandler; + + // For logging + String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", ""); + mPrefixForLog = classNameForLog + "#" + mIdentifier + ": "; + } + + public ComponentName getTransportComponent() { + return mTransportComponent; + } + + // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one + // of these calls, if a binding happen again the new service can be a different instance. Since + // transports are stateful, we don't want a new instance responding for an old instance's state. + private ServiceConnection mConnection = + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder binder) { + IBackupTransport transport = IBackupTransport.Stub.asInterface(binder); + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + if (mState != State.UNUSABLE) { + log(Log.DEBUG, "Transport connected"); + setStateLocked(State.CONNECTED, transport); + notifyListenersAndClearLocked(transport); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + synchronized (mStateLock) { + log(Log.ERROR, "Service disconnected: client UNUSABLE"); + setStateLocked(State.UNUSABLE, null); + // After unbindService() no calls back to mConnection + mContext.unbindService(this); + } + } + + @Override + public void onBindingDied(ComponentName name) { + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + log(Log.ERROR, "Binding died: client UNUSABLE"); + // After unbindService() no calls back to mConnection + switch (mState) { + case State.UNUSABLE: + break; + case State.IDLE: + log(Log.ERROR, "Unexpected state transition IDLE => UNUSABLE"); + setStateLocked(State.UNUSABLE, null); + break; + case State.BOUND_AND_CONNECTING: + setStateLocked(State.UNUSABLE, null); + mContext.unbindService(this); + notifyListenersAndClearLocked(null); + break; + case State.CONNECTED: + setStateLocked(State.UNUSABLE, null); + mContext.unbindService(this); + break; + } + } + } + }; + + /** + * Attempts to connect to the transport (if needed). + * + * <p>Note that being bound is not the same as connected. To be connected you also need to be + * bound. You go from nothing to bound, then to bound and connected. To have a usable transport + * binder instance you need to be connected. This method will attempt to connect and return an + * usable transport binder regardless of the state of the object, it may already be connected, + * or bound but not connected, not bound at all or even unusable. + * + * <p>So, a {@link Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)} (or + * one of its variants) can be called or not depending on the inner state. However, it won't be + * called again if we're already bound. For example, if one was already requested but the + * framework has not yet returned (meaning we're bound but still trying to connect) it won't + * trigger another one, just piggyback on the original request. + * + * <p>It's guaranteed that you are going to get a call back to {@param listener} after this + * call. However, the {@param IBackupTransport} parameter, the transport binder, is not + * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can + * throw {@link DeadObjectException}s on method calls. You should check for both in your code. + * The reasons for a null transport binder are: + * + * <ul> + * <li>Some code called {@link #unbind(String)} before you got a callback. + * <li>The framework had already called {@link + * ServiceConnection#onServiceDisconnected(ComponentName)} or {@link + * ServiceConnection#onBindingDied(ComponentName)} on this object's connection before. + * Check the documentation of those methods for when that happens. + * <li>The framework returns false for {@link Context#bindServiceAsUser(Intent, + * ServiceConnection, int, UserHandle)} (or one of its variants). Check documentation for + * when this happens. + * </ul> + * + * For unusable transport binders check {@link DeadObjectException}. + * + * @param listener The listener that will be called with the (possibly null or unusable) {@link + * IBackupTransport} instance and this {@link TransportClient} object. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. This + * should be a human-readable short string that is easily identifiable in the logs. Ideally + * TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very + * descriptive like MyHandler.handleMessage() you should put something that someone reading + * the code would understand, like MyHandler/MSG_FOO. + * @see #connect(String) + * @see DeadObjectException + * @see ServiceConnection#onServiceConnected(ComponentName, IBinder) + * @see ServiceConnection#onServiceDisconnected(ComponentName) + * @see Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle) + */ + public void connectAsync(TransportConnectionListener listener, String caller) { + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + switch (mState) { + case State.UNUSABLE: + log(Log.DEBUG, caller, "Async connect: UNUSABLE client"); + notifyListener(listener, null, caller); + break; + case State.IDLE: + boolean hasBound = + mContext.bindServiceAsUser( + mBindIntent, + mConnection, + Context.BIND_AUTO_CREATE, + TransportManager.createSystemUserHandle()); + if (hasBound) { + // We don't need to set a time-out because we are guaranteed to get a call + // back in ServiceConnection, either an onServiceConnected() or + // onBindingDied(). + log(Log.DEBUG, caller, "Async connect: service bound, connecting"); + setStateLocked(State.BOUND_AND_CONNECTING, null); + mListeners.put(listener, caller); + } else { + log(Log.ERROR, "Async connect: bindService returned false"); + // mState remains State.IDLE + mContext.unbindService(mConnection); + notifyListener(listener, null, caller); + } + break; + case State.BOUND_AND_CONNECTING: + log(Log.DEBUG, caller, "Async connect: already connecting, adding listener"); + mListeners.put(listener, caller); + break; + case State.CONNECTED: + log(Log.DEBUG, caller, "Async connect: reusing transport"); + notifyListener(listener, mTransport, caller); + break; + } + } + } + + /** + * Removes the transport binding. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link #connectAsync(TransportConnectionListener, String)} for more details. + */ + public void unbind(String caller) { + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + log(Log.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")"); + switch (mState) { + case State.UNUSABLE: + case State.IDLE: + break; + case State.BOUND_AND_CONNECTING: + setStateLocked(State.IDLE, null); + // After unbindService() no calls back to mConnection + mContext.unbindService(mConnection); + notifyListenersAndClearLocked(null); + break; + case State.CONNECTED: + setStateLocked(State.IDLE, null); + mContext.unbindService(mConnection); + break; + } + } + } + + /** + * Attempts to connect to the transport (if needed) and returns it. + * + * <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The + * same observations about state are valid here. Also, what was said about the {@link + * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return + * value of this method. + * + * <p>This is a potentially blocking operation, so be sure to call this carefully on the correct + * threads. You can't call this from the process main-thread (it throws an exception if you do + * so). + * + * <p>In most cases only the first call to this method will block, the following calls should + * return instantly. However, this is not guaranteed. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link #connectAsync(TransportConnectionListener, String)} for more details. + * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can + * still be unusable - throws {@link DeadObjectException} on method calls + */ + @WorkerThread + @Nullable + public IBackupTransport connect(String caller) { + // If called on the main-thread this could deadlock waiting because calls to + // ServiceConnection are on the main-thread as well + Preconditions.checkState( + !Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread"); + + IBackupTransport transport = mTransport; + if (transport != null) { + log(Log.DEBUG, caller, "Sync connect: reusing transport"); + return transport; + } + + // If it's already UNUSABLE we return straight away, no need to go to main-thread + synchronized (mStateLock) { + if (mState == State.UNUSABLE) { + log(Log.DEBUG, caller, "Sync connect: UNUSABLE client"); + return null; + } + } + + CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>(); + TransportConnectionListener requestListener = + (requestedTransport, transportClient) -> + transportFuture.complete(requestedTransport); + + log(Log.DEBUG, caller, "Sync connect: calling async"); + connectAsync(requestListener, caller); + + try { + return transportFuture.get(); + } catch (InterruptedException | ExecutionException e) { + String error = e.getClass().getSimpleName(); + log(Log.ERROR, caller, error + " while waiting for transport: " + e.getMessage()); + return null; + } + } + + /** + * Tries to connect to the transport, if it fails throws {@link TransportNotAvailableException}. + * + * <p>Same as {@link #connect(String)} except it throws instead of returning null. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link #connectAsync(TransportConnectionListener, String)} for more details. + * @return A {@link IBackupTransport} transport binder instance. + * @see #connect(String) + * @throws TransportNotAvailableException if connection attempt fails. + */ + @WorkerThread + public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException { + IBackupTransport transport = connect(caller); + if (transport == null) { + log(Log.ERROR, caller, "Transport connection failed"); + throw new TransportNotAvailableException(); + } + return transport; + } + + @Override + public String toString() { + return "TransportClient{" + + mTransportComponent.flattenToShortString() + + "#" + + mIdentifier + + "}"; + } + + private void notifyListener( + TransportConnectionListener listener, IBackupTransport transport, String caller) { + log(Log.VERBOSE, caller, "Notifying listener of transport = " + transport); + mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this)); + } + + @GuardedBy("mStateLock") + private void notifyListenersAndClearLocked(IBackupTransport transport) { + for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) { + TransportConnectionListener listener = entry.getKey(); + String caller = entry.getValue(); + notifyListener(listener, transport, caller); + } + mListeners.clear(); + } + + @GuardedBy("mStateLock") + private void setStateLocked(@State int state, @Nullable IBackupTransport transport) { + log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state)); + mState = state; + mTransport = transport; + } + + @GuardedBy("mStateLock") + private void checkStateIntegrityLocked() { + switch (mState) { + case State.UNUSABLE: + checkState(mListeners.isEmpty(), "Unexpected listeners when state = UNUSABLE"); + checkState( + mTransport == null, "Transport expected to be null when state = UNUSABLE"); + case State.IDLE: + checkState(mListeners.isEmpty(), "Unexpected listeners when state = IDLE"); + checkState(mTransport == null, "Transport expected to be null when state = IDLE"); + break; + case State.BOUND_AND_CONNECTING: + checkState( + mTransport == null, + "Transport expected to be null when state = BOUND_AND_CONNECTING"); + break; + case State.CONNECTED: + checkState(mListeners.isEmpty(), "Unexpected listeners when state = CONNECTED"); + checkState( + mTransport != null, + "Transport expected to be non-null when state = CONNECTED"); + break; + default: + checkState(false, "Unexpected state = " + stateToString(mState)); + } + } + + private void checkState(boolean assertion, String message) { + if (!assertion) { + log(Log.ERROR, message); + } + } + + private String stateToString(@State int state) { + switch (state) { + case State.UNUSABLE: + return "UNUSABLE"; + case State.IDLE: + return "IDLE"; + case State.BOUND_AND_CONNECTING: + return "BOUND_AND_CONNECTING"; + case State.CONNECTED: + return "CONNECTED"; + default: + return "<UNKNOWN = " + state + ">"; + } + } + + private void log(int priority, String message) { + TransportUtils.log(priority, TAG, message); + } + + private void log(int priority, String caller, String msg) { + TransportUtils.log(priority, TAG, mPrefixForLog, caller, msg); + // TODO(brufino): Log in internal list for dump + // CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis()); + } + + @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED}) + @Retention(RetentionPolicy.SOURCE) + private @interface State { + int UNUSABLE = 0; + int IDLE = 1; + int BOUND_AND_CONNECTING = 2; + int CONNECTED = 3; + } +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java new file mode 100644 index 000000000000..1cbe74716b03 --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import com.android.server.backup.TransportManager; + +/** + * Manages the creation and disposal of {@link TransportClient}s. The only class that should use + * this is {@link TransportManager}, all the other usages should go to {@link TransportManager}. + * + * <p>TODO(brufino): Implement pool of TransportClients + */ +public class TransportClientManager { + private static final String TAG = "TransportClientManager"; + + private final Context mContext; + private final Object mTransportClientsLock = new Object(); + private int mTransportClientsCreated = 0; + + public TransportClientManager(Context context) { + mContext = context; + } + + /** + * Retrieves a {@link TransportClient} for the transport identified by {@param + * transportComponent}. + * + * @param transportComponent The {@link ComponentName} of the transport. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient}. + */ + public TransportClient getTransportClient(ComponentName transportComponent, String caller) { + Intent bindIntent = + new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent); + synchronized (mTransportClientsLock) { + TransportClient transportClient = + new TransportClient( + mContext, + bindIntent, + transportComponent, + Integer.toString(mTransportClientsCreated)); + mTransportClientsCreated++; + TransportUtils.log(Log.DEBUG, TAG, caller, "Retrieving " + transportClient); + return transportClient; + } + } + + /** + * Disposes of the {@link TransportClient}. + * + * @param transportClient The {@link TransportClient} to be disposed of. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + */ + public void disposeOfTransportClient(TransportClient transportClient, String caller) { + TransportUtils.log(Log.DEBUG, TAG, caller, "Disposing of " + transportClient); + transportClient.unbind(caller); + } +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java new file mode 100644 index 000000000000..1ccffd01d12c --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import android.annotation.Nullable; + +import com.android.internal.backup.IBackupTransport; + +/** + * Listener to be called by {@link TransportClient#connectAsync(TransportConnectionListener, + * String)}. + */ +public interface TransportConnectionListener { + /** + * Called when {@link TransportClient} has a transport binder available or that it decided it + * couldn't obtain one, in which case {@param transport} is null. + * + * @param transport A {@link IBackupTransport} transport binder or null. + * @param transportClient The {@link TransportClient} used to retrieve this transport binder. + */ + void onTransportConnectionResult( + @Nullable IBackupTransport transport, TransportClient transportClient); +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java b/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java new file mode 100644 index 000000000000..a02f03c2d1cb --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import com.android.internal.backup.IBackupTransport; + +/** + * Exception thrown when the {@link IBackupTransport} is not available. This happen when a {@link + * TransportClient} connection attempt fails. Check {@link + * TransportClient#connectAsync(TransportConnectionListener, String)} for when that happens. + * + * @see TransportClient#connectAsync(TransportConnectionListener, String) + */ +class TransportNotAvailableException extends Exception { + TransportNotAvailableException() { + super("Transport not available"); + } +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportUtils.java b/services/backup/java/com/android/server/backup/transport/TransportUtils.java new file mode 100644 index 000000000000..514717f46b9f --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/TransportUtils.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import android.annotation.Nullable; +import android.os.DeadObjectException; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.backup.IBackupTransport; + +/** Utility methods for transport-related operations. */ +public class TransportUtils { + + /** + * Throws {@link TransportNotAvailableException} if {@param transport} is null. The semantics is + * similar to a {@link DeadObjectException} coming from a dead transport binder. + */ + public static IBackupTransport checkTransport(@Nullable IBackupTransport transport) + throws TransportNotAvailableException { + if (transport == null) { + throw new TransportNotAvailableException(); + } + return transport; + } + + static void log(int priority, String tag, String message) { + log(priority, tag, null, message); + } + + static void log(int priority, String tag, @Nullable String caller, String message) { + log(priority, tag, "", caller, message); + } + + static void log( + int priority, String tag, String prefix, @Nullable String caller, String message) { + if (Log.isLoggable(tag, priority)) { + if (caller != null) { + prefix += "[" + caller + "] "; + } + Slog.println(priority, tag, prefix + message); + } + } + + private TransportUtils() {} +} diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 0fd59eaa111f..bdfccd6e9d93 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -118,7 +118,7 @@ public class LocationManagerService extends ILocationManager.Stub { private static final String TAG = "LocationManagerService"; public static final boolean D = Log.isLoggable(TAG, Log.DEBUG); - private static final String WAKELOCK_KEY = TAG; + private static final String WAKELOCK_KEY = "*location*"; // Location resolution level: no location data whatsoever private static final int RESOLUTION_LEVEL_NONE = 0; diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index e5fbdd21438c..296fb3210118 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -1,3 +1,4 @@ +# Connectivity / Networking per-file ConnectivityService.java=ek@google.com per-file ConnectivityService.java=hugobenichi@google.com per-file ConnectivityService.java=lorenzo@google.com @@ -7,3 +8,9 @@ per-file NetworkManagementService.java=lorenzo@google.com per-file NsdService.java=ek@google.com per-file NsdService.java=hugobenichi@google.com per-file NsdService.java=lorenzo@google.com + +# Vibrator +per-file VibratorService.java=michaelwr@google.com + +# Threads +per-file DisplayThread.java=michaelwr@google.com diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 0a6f976ca306..ae91b8208b72 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -351,7 +351,6 @@ import android.util.AtomicFile; import android.util.StatsLog; import android.util.TimingsTraceLog; import android.util.DebugUtils; -import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.Pair; @@ -716,6 +715,8 @@ public class ActivityManagerService extends IActivityManager.Stub final AppErrors mAppErrors; + final AppWarnings mAppWarnings; + /** * Dump of the activity state at the time of the last ANR. Cleared after * {@link WindowManagerService#LAST_ANR_LIFETIME_DURATION_MSECS} @@ -1726,7 +1727,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final int IDLE_UIDS_MSG = 58; static final int LOG_STACK_STATE = 60; static final int VR_MODE_CHANGE_MSG = 61; - static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 62; static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63; static final int NOTIFY_VR_SLEEPING_MSG = 65; static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66; @@ -1745,7 +1745,6 @@ public class ActivityManagerService extends IActivityManager.Stub static KillHandler sKillHandler = null; CompatModeDialog mCompatModeDialog; - UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog; long mLastMemUsageReportTime = 0; /** @@ -1927,23 +1926,6 @@ public class ActivityManagerService extends IActivityManager.Stub } break; } - case SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG: { - synchronized (ActivityManagerService.this) { - final ActivityRecord ar = (ActivityRecord) msg.obj; - if (mUnsupportedDisplaySizeDialog != null) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; - } - if (ar != null && mCompatModePackages.getPackageNotifyUnsupportedZoomLocked( - ar.packageName)) { - // TODO(multi-display): Show dialog on appropriate display. - mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog( - ActivityManagerService.this, mUiContext, ar.info.applicationInfo); - mUnsupportedDisplaySizeDialog.show(); - } - } - break; - } case DISMISS_DIALOG_UI_MSG: { final Dialog d = (Dialog) msg.obj; d.dismiss(); @@ -2676,6 +2658,7 @@ public class ActivityManagerService extends IActivityManager.Stub GL_ES_VERSION = 0; mActivityStarter = null; mAppErrors = null; + mAppWarnings = null; mAppOpsService = mInjector.getAppOpsService(null, null); mBatteryStatsService = null; mCompatModePackages = null; @@ -2743,10 +2726,13 @@ public class ActivityManagerService extends IActivityManager.Stub mProviderMap = new ProviderMap(this); mAppErrors = new AppErrors(mUiContext, this); - // TODO: Move creation of battery stats service outside of activity manager service. File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); systemDir.mkdirs(); + + mAppWarnings = new AppWarnings(this, mUiContext, mHandler, mUiHandler, systemDir); + + // TODO: Move creation of battery stats service outside of activity manager service. mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler); mBatteryStatsService.getActiveStatistics().readLocked(); mBatteryStatsService.scheduleWriteToDisk(); @@ -3310,15 +3296,18 @@ public class ActivityManagerService extends IActivityManager.Stub mUiHandler.sendMessage(msg); } - final void showUnsupportedZoomDialogIfNeededLocked(ActivityRecord r) { - final Configuration globalConfig = getGlobalConfiguration(); - if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE - && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) { - final Message msg = Message.obtain(); - msg.what = SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG; - msg.obj = r; - mUiHandler.sendMessage(msg); - } + final AppWarnings getAppWarningsLocked() { + return mAppWarnings; + } + + /** + * Shows app warning dialogs, if necessary. + * + * @param r activity record for which the warnings may be displayed + */ + final void showAppWarningsIfNeededLocked(ActivityRecord r) { + mAppWarnings.showUnsupportedCompileSdkDialogIfNeeded(r); + mAppWarnings.showUnsupportedDisplaySizeDialogIfNeeded(r); } private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index, @@ -14578,6 +14567,18 @@ public class ActivityManagerService extends IActivityManager.Stub final String dropboxTag = processClass(process) + "_" + eventType; if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return; + // Log to StatsLog before the rate-limiting. + // The logging below is adapated from appendDropboxProcessHeaders. + StatsLog.write(StatsLog.DROPBOX_ERROR_CHANGED, + process != null ? process.uid : -1, + dropboxTag, + processName, + process != null ? process.pid : -1, + (process != null && process.info != null) ? + (process.info.isInstantApp() ? 1 : 0) : -1, + activity != null ? activity.shortComponentName : null, + process != null ? (process.isInterestingToUserLocked() ? 1 : 0) : -1); + // Rate-limit how often we're willing to do the heavy lifting below to // collect and record logs; currently 5 logs per 10 second period. final long now = SystemClock.elapsedRealtime(); @@ -19362,13 +19363,7 @@ public class ActivityManagerService extends IActivityManager.Stub mRecentTasks.removeTasksByPackageName(ssp, userId); mServices.forceStopPackageLocked(ssp, userId); - - // Hide the "unsupported display" dialog if necessary. - if (mUnsupportedDisplaySizeDialog != null && ssp.equals( - mUnsupportedDisplaySizeDialog.getPackageName())) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; - } + mAppWarnings.onPackageUninstalled(ssp); mCompatModePackages.handlePackageUninstalledLocked(ssp); mBatteryStatsService.notePackageUninstalled(ssp); } @@ -19447,13 +19442,8 @@ public class ActivityManagerService extends IActivityManager.Stub Uri data = intent.getData(); String ssp; if (data != null && (ssp = data.getSchemeSpecificPart()) != null) { - // Hide the "unsupported display" dialog if necessary. - if (mUnsupportedDisplaySizeDialog != null && ssp.equals( - mUnsupportedDisplaySizeDialog.getPackageName())) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; - } mCompatModePackages.handlePackageDataClearedLocked(ssp); + mAppWarnings.onPackageDataCleared(ssp); } break; } @@ -20643,8 +20633,7 @@ public class ActivityManagerService extends IActivityManager.Stub final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0; if (isDensityChange && displayId == DEFAULT_DISPLAY) { - // Reset the unsupported display size dialog. - mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG); + mAppWarnings.onDensityChanged(); killAllBackgroundProcessesExcept(N, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 2e764713aec2..5927666141d4 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 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } results = null; newIntents = null; - service.showUnsupportedZoomDialogIfNeededLocked(this); + service.getAppWarningsLocked().onResumeActivity(this); service.showAskCompatModeDialogLocked(this); } else { service.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index c086c5292059..6985d6e36ac1 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -2602,7 +2602,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai next.shortComponentName); next.sleeping = false; - mService.showUnsupportedZoomDialogIfNeededLocked(next); + mService.getAppWarningsLocked().onResumeActivity(next); mService.showAskCompatModeDialogLocked(next); next.app.pendingUiClean = true; next.app.forceProcessStateUpTo(mService.mTopProcessState); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index b015bcfe7b23..ddde4bc4957c 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY); r.sleeping = false; r.forceNewConfig = false; - mService.showUnsupportedZoomDialogIfNeededLocked(r); + mService.getAppWarningsLocked().onStartActivity(r); mService.showAskCompatModeDialogLocked(r); r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo); ProfilerInfo profilerInfo = null; diff --git a/services/core/java/com/android/server/am/AppWarnings.java b/services/core/java/com/android/server/am/AppWarnings.java new file mode 100644 index 000000000000..a3c0345066de --- /dev/null +++ b/services/core/java/com/android/server/am/AppWarnings.java @@ -0,0 +1,498 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.annotation.UiThread; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.AtomicFile; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.util.FastXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * Manages warning dialogs shown during application lifecycle. + */ +class AppWarnings { + private static final String TAG = "AppWarnings"; + private static final String CONFIG_FILE_NAME = "packages-warnings.xml"; + + public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01; + public static final int FLAG_HIDE_COMPILE_SDK = 0x02; + + private final HashMap<String, Integer> mPackageFlags = new HashMap<>(); + + private final ActivityManagerService mAms; + private final Context mUiContext; + private final ConfigHandler mAmsHandler; + private final UiHandler mUiHandler; + private final AtomicFile mConfigFile; + + private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog; + private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog; + + /** + * Creates a new warning dialog manager. + * <p> + * <strong>Note:</strong> Must be called from the ActivityManagerService thread. + * + * @param ams + * @param uiContext + * @param amsHandler + * @param uiHandler + * @param systemDir + */ + public AppWarnings(ActivityManagerService ams, Context uiContext, Handler amsHandler, + Handler uiHandler, File systemDir) { + mAms = ams; + mUiContext = uiContext; + mAmsHandler = new ConfigHandler(amsHandler.getLooper()); + mUiHandler = new UiHandler(uiHandler.getLooper()); + mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME)); + + readConfigFromFileAmsThread(); + } + + /** + * Shows the "unsupported display size" warning, if necessary. + * + * @param r activity record for which the warning may be displayed + */ + public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) { + final Configuration globalConfig = mAms.getGlobalConfiguration(); + if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE + && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) { + mUiHandler.showUnsupportedDisplaySizeDialog(r); + } + } + + /** + * Shows the "unsupported compile SDK" warning, if necessary. + * + * @param r activity record for which the warning may be displayed + */ + public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) { + if (r.appInfo.compileSdkVersion == 0 || r.appInfo.compileSdkVersionCodename == null) { + // We don't know enough about this package. Abort! + return; + } + + // If the application was built against an pre-release SDK that's older than the current + // platform OR if the current platform is pre-release and older than the SDK against which + // the application was built OR both are pre-release with the same SDK_INT but different + // codenames (e.g. simultaneous pre-release development), then we're likely to run into + // compatibility issues. Warn the user and offer to check for an update. + final int compileSdk = r.appInfo.compileSdkVersion; + final int platformSdk = Build.VERSION.SDK_INT; + final boolean isCompileSdkPreview = !"REL".equals(r.appInfo.compileSdkVersionCodename); + final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME); + if ((isCompileSdkPreview && compileSdk < platformSdk) + || (isPlatformSdkPreview && platformSdk < compileSdk) + || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk + && !Build.VERSION.CODENAME.equals(r.appInfo.compileSdkVersionCodename))) { + mUiHandler.showUnsupportedCompileSdkDialog(r); + } + } + + /** + * Called when an activity is being started. + * + * @param r record for the activity being started + */ + public void onStartActivity(ActivityRecord r) { + showUnsupportedCompileSdkDialogIfNeeded(r); + showUnsupportedDisplaySizeDialogIfNeeded(r); + } + + /** + * Called when an activity was previously started and is being resumed. + * + * @param r record for the activity being resumed + */ + public void onResumeActivity(ActivityRecord r) { + showUnsupportedDisplaySizeDialogIfNeeded(r); + } + + /** + * Called by ActivityManagerService when package data has been cleared. + * + * @param name the package whose data has been cleared + */ + public void onPackageDataCleared(String name) { + removePackageAndHideDialogs(name); + } + + /** + * Called by ActivityManagerService when a package has been uninstalled. + * + * @param name the package that has been uninstalled + */ + public void onPackageUninstalled(String name) { + removePackageAndHideDialogs(name); + } + + /** + * Called by ActivityManagerService when the default display density has changed. + */ + public void onDensityChanged() { + mUiHandler.hideUnsupportedDisplaySizeDialog(); + } + + /** + * Does what it says on the tin. + */ + private void removePackageAndHideDialogs(String name) { + mUiHandler.hideDialogsForPackage(name); + + synchronized (mPackageFlags) { + mPackageFlags.remove(name); + mAmsHandler.scheduleWrite(); + } + } + + /** + * Hides the "unsupported display size" warning. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + */ + @UiThread + private void hideUnsupportedDisplaySizeDialogUiThread() { + if (mUnsupportedDisplaySizeDialog != null) { + mUnsupportedDisplaySizeDialog.dismiss(); + mUnsupportedDisplaySizeDialog = null; + } + } + + /** + * Shows the "unsupported display size" warning for the given application. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + * + * @param ar record for the activity that triggered the warning + */ + @UiThread + private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) { + if (mUnsupportedDisplaySizeDialog != null) { + mUnsupportedDisplaySizeDialog.dismiss(); + mUnsupportedDisplaySizeDialog = null; + } + if (ar != null && !hasPackageFlag( + ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) { + mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog( + AppWarnings.this, mUiContext, ar.info.applicationInfo); + mUnsupportedDisplaySizeDialog.show(); + } + } + + /** + * Shows the "unsupported compile SDK" warning for the given application. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + * + * @param ar record for the activity that triggered the warning + */ + @UiThread + private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) { + if (mUnsupportedCompileSdkDialog != null) { + mUnsupportedCompileSdkDialog.dismiss(); + mUnsupportedCompileSdkDialog = null; + } + if (ar != null && !hasPackageFlag( + ar.packageName, FLAG_HIDE_COMPILE_SDK)) { + mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog( + AppWarnings.this, mUiContext, ar.info.applicationInfo); + mUnsupportedCompileSdkDialog.show(); + } + } + + /** + * Dismisses all warnings for the given package. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + * + * @param name the package for which warnings should be dismissed, or {@code null} to dismiss + * all warnings + */ + @UiThread + private void hideDialogsForPackageUiThread(String name) { + // Hides the "unsupported display" dialog if necessary. + if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals( + mUnsupportedDisplaySizeDialog.getPackageName()))) { + mUnsupportedDisplaySizeDialog.dismiss(); + mUnsupportedDisplaySizeDialog = null; + } + + // Hides the "unsupported compile SDK" dialog if necessary. + if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals( + mUnsupportedCompileSdkDialog.getPackageName()))) { + mUnsupportedCompileSdkDialog.dismiss(); + mUnsupportedCompileSdkDialog = null; + } + } + + /** + * Returns the value of the flag for the given package. + * + * @param name the package from which to retrieve the flag + * @param flag the bitmask for the flag to retrieve + * @return {@code true} if the flag is enabled, {@code false} otherwise + */ + boolean hasPackageFlag(String name, int flag) { + return (getPackageFlags(name) & flag) == flag; + } + + /** + * Sets the flag for the given package to the specified value. + * + * @param name the package on which to set the flag + * @param flag the bitmask for flag to set + * @param enabled the value to set for the flag + */ + void setPackageFlag(String name, int flag, boolean enabled) { + synchronized (mPackageFlags) { + final int curFlags = getPackageFlags(name); + final int newFlags = enabled ? (curFlags & ~flag) : (curFlags | flag); + if (curFlags != newFlags) { + if (newFlags != 0) { + mPackageFlags.put(name, newFlags); + } else { + mPackageFlags.remove(name); + } + mAmsHandler.scheduleWrite(); + } + } + } + + /** + * Returns the bitmask of flags set for the specified package. + */ + private int getPackageFlags(String name) { + synchronized (mPackageFlags) { + return mPackageFlags.getOrDefault(name, 0); + } + } + + /** + * Handles messages on the system process UI thread. + */ + private final class UiHandler extends Handler { + private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1; + private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2; + private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3; + private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4; + + public UiHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: { + final ActivityRecord ar = (ActivityRecord) msg.obj; + showUnsupportedDisplaySizeDialogUiThread(ar); + } break; + case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: { + hideUnsupportedDisplaySizeDialogUiThread(); + } break; + case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: { + final ActivityRecord ar = (ActivityRecord) msg.obj; + showUnsupportedCompileSdkDialogUiThread(ar); + } break; + case MSG_HIDE_DIALOGS_FOR_PACKAGE: { + final String name = (String) msg.obj; + hideDialogsForPackageUiThread(name); + } break; + } + } + + public void showUnsupportedDisplaySizeDialog(ActivityRecord r) { + removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG); + obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget(); + } + + public void hideUnsupportedDisplaySizeDialog() { + removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG); + sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG); + } + + public void showUnsupportedCompileSdkDialog(ActivityRecord r) { + removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG); + obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget(); + } + + public void hideDialogsForPackage(String name) { + obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget(); + } + } + + /** + * Handles messages on the ActivityManagerService thread. + */ + private final class ConfigHandler extends Handler { + private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG; + + private static final int DELAY_MSG_WRITE = 10000; + + public ConfigHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_WRITE: + writeConfigToFileAmsThread(); + break; + } + } + + public void scheduleWrite() { + removeMessages(MSG_WRITE); + sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE); + } + } + + /** + * Writes the configuration file. + * <p> + * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you + * don't care where you're doing I/O operations. But you <i>do</i> care, don't you? + */ + private void writeConfigToFileAmsThread() { + // Create a shallow copy so that we don't have to synchronize on config. + final HashMap<String, Integer> packageFlags; + synchronized (mPackageFlags) { + packageFlags = new HashMap<>(mPackageFlags); + } + + FileOutputStream fos = null; + try { + fos = mConfigFile.startWrite(); + + final XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + out.startTag(null, "packages"); + + for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) { + String pkg = entry.getKey(); + int mode = entry.getValue(); + if (mode == 0) { + continue; + } + out.startTag(null, "package"); + out.attribute(null, "name", pkg); + out.attribute(null, "flags", Integer.toString(mode)); + out.endTag(null, "package"); + } + + out.endTag(null, "packages"); + out.endDocument(); + + mConfigFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Slog.w(TAG, "Error writing package metadata", e1); + if (fos != null) { + mConfigFile.failWrite(fos); + } + } + } + + /** + * Reads the configuration file and populates the package flags. + * <p> + * <strong>Note:</strong> Must be called from the constructor (and thus on the + * ActivityManagerService thread) since we don't synchronize on config. + */ + private void readConfigFromFileAmsThread() { + FileInputStream fis = null; + + try { + fis = mConfigFile.openRead(); + + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, StandardCharsets.UTF_8.name()); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG && + eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + } + if (eventType == XmlPullParser.END_DOCUMENT) { + return; + } + + String tagName = parser.getName(); + if ("packages".equals(tagName)) { + eventType = parser.next(); + do { + if (eventType == XmlPullParser.START_TAG) { + tagName = parser.getName(); + if (parser.getDepth() == 2) { + if ("package".equals(tagName)) { + final String name = parser.getAttributeValue(null, "name"); + if (name != null) { + final String flags = parser.getAttributeValue( + null, "flags"); + int flagsInt = 0; + if (flags != null) { + try { + flagsInt = Integer.parseInt(flags); + } catch (NumberFormatException e) { + } + } + mPackageFlags.put(name, flagsInt); + } + } + } + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + } + } catch (XmlPullParserException e) { + Slog.w(TAG, "Error reading package metadata", e); + } catch (java.io.IOException e) { + if (fis != null) Slog.w(TAG, "Error reading package metadata", e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (java.io.IOException e1) { + } + } + } + } +} diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java index bfc0456586c1..82019fde30b7 100644 --- a/services/core/java/com/android/server/am/CompatModePackages.java +++ b/services/core/java/com/android/server/am/CompatModePackages.java @@ -60,6 +60,8 @@ public final class CompatModePackages { public static final int COMPAT_FLAG_ENABLED = 1<<1; // Unsupported zoom state: don't warn the user about unsupported zoom mode. public static final int UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY = 1<<2; + // Unsupported compile SDK state: don't warn the user about unsupported compile SDK. + public static final int UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY = 1<<3; private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>(); @@ -237,6 +239,10 @@ public final class CompatModePackages { return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0; } + public boolean getPackageNotifyUnsupportedCompileSdkLocked(String packageName) { + return (getPackageFlags(packageName)&UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY) == 0; + } + public void setFrontActivityAskCompatModeLocked(boolean ask) { ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(); if (r != null) { @@ -245,22 +251,20 @@ public final class CompatModePackages { } public void setPackageAskCompatModeLocked(String packageName, boolean ask) { - int curFlags = getPackageFlags(packageName); - int newFlags = ask ? (curFlags&~COMPAT_FLAG_DONT_ASK) : (curFlags|COMPAT_FLAG_DONT_ASK); - if (curFlags != newFlags) { - if (newFlags != 0) { - mPackages.put(packageName, newFlags); - } else { - mPackages.remove(packageName); - } - scheduleWrite(); - } + setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask); } public void setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify) { + setPackageFlagLocked(packageName, UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY, notify); + } + + public void setPackageNotifyUnsupportedCompileSdkLocked(String packageName, boolean notify) { + setPackageFlagLocked(packageName, UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY, notify); + } + + private void setPackageFlagLocked(String packageName, int flag, boolean set) { final int curFlags = getPackageFlags(packageName); - final int newFlags = notify ? (curFlags&~UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) : - (curFlags|UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY); + final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag); if (curFlags != newFlags) { if (newFlags != 0) { mPackages.put(packageName, newFlags); diff --git a/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java new file mode 100644 index 000000000000..600589a40bb8 --- /dev/null +++ b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.view.Window; +import android.view.WindowManager; +import android.widget.CheckBox; + +import com.android.internal.R; +import com.android.server.utils.AppInstallerUtil; + +public class UnsupportedCompileSdkDialog { + private final AlertDialog mDialog; + private final String mPackageName; + + public UnsupportedCompileSdkDialog(final AppWarnings manager, Context context, + ApplicationInfo appInfo) { + mPackageName = appInfo.packageName; + + final PackageManager pm = context.getPackageManager(); + final CharSequence label = appInfo.loadSafeLabel(pm); + final CharSequence message = context.getString(R.string.unsupported_compile_sdk_message, + label, appInfo.compileSdkVersionCodename); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setPositiveButton(R.string.ok, null) + .setMessage(message) + .setView(R.layout.unsupported_compile_sdk_dialog_content); + + // If we might be able to update the app, show a button. + final Intent installerIntent = AppInstallerUtil.createIntent(context, appInfo.packageName); + if (installerIntent != null) { + builder.setNeutralButton(R.string.unsupported_compile_sdk_check_update, + (dialog, which) -> context.startActivity(installerIntent)); + } + + // Ensure the content view is prepared. + mDialog = builder.create(); + mDialog.create(); + + final Window window = mDialog.getWindow(); + window.setType(WindowManager.LayoutParams.TYPE_PHONE); + + // DO NOT MODIFY. Used by CTS to verify the dialog is displayed. + window.getAttributes().setTitle("UnsupportedCompileSdkDialog"); + + final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox); + alwaysShow.setChecked(true); + alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag( + mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked)); + } + + public String getPackageName() { + return mPackageName; + } + + public void show() { + mDialog.show(); + } + + public void dismiss() { + mDialog.dismiss(); + } +} diff --git a/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java b/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java index 501cd6bbba6d..88506632d7c3 100644 --- a/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java +++ b/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java @@ -30,7 +30,7 @@ public class UnsupportedDisplaySizeDialog { private final AlertDialog mDialog; private final String mPackageName; - public UnsupportedDisplaySizeDialog(final ActivityManagerService service, Context context, + public UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context, ApplicationInfo appInfo) { mPackageName = appInfo.packageName; @@ -54,14 +54,10 @@ public class UnsupportedDisplaySizeDialog { // DO NOT MODIFY. Used by CTS to verify the dialog is displayed. window.getAttributes().setTitle("UnsupportedDisplaySizeDialog"); - final CheckBox alwaysShow = (CheckBox) mDialog.findViewById(R.id.ask_checkbox); + final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox); alwaysShow.setChecked(true); - alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> { - synchronized (service) { - service.mCompatModePackages.setPackageNotifyUnsupportedZoomLocked( - mPackageName, isChecked); - } - }); + alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag( + mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked)); } public String getPackageName() { diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS new file mode 100644 index 000000000000..7e7335d68d3b --- /dev/null +++ b/services/core/java/com/android/server/display/OWNERS @@ -0,0 +1 @@ +michaelwr@google.com diff --git a/services/core/java/com/android/server/input/OWNERS b/services/core/java/com/android/server/input/OWNERS new file mode 100644 index 000000000000..0313a40f7270 --- /dev/null +++ b/services/core/java/com/android/server/input/OWNERS @@ -0,0 +1,2 @@ +michaelwr@google.com +svv@google.com diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index b9777ec0d968..4a3becbe3e07 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -151,6 +151,7 @@ public final class JobSchedulerService extends com.android.server.SystemService StorageController mStorageController; /** Need directly for sending uid state changes */ private BackgroundJobsController mBackgroundJobsController; + private DeviceIdleJobsController mDeviceIdleJobsController; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list * when ready to execute them. @@ -622,15 +623,24 @@ public final class JobSchedulerService extends com.android.server.SystemService if (disabled) { cancelJobsForUid(uid, "uid gone"); } + synchronized (mLock) { + mDeviceIdleJobsController.setUidActiveLocked(uid, false); + } } @Override public void onUidActive(int uid) throws RemoteException { + synchronized (mLock) { + mDeviceIdleJobsController.setUidActiveLocked(uid, true); + } } @Override public void onUidIdle(int uid, boolean disabled) { if (disabled) { cancelJobsForUid(uid, "app uid idle"); } + synchronized (mLock) { + mDeviceIdleJobsController.setUidActiveLocked(uid, false); + } } @Override public void onUidCachedChanged(int uid, boolean cached) { @@ -939,11 +949,11 @@ public final class JobSchedulerService extends com.android.server.SystemService mControllers.add(mBatteryController); mStorageController = StorageController.get(this); mControllers.add(mStorageController); - mBackgroundJobsController = BackgroundJobsController.get(this); - mControllers.add(mBackgroundJobsController); + mControllers.add(BackgroundJobsController.get(this)); mControllers.add(AppIdleController.get(this)); mControllers.add(ContentObserverController.get(this)); - mControllers.add(DeviceIdleJobsController.get(this)); + mDeviceIdleJobsController = DeviceIdleJobsController.get(this); + mControllers.add(mDeviceIdleJobsController); // If the job store determined that it can't yet reschedule persisted jobs, // we need to start watching the clock. diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java index 1af3b39e928b..28b60e3eadda 100644 --- a/services/core/java/com/android/server/job/JobStore.java +++ b/services/core/java/com/android/server/job/JobStore.java @@ -250,7 +250,7 @@ public final class JobStore { /** * @param userHandle User for whom we are querying the list of jobs. - * @return A list of all the jobs scheduled by the provided user. Never null. + * @return A list of all the jobs scheduled for the provided user. Never null. */ public List<JobStatus> getJobsByUser(int userHandle) { return mJobSet.getJobsByUser(userHandle); @@ -287,6 +287,10 @@ public final class JobStore { mJobSet.forEachJob(uid, functor); } + public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) { + mJobSet.forEachJobForSourceUid(sourceUid, functor); + } + public interface JobStatusFunctor { public void process(JobStatus jobStatus); } @@ -979,9 +983,12 @@ public final class JobStore { static final class JobSet { // Key is the getUid() originator of the jobs in each sheaf private SparseArray<ArraySet<JobStatus>> mJobs; + // Same data but with the key as getSourceUid() of the jobs in each sheaf + private SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid; public JobSet() { mJobs = new SparseArray<ArraySet<JobStatus>>(); + mJobsPerSourceUid = new SparseArray<>(); } public List<JobStatus> getJobsByUid(int uid) { @@ -995,10 +1002,10 @@ public final class JobStore { // By user, not by uid, so we need to traverse by key and check public List<JobStatus> getJobsByUser(int userId) { - ArrayList<JobStatus> result = new ArrayList<JobStatus>(); - for (int i = mJobs.size() - 1; i >= 0; i--) { - if (UserHandle.getUserId(mJobs.keyAt(i)) == userId) { - ArraySet<JobStatus> jobs = mJobs.valueAt(i); + final ArrayList<JobStatus> result = new ArrayList<JobStatus>(); + for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) { + final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i); if (jobs != null) { result.addAll(jobs); } @@ -1009,32 +1016,60 @@ public final class JobStore { public boolean add(JobStatus job) { final int uid = job.getUid(); + final int sourceUid = job.getSourceUid(); ArraySet<JobStatus> jobs = mJobs.get(uid); if (jobs == null) { jobs = new ArraySet<JobStatus>(); mJobs.put(uid, jobs); } - return jobs.add(job); + ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid); + if (jobsForSourceUid == null) { + jobsForSourceUid = new ArraySet<>(); + mJobsPerSourceUid.put(sourceUid, jobsForSourceUid); + } + return jobs.add(job) && jobsForSourceUid.add(job); } public boolean remove(JobStatus job) { final int uid = job.getUid(); - ArraySet<JobStatus> jobs = mJobs.get(uid); - boolean didRemove = (jobs != null) ? jobs.remove(job) : false; - if (didRemove && jobs.size() == 0) { - // no more jobs for this uid; let the now-empty set object be GC'd. - mJobs.remove(uid); + final ArraySet<JobStatus> jobs = mJobs.get(uid); + final int sourceUid = job.getSourceUid(); + final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid); + boolean didRemove = jobs != null && jobs.remove(job) && jobsForSourceUid.remove(job); + if (didRemove) { + if (jobs.size() == 0) { + // no more jobs for this uid; let the now-empty set object be GC'd. + mJobs.remove(uid); + } + if (jobsForSourceUid.size() == 0) { + mJobsPerSourceUid.remove(sourceUid); + } + return true; } - return didRemove; + return false; } - // Remove the jobs all users not specified by the whitelist of user ids + /** + * Removes the jobs of all users not specified by the whitelist of user ids. + * The jobs scheduled by non existent users will not be removed if they were + */ public void removeJobsOfNonUsers(int[] whitelist) { - for (int jobIndex = mJobs.size() - 1; jobIndex >= 0; jobIndex--) { - int jobUserId = UserHandle.getUserId(mJobs.keyAt(jobIndex)); - // check if job's user id is not in the whitelist + for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) { + final int jobUserId = UserHandle.getUserId(mJobsPerSourceUid.keyAt(jobSetIndex)); if (!ArrayUtils.contains(whitelist, jobUserId)) { - mJobs.removeAt(jobIndex); + mJobsPerSourceUid.removeAt(jobSetIndex); + } + } + for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) { + final ArraySet<JobStatus> jobsForUid = mJobs.valueAt(jobSetIndex); + for (int jobIndex = jobsForUid.size() - 1; jobIndex >= 0; jobIndex--) { + final int jobUserId = jobsForUid.valueAt(jobIndex).getUserId(); + if (!ArrayUtils.contains(whitelist, jobUserId)) { + jobsForUid.removeAt(jobIndex); + } + } + if (jobsForUid.size() == 0) { + mJobs.removeAt(jobSetIndex); } } } @@ -1077,6 +1112,7 @@ public final class JobStore { public void clear() { mJobs.clear(); + mJobsPerSourceUid.clear(); } public int size() { @@ -1112,8 +1148,17 @@ public final class JobStore { } } - public void forEachJob(int uid, JobStatusFunctor functor) { - ArraySet<JobStatus> jobs = mJobs.get(uid); + public void forEachJob(int callingUid, JobStatusFunctor functor) { + ArraySet<JobStatus> jobs = mJobs.get(callingUid); + if (jobs != null) { + for (int i = jobs.size() - 1; i >= 0; i--) { + functor.process(jobs.valueAt(i)); + } + } + } + + public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) { + final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid); if (jobs != null) { for (int i = jobs.size() - 1; i >= 0; i--) { functor.process(jobs.valueAt(i)); diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java index 374ab43ca736..b7eb9e063591 100644 --- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -16,14 +16,19 @@ package com.android.server.job.controllers; +import android.app.job.JobInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.PowerManager; import android.os.UserHandle; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseBooleanArray; import com.android.internal.util.ArrayUtils; import com.android.server.DeviceIdleController; @@ -42,11 +47,22 @@ public final class DeviceIdleJobsController extends StateController { private static final String LOG_TAG = "DeviceIdleJobsController"; private static final boolean LOG_DEBUG = false; + private static final long BACKGROUND_JOBS_DELAY = 3000; + + static final int PROCESS_BACKGROUND_JOBS = 1; // Singleton factory private static Object sCreationLock = new Object(); private static DeviceIdleJobsController sController; + /** + * These are jobs added with a special flag to indicate that they should be exempted from doze + * when the app is temp whitelisted or in the foreground. + */ + private final ArraySet<JobStatus> mAllowInIdleJobs; + private final SparseBooleanArray mForegroundUids; + private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor; + private final DeviceIdleJobsDelayHandler mHandler; private final JobSchedulerService mJobSchedulerService; private final PowerManager mPowerManager; private final DeviceIdleController.LocalService mLocalDeviceIdleController; @@ -57,14 +73,6 @@ public final class DeviceIdleJobsController extends StateController { private boolean mDeviceIdleMode; private int[] mDeviceIdleWhitelistAppIds; private int[] mPowerSaveTempWhitelistAppIds; - // These jobs were added when the app was in temp whitelist, these should be exempted from doze - private final ArraySet<JobStatus> mTempWhitelistedJobs; - - final JobStore.JobStatusFunctor mUpdateFunctor = new JobStore.JobStatusFunctor() { - @Override public void process(JobStatus jobStatus) { - updateTaskStateLocked(jobStatus); - } - }; /** * Returns a singleton for the DeviceIdleJobsController @@ -108,8 +116,8 @@ public final class DeviceIdleJobsController extends StateController { + Arrays.toString(mPowerSaveTempWhitelistAppIds)); } boolean changed = false; - for (int i = 0; i < mTempWhitelistedJobs.size(); i ++) { - changed |= updateTaskStateLocked(mTempWhitelistedJobs.valueAt(i)); + for (int i = 0; i < mAllowInIdleJobs.size(); i++) { + changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i)); } if (changed) { mStateChangedListener.onControllerStateChanged(); @@ -125,6 +133,7 @@ public final class DeviceIdleJobsController extends StateController { super(jobSchedulerService, context, lock); mJobSchedulerService = jobSchedulerService; + mHandler = new DeviceIdleJobsDelayHandler(context.getMainLooper()); // Register for device idle mode changes mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mLocalDeviceIdleController = @@ -132,7 +141,9 @@ public final class DeviceIdleJobsController extends StateController { mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds(); mPowerSaveTempWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds(); - mTempWhitelistedJobs = new ArraySet<>(); + mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor(); + mAllowInIdleJobs = new ArraySet<>(); + mForegroundUids = new SparseBooleanArray(); final IntentFilter filter = new IntentFilter(); filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); @@ -150,7 +161,20 @@ public final class DeviceIdleJobsController extends StateController { } mDeviceIdleMode = enabled; if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode); - mJobSchedulerService.getJobStore().forEachJob(mUpdateFunctor); + if (enabled) { + mHandler.removeMessages(PROCESS_BACKGROUND_JOBS); + mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor); + } else { + // When coming out of doze, process all foreground uids immediately, while others + // will be processed after a delay of 3 seconds. + for (int i = 0; i < mForegroundUids.size(); i++) { + if (mForegroundUids.valueAt(i)) { + mJobSchedulerService.getJobStore().forEachJobForSourceUid( + mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor); + } + } + mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY); + } } // Inform the job scheduler service about idle mode changes if (changed) { @@ -159,11 +183,30 @@ public final class DeviceIdleJobsController extends StateController { } /** + * Called by jobscheduler service to report uid state changes between active and idle + */ + public void setUidActiveLocked(int uid, boolean active) { + final boolean changed = (active != mForegroundUids.get(uid)); + if (!changed) { + return; + } + if (LOG_DEBUG) { + Slog.d(LOG_TAG, "uid " + uid + " going " + (active ? "active" : "inactive")); + } + mForegroundUids.put(uid, active); + mDeviceIdleUpdateFunctor.mChanged = false; + mJobSchedulerService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor); + if (mDeviceIdleUpdateFunctor.mChanged) { + mStateChangedListener.onControllerStateChanged(); + } + } + + /** * Checks if the given job's scheduling app id exists in the device idle user whitelist. */ boolean isWhitelistedLocked(JobStatus job) { - return ArrayUtils.contains(mDeviceIdleWhitelistAppIds, - UserHandle.getAppId(job.getSourceUid())); + return Arrays.binarySearch(mDeviceIdleWhitelistAppIds, + UserHandle.getAppId(job.getSourceUid())) >= 0; } /** @@ -175,31 +218,33 @@ public final class DeviceIdleJobsController extends StateController { } private boolean updateTaskStateLocked(JobStatus task) { - final boolean whitelisted = isWhitelistedLocked(task) - || (mTempWhitelistedJobs.contains(task) && isTempWhitelistedLocked(task)); - final boolean enableTask = !mDeviceIdleMode || whitelisted; + final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) + && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task)); + final boolean whitelisted = isWhitelistedLocked(task); + final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle; return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted); } @Override public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { - if (isTempWhitelistedLocked(jobStatus)) { - mTempWhitelistedJobs.add(jobStatus); - jobStatus.setDeviceNotDozingConstraintSatisfied(true, true); - } else { - updateTaskStateLocked(jobStatus); + if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) { + mAllowInIdleJobs.add(jobStatus); } + updateTaskStateLocked(jobStatus); } @Override public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) { - mTempWhitelistedJobs.remove(jobStatus); + if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) { + mAllowInIdleJobs.remove(jobStatus); + } } @Override public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) { pw.println("DeviceIdleJobsController"); + pw.println("mDeviceIdleMode=" + mDeviceIdleMode); mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() { @Override public void process(JobStatus jobStatus) { if (!jobStatus.shouldDump(filterUid)) { @@ -217,8 +262,42 @@ public final class DeviceIdleJobsController extends StateController { if (jobStatus.dozeWhitelisted) { pw.print(" WHITELISTED"); } + if (mAllowInIdleJobs.contains(jobStatus)) { + pw.print(" ALLOWED_IN_DOZE"); + } pw.println(); } }); } + + final class DeviceIdleUpdateFunctor implements JobStore.JobStatusFunctor { + boolean mChanged; + + @Override + public void process(JobStatus jobStatus) { + mChanged |= updateTaskStateLocked(jobStatus); + } + } + + final class DeviceIdleJobsDelayHandler extends Handler { + public DeviceIdleJobsDelayHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case PROCESS_BACKGROUND_JOBS: + // Just process all the jobs, the ones in foreground should already be running. + synchronized (mLock) { + mDeviceIdleUpdateFunctor.mChanged = false; + mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor); + if (mDeviceIdleUpdateFunctor.mChanged) { + mStateChangedListener.onControllerStateChanged(); + } + } + break; + } + } + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/lights/OWNERS b/services/core/java/com/android/server/lights/OWNERS new file mode 100644 index 000000000000..7e7335d68d3b --- /dev/null +++ b/services/core/java/com/android/server/lights/OWNERS @@ -0,0 +1 @@ +michaelwr@google.com diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6ba1d8de79cf..08da568d5931 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -16,15 +16,21 @@ package com.android.server.notification; +import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED; +import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_NONE; -import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_TELEVISION; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_NULL; import static android.service.notification.NotificationListenerService + .HINT_HOST_DISABLE_CALL_EFFECTS; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; +import static android.service.notification.NotificationListenerService + .HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; +import static android.service.notification.NotificationListenerService .NOTIFICATION_CHANNEL_OR_GROUP_ADDED; import static android.service.notification.NotificationListenerService .NOTIFICATION_CHANNEL_OR_GROUP_DELETED; @@ -32,12 +38,13 @@ import static android.service.notification.NotificationListenerService .NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; -import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; +import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; import static android.service.notification.NotificationListenerService.REASON_CLICK; import static android.service.notification.NotificationListenerService.REASON_ERROR; -import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; +import static android.service.notification.NotificationListenerService + .REASON_GROUP_SUMMARY_CANCELED; import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL; import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED; @@ -48,14 +55,10 @@ import static android.service.notification.NotificationListenerService.REASON_SN import static android.service.notification.NotificationListenerService.REASON_TIMEOUT; import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED; import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED; -import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; -import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; -import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON; import static android.service.notification.NotificationListenerService.TRIM_FULL; import static android.service.notification.NotificationListenerService.TRIM_LIGHT; - import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; @@ -68,17 +71,17 @@ import android.app.AlarmManager; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AutomaticZenRule; -import android.app.NotificationChannelGroup; -import android.app.backup.BackupManager; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.ITransientNotification; import android.app.Notification; import android.app.NotificationChannel; -import android.app.NotificationManager.Policy; +import android.app.NotificationChannelGroup; import android.app.NotificationManager; +import android.app.NotificationManager.Policy; import android.app.PendingIntent; import android.app.StatusBarManager; +import android.app.backup.BackupManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; @@ -119,8 +122,8 @@ import android.os.ShellCommand; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; -import android.os.Vibrator; import android.os.VibrationEffect; +import android.os.Vibrator; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.Condition; @@ -174,9 +177,9 @@ import com.android.server.SystemService; import com.android.server.lights.Light; import com.android.server.lights.LightsManager; import com.android.server.notification.ManagedServices.ManagedServiceInfo; +import com.android.server.notification.ManagedServices.UserProfiles; import com.android.server.policy.PhoneWindowManager; import com.android.server.statusbar.StatusBarManagerInternal; -import com.android.server.notification.ManagedServices.UserProfiles; import libcore.io.IoUtils; @@ -196,7 +199,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; @@ -1520,7 +1522,11 @@ public class NotificationManagerService extends SystemService { } } } + final NotificationChannel preUpdate = + mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), true); + mRankingHelper.updateNotificationChannel(pkg, uid, channel, true); + maybeNotifyChannelOwner(pkg, uid, preUpdate, channel); if (!fromListener) { final NotificationChannel modifiedChannel = @@ -1533,12 +1539,40 @@ public class NotificationManagerService extends SystemService { savePolicyFile(); } + private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate, + NotificationChannel update) { + try { + if ((preUpdate.getImportance() == IMPORTANCE_NONE + && update.getImportance() != IMPORTANCE_NONE) + || (preUpdate.getImportance() != IMPORTANCE_NONE + && update.getImportance() == IMPORTANCE_NONE)) { + getContext().sendBroadcastAsUser( + new Intent(ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED) + .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID, + update.getId()) + .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, + update.getImportance() == IMPORTANCE_NONE) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .setPackage(pkg), + UserHandle.of(UserHandle.getUserId(uid)), null); + } + } catch (SecurityException e) { + Slog.w(TAG, "Can't notify app about channel change", e); + } + } + private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromApp, boolean fromListener) { Preconditions.checkNotNull(group); Preconditions.checkNotNull(pkg); + + final NotificationChannelGroup preUpdate = + mRankingHelper.getNotificationChannelGroup(group.getId(), pkg, uid); mRankingHelper.createNotificationChannelGroup(pkg, uid, group, fromApp); + if (!fromApp) { + maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group); + } if (!fromListener) { mListeners.notifyNotificationChannelGroupChanged(pkg, UserHandle.of(UserHandle.getCallingUserId()), group, @@ -1546,6 +1580,25 @@ public class NotificationManagerService extends SystemService { } } + private void maybeNotifyChannelGroupOwner(String pkg, int uid, + NotificationChannelGroup preUpdate, NotificationChannelGroup update) { + try { + if (preUpdate.isBlocked() != update.isBlocked()) { + getContext().sendBroadcastAsUser( + new Intent(ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED) + .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID, + update.getId()) + .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, + update.isBlocked()) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .setPackage(pkg), + UserHandle.of(UserHandle.getUserId(uid)), null); + } + } catch (SecurityException e) { + Slog.w(TAG, "Can't notify app about group change", e); + } + } + private ArrayList<ComponentName> getSuppressors() { ArrayList<ComponentName> names = new ArrayList<ComponentName>(); for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) { @@ -1924,11 +1977,18 @@ public class NotificationManagerService extends SystemService { } @Override + public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) { + checkCallerIsSystemOrSameApp(pkg); + return mRankingHelper.getNotificationChannelGroupWithChannels( + pkg, Binder.getCallingUid(), groupId, false); + } + + @Override public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups( String pkg) { checkCallerIsSystemOrSameApp(pkg); - return new ParceledListSlice<>(new ArrayList( - mRankingHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid()))); + return mRankingHelper.getNotificationChannelGroups( + pkg, Binder.getCallingUid(), false, false); } @Override @@ -1998,7 +2058,7 @@ public class NotificationManagerService extends SystemService { public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage( String pkg, int uid, boolean includeDeleted) { checkCallerIsSystem(); - return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted); + return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true); } @Override diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index b9c0d90741f3..b1b0bf26d9ee 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -36,7 +36,7 @@ public interface RankingConfig { void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromTargetApp); ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, - int uid, boolean includeDeleted); + int uid, boolean includeDeleted, boolean includeNonGrouped); void createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp); void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index d566a45011d7..c0dccb53c08a 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -750,12 +750,15 @@ public class RankingHelper implements RankingConfig { int uid) { Preconditions.checkNotNull(pkg); Record r = getRecord(pkg, uid); + if (r == null) { + return null; + } return r.groups.get(groupId); } @Override public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, - int uid, boolean includeDeleted) { + int uid, boolean includeDeleted, boolean includeNonGrouped) { Preconditions.checkNotNull(pkg); Map<String, NotificationChannelGroup> groups = new ArrayMap<>(); Record r = getRecord(pkg, uid); @@ -783,7 +786,7 @@ public class RankingHelper implements RankingConfig { } } } - if (nonGrouped.getChannels().size() > 0) { + if (includeNonGrouped && nonGrouped.getChannels().size() > 0) { groups.put(null, nonGrouped); } return new ParceledListSlice<>(new ArrayList<>(groups.values())); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index bbe59eb84904..2e44e7973cab 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3585,7 +3585,7 @@ public class PackageManagerService extends IPackageManager.Stub final int N = list.size(); for (int i = 0; i < N; i++) { ResolveInfo info = list.get(i); - if (packageName.equals(info.activityInfo.packageName)) { + if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) { return true; } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 7748ae4f3fe0..384070cf4777 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -304,6 +304,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; static final int LONG_PRESS_POWER_SHUT_OFF = 2; static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3; + static final int LONG_PRESS_POWER_GO_TO_VOICE_ASSIST = 4; + + static final int VERY_LONG_PRESS_POWER_NOTHING = 0; + static final int VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; static final int MULTI_PRESS_POWER_NOTHING = 0; static final int MULTI_PRESS_POWER_THEATER_MODE = 1; @@ -569,6 +573,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mLidControlsSleep; int mShortPressOnPowerBehavior; int mLongPressOnPowerBehavior; + int mVeryLongPressOnPowerBehavior; int mDoublePressOnPowerBehavior; int mTriplePressOnPowerBehavior; int mLongPressOnBackBehavior; @@ -586,6 +591,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mHasSoftInput = false; boolean mTranslucentDecorEnabled = true; boolean mUseTvRouting; + int mVeryLongPressTimeout; private boolean mHandleVolumeKeysInWM; @@ -796,6 +802,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_HANDLE_ALL_APPS = 26; private static final int MSG_LAUNCH_ASSIST = 27; private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28; + private static final int MSG_POWER_VERY_LONG_PRESS = 29; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1; @@ -855,6 +862,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_POWER_LONG_PRESS: powerLongPress(); break; + case MSG_POWER_VERY_LONG_PRESS: + powerVeryLongPress(); + break; case MSG_UPDATE_DREAMING_SLEEP_TOKEN: updateDreamingSleepToken(msg.arg1 != 0); break; @@ -1299,6 +1309,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); + + if (hasVeryLongPressOnPowerBehavior()) { + Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS); + longMsg.setAsynchronous(true); + mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout); + } } } else { wakeUpFromPowerKey(event.getDownTime()); @@ -1308,6 +1324,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); + + if (hasVeryLongPressOnPowerBehavior()) { + Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS); + longMsg.setAsynchronous(true); + mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout); + } + mBeganFromNonInteractive = true; } else { final int maxCount = getMaxMultiPressPowerCount(); @@ -1369,6 +1392,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerKeyHandled = true; mHandler.removeMessages(MSG_POWER_LONG_PRESS); } + if (hasVeryLongPressOnPowerBehavior()) { + mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS); + } } private void cancelPendingBackKeyAction() { @@ -1516,6 +1542,29 @@ public class PhoneWindowManager implements WindowManagerPolicy { sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF); break; + case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST: + mPowerKeyHandled = true; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + final boolean keyguardActive = mKeyguardDelegate == null + ? false + : mKeyguardDelegate.isShowing(); + if (!keyguardActive) { + Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); + startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); + } + break; + } + } + + private void powerVeryLongPress() { + switch (mVeryLongPressOnPowerBehavior) { + case VERY_LONG_PRESS_POWER_NOTHING: + break; + case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS: + mPowerKeyHandled = true; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + showGlobalActionsInternal(); + break; } } @@ -1574,6 +1623,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING; } + private boolean hasVeryLongPressOnPowerBehavior() { + return mVeryLongPressOnPowerBehavior != VERY_LONG_PRESS_POWER_NOTHING; + } + private boolean hasLongPressOnBackBehavior() { return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING; } @@ -1979,12 +2032,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { com.android.internal.R.integer.config_shortPressOnPowerBehavior); mLongPressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_longPressOnPowerBehavior); + mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_veryLongPressOnPowerBehavior); mDoublePressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_doublePressOnPowerBehavior); mTriplePressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_triplePressOnPowerBehavior); mShortPressOnSleepBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_shortPressOnSleepBehavior); + mVeryLongPressTimeout = mContext.getResources().getInteger( + com.android.internal.R.integer.config_veryLongPressTimeout); mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION; @@ -8193,6 +8250,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print("mLongPressOnPowerBehavior="); pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior)); pw.print(prefix); + pw.print("mVeryLongPressOnPowerBehavior="); + pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior)); + pw.print(prefix); pw.print("mDoublePressOnPowerBehavior="); pw.println(multiPressOnPowerBehaviorToString(mDoublePressOnPowerBehavior)); pw.print(prefix); @@ -8445,6 +8505,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { return Integer.toString(behavior); } } + + private static String veryLongPressOnPowerBehaviorToString(int behavior) { + switch (behavior) { + case VERY_LONG_PRESS_POWER_NOTHING: + return "VERY_LONG_PRESS_POWER_NOTHING"; + case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS: + return "VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS"; + default: + return Integer.toString(behavior); + } + } + private static String multiPressOnPowerBehaviorToString(int behavior) { switch (behavior) { case MULTI_PRESS_POWER_NOTHING: diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java index 336df48b9adc..87c92744cdab 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 @@ public class BatterySaverPolicy extends ContentObserver { private static final String KEY_FIREWALL_DISABLED = "firewall_disabled"; private static final String KEY_ADJUST_BRIGHTNESS_DISABLED = "adjust_brightness_disabled"; private static final String KEY_DATASAVER_DISABLED = "datasaver_disabled"; + private static final String KEY_LAUNCH_BOOST_DISABLED = "launch_boost_disabled"; private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor"; private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred"; private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred"; @@ -73,9 +74,16 @@ public class BatterySaverPolicy extends ContentObserver { 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 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_VIBRATION_DISABLED */ + @GuardedBy("mLock") private boolean mVibrationDisabled; /** @@ -91,6 +100,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_ANIMATION_DISABLED */ + @GuardedBy("mLock") private boolean mAnimationDisabled; /** @@ -100,6 +110,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_SOUNDTRIGGER_DISABLED */ + @GuardedBy("mLock") private boolean mSoundTriggerDisabled; /** @@ -108,6 +119,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_FULLBACKUP_DEFERRED */ + @GuardedBy("mLock") private boolean mFullBackupDeferred; /** @@ -116,6 +128,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_KEYVALUE_DEFERRED */ + @GuardedBy("mLock") private boolean mKeyValueBackupDeferred; /** @@ -124,6 +137,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_FIREWALL_DISABLED */ + @GuardedBy("mLock") private boolean mFireWallDisabled; /** @@ -132,6 +146,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_ADJUST_BRIGHTNESS_DISABLED */ + @GuardedBy("mLock") private boolean mAdjustBrightnessDisabled; /** @@ -140,14 +155,22 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_DATASAVER_DISABLED */ + @GuardedBy("mLock") private boolean mDataSaverDisabled; /** + * {@code true} if launch boost should be disabled on battery saver. + */ + @GuardedBy("mLock") + private boolean mLaunchBoostDisabled; + + /** * This is the flag to decide the gps mode in battery saver mode. * * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_GPS_MODE */ + @GuardedBy("mLock") private int mGpsMode; /** @@ -157,20 +180,21 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_ADJUST_BRIGHTNESS_FACTOR */ + @GuardedBy("mLock") private float mAdjustBrightnessFactor; /** * Whether to put all apps in the stand-by mode. */ + @GuardedBy("mLock") private boolean mForceAllAppsStandby; /** * Weather to show non-essential sensors (e.g. edge sensors) or not. */ + @GuardedBy("mLock") private boolean mOptionalSensorsDisabled; - private final Object mLock = new Object(); - @GuardedBy("mLock") private Context mContext; @@ -227,7 +251,11 @@ public class BatterySaverPolicy extends ContentObserver { @VisibleForTesting String getGlobalSetting(String key) { - return Settings.Global.getString(mContentResolver, key); + final ContentResolver cr; + synchronized (mLock) { + cr = mContentResolver; + } + return Settings.Global.getString(cr, key); } @VisibleForTesting @@ -296,6 +324,7 @@ public class BatterySaverPolicy extends ContentObserver { mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false); mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f); mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true); + mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true); mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true); mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true); @@ -382,6 +411,12 @@ public class BatterySaverPolicy extends ContentObserver { } } + public boolean isLaunchBoostDisabled() { + synchronized (mLock) { + return mLaunchBoostDisabled; + } + } + public void dump(PrintWriter pw) { synchronized (mLock) { pw.println(); @@ -398,6 +433,7 @@ public class BatterySaverPolicy extends ContentObserver { pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred); pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled); pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled); + pw.println(" " + KEY_LAUNCH_BOOST_DISABLED + "=" + mLaunchBoostDisabled); pw.println(" " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled); pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor); pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode); diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS new file mode 100644 index 000000000000..b4300a930cdf --- /dev/null +++ b/services/core/java/com/android/server/power/OWNERS @@ -0,0 +1,3 @@ +michaelwr@google.com + +per-file BatterySaverPolicy.java=omakoto@google.com diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 0ca016716cdd..2c87a4034300 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 @@ public final class PowerManagerService extends SystemService mIsVrModeEnabled = enabled; } - private static void powerHintInternal(int hintId, int data) { + private void powerHintInternal(int hintId, int data) { + // Maybe filter the event. + switch (hintId) { + case PowerHint.LAUNCH: // 1: activate launch boost 0: deactivate. + if (data == 1 && mBatterySaverController.isLaunchBoostDisabled()) { + return; + } + break; + } + nativeSendPowerHint(hintId, data); } diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java index b471c8d9ab60..ae01ea5721d8 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 @@ public class BatterySaverController implements BatterySaverPolicyListener { } /** + * @return true if launch boost should currently be disabled. + */ + public boolean isLaunchBoostDisabled() { + return isEnabled() && mBatterySaverPolicy.isLaunchBoostDisabled(); + } + + /** * Dispatch power save events to the listeners. * * This method is always called on the handler thread. diff --git a/services/core/java/com/android/server/power/batterysaver/OWNERS b/services/core/java/com/android/server/power/batterysaver/OWNERS new file mode 100644 index 000000000000..09136dcc2feb --- /dev/null +++ b/services/core/java/com/android/server/power/batterysaver/OWNERS @@ -0,0 +1 @@ +omakoto@google.com diff --git a/services/core/java/com/android/server/utils/AppInstallerUtil.java b/services/core/java/com/android/server/utils/AppInstallerUtil.java new file mode 100644 index 000000000000..af7ff41f4455 --- /dev/null +++ b/services/core/java/com/android/server/utils/AppInstallerUtil.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.util.Log; + +public class AppInstallerUtil { + private static final String LOG_TAG = "AppInstallerUtil"; + + private static Intent resolveIntent(Context context, Intent i) { + ResolveInfo result = context.getPackageManager().resolveActivity(i, 0); + return result != null ? new Intent(i.getAction()) + .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null; + } + + /** + * Returns the package name of the app which installed a given packageName, if available. + */ + public static String getInstallerPackageName(Context context, String packageName) { + String installerPackageName = null; + try { + installerPackageName = + context.getPackageManager().getInstallerPackageName(packageName); + } catch (IllegalArgumentException e) { + Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e); + } + if (installerPackageName == null) { + return null; + } + return installerPackageName; + } + + /** + * Returns an intent to launcher the installer for a given package name. + */ + public static Intent createIntent(Context context, String installerPackageName, + String packageName) { + Intent intent = new Intent(Intent.ACTION_SHOW_APP_INFO).setPackage(installerPackageName); + final Intent result = resolveIntent(context, intent); + if (result != null) { + result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + return result; + } + return null; + } + + /** + * Convenience method that looks up the installerPackageName. + */ + public static Intent createIntent(Context context, String packageName) { + String installerPackageName = getInstallerPackageName(context, packageName); + return createIntent(context, installerPackageName, packageName); + } +} diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 52b7a25684f1..730ec3795ac0 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2109,7 +2109,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (task != null) { return task.getDimmer(); } - return getStack().getDimmer(); + TaskStack taskStack = getStack(); + if (taskStack != null) { + return taskStack.getDimmer(); + } + return null; } /** Returns true if the replacement window was removed. */ @@ -4390,10 +4394,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mToken.makeChildSurface(this); } - - @Override - void prepareSurfaces() { - mIsDimming = false; + private void applyDims(Dimmer dimmer) { if (!mAnimatingExit && mAppDied) { mIsDimming = true; getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW); @@ -4402,6 +4403,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mIsDimming = true; getDimmer().dimBelow(getPendingTransaction(), this, mAttrs.dimAmount); } + } + + @Override + void prepareSurfaces() { + final Dimmer dimmer = getDimmer(); + mIsDimming = false; + if (dimmer != null) { + applyDims(dimmer); + } mWinAnimator.prepareSurfaceLocked(true); super.prepareSurfaces(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2d8a0ee02225..60c36d1b7203 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 000000000000..54d233a94815 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; + +import com.android.internal.backup.IBackupTransport; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLooper; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 23) +@Presubmit +public class TransportClientTest { + private static final String PACKAGE_NAME = "some.package.name"; + private static final ComponentName TRANSPORT_COMPONENT = + new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport"); + + @Mock private Context mContext; + @Mock private TransportConnectionListener mTransportConnectionListener; + @Mock private TransportConnectionListener mTransportConnectionListener2; + @Mock private IBackupTransport.Stub mIBackupTransport; + private TransportClient mTransportClient; + private Intent mBindIntent; + private ShadowLooper mShadowLooper; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Looper mainLooper = Looper.getMainLooper(); + mShadowLooper = shadowOf(mainLooper); + mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(TRANSPORT_COMPONENT); + mTransportClient = + new TransportClient( + mContext, mBindIntent, TRANSPORT_COMPONENT, "1", new Handler(mainLooper)); + + when(mContext.bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class))) + .thenReturn(true); + } + + // TODO: Testing implementation? Remove? + @Test + public void testConnectAsync_callsBindService() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + verify(mContext) + .bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class)); + } + + @Test + public void testConnectAsync_callsListenerWhenConnected() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + // Simulate framework connecting + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenPendingConnection_callsAllListenersWhenConnected() + throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); + + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + verify(mTransportConnectionListener2) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenAlreadyConnected_callsListener() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener2) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenFrameworkDoesntBind_callsListener() throws Exception { + when(mContext.bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class))) + .thenReturn(false); + + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenFrameworkDoesntBind_releasesConnection() throws Exception { + when(mContext.bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class))) + .thenReturn(false); + + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + verify(mContext).unbindService(eq(connection)); + } + + @Test + public void testConnectAsync_afterServiceDisconnectedBeforeNewConnection_callsListener() + throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + connection.onServiceDisconnected(TRANSPORT_COMPONENT); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller1"); + + verify(mTransportConnectionListener2) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_afterServiceDisconnectedAfterNewConnection_callsListener() + throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + connection.onServiceDisconnected(TRANSPORT_COMPONENT); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller1"); + + // Yes, it should return null because the object became unusable, check design doc + verify(mTransportConnectionListener2) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); + } + + // TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests + /*@Test + public void testConnectAsync_callsListenerIfBindingDies() throws Exception { + mTransportClient.connectAsync(mTransportListener, "caller"); + + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onBindingDied(TRANSPORT_COMPONENT); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies() + throws Exception { + mTransportClient.connectAsync(mTransportListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + + mTransportClient.connectAsync(mTransportListener2, "caller2"); + + connection.onBindingDied(TRANSPORT_COMPONENT); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient)); + verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient)); + }*/ + + private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) { + ArgumentCaptor<ServiceConnection> connectionCaptor = + ArgumentCaptor.forClass(ServiceConnection.class); + verify(context) + .bindServiceAsUser( + any(Intent.class), + connectionCaptor.capture(), + anyInt(), + any(UserHandle.class)); + return connectionCaptor.getValue(); + } +} diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index c145e828c49c..55ec133a29da 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -16,8 +16,10 @@ package com.android.server.notification; +import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.content.pm.PackageManager.FEATURE_WATCH; @@ -940,6 +942,120 @@ public class NotificationManagerServiceTest extends NotificationTestCase { } @Test + public void testUpdateChannelNotifyCreatorBlock() throws Exception { + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(mTestNotificationChannel); + + NotificationChannel updatedChannel = + new NotificationChannel(mTestNotificationChannel.getId(), + mTestNotificationChannel.getName(), IMPORTANCE_NONE); + + mBinderService.updateNotificationChannelForPackage(PKG, 0, updatedChannel); + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertEquals(mTestNotificationChannel.getId(), captor.getValue().getStringExtra( + NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID)); + assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)); + } + + @Test + public void testUpdateChannelNotifyCreatorUnblock() throws Exception { + NotificationChannel existingChannel = + new NotificationChannel(mTestNotificationChannel.getId(), + mTestNotificationChannel.getName(), IMPORTANCE_NONE); + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(existingChannel); + + mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel); + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertEquals(mTestNotificationChannel.getId(), captor.getValue().getStringExtra( + NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID)); + assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)); + } + + @Test + public void testUpdateChannelNoNotifyCreatorOtherChanges() throws Exception { + NotificationChannel existingChannel = + new NotificationChannel(mTestNotificationChannel.getId(), + mTestNotificationChannel.getName(), IMPORTANCE_MAX); + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(existingChannel); + + mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel); + verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null)); + } + + @Test + public void testUpdateGroupNotifyCreatorBlock() throws Exception { + NotificationChannelGroup existing = new NotificationChannelGroup("id", "name"); + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt())) + .thenReturn(existing); + + NotificationChannelGroup updated = new NotificationChannelGroup("id", "name"); + updated.setBlocked(true); + + mBinderService.updateNotificationChannelGroupForPackage(PKG, 0, updated); + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertEquals(existing.getId(), captor.getValue().getStringExtra( + NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID)); + assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)); + } + + @Test + public void testUpdateGroupNotifyCreatorUnblock() throws Exception { + NotificationChannelGroup existing = new NotificationChannelGroup("id", "name"); + existing.setBlocked(true); + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt())) + .thenReturn(existing); + + mBinderService.updateNotificationChannelGroupForPackage( + PKG, 0, new NotificationChannelGroup("id", "name")); + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertEquals(existing.getId(), captor.getValue().getStringExtra( + NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID)); + assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)); + } + + @Test + public void testUpdateGroupNoNotifyCreatorOtherChanges() throws Exception { + NotificationChannelGroup existing = new NotificationChannelGroup("id", "name"); + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt())) + .thenReturn(existing); + + mBinderService.updateNotificationChannelGroupForPackage( + PKG, 0, new NotificationChannelGroup("id", "new name")); + verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null)); + } + + @Test public void testCreateChannelNotifyListener() throws Exception { List<String> associations = new ArrayList<>(); associations.add("a"); @@ -1040,6 +1156,9 @@ public class NotificationManagerServiceTest extends NotificationTestCase { List<String> associations = new ArrayList<>(); associations.add("a"); when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(mTestNotificationChannel); mBinderService.updateNotificationChannelFromPrivilegedListener( null, PKG, Process.myUserHandle(), mTestNotificationChannel); diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java index 3c02e23eed2a..2d03f111e528 100644 --- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java @@ -372,7 +372,7 @@ public class RankingHelperTest extends NotificationTestCase { mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false)); List<NotificationChannelGroup> actualGroups = - mHelper.getNotificationChannelGroups(PKG, UID, false).getList(); + mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList(); boolean foundNcg = false; for (NotificationChannelGroup actual : actualGroups) { if (ncg.getId().equals(actual.getId())) { @@ -442,7 +442,7 @@ public class RankingHelperTest extends NotificationTestCase { mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false)); List<NotificationChannelGroup> actualGroups = - mHelper.getNotificationChannelGroups(PKG, UID, false).getList(); + mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList(); boolean foundNcg = false; for (NotificationChannelGroup actual : actualGroups) { if (ncg.getId().equals(actual.getId())) { @@ -1281,7 +1281,8 @@ public class RankingHelperTest extends NotificationTestCase { mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID}); - assertEquals(0, mHelper.getNotificationChannelGroups(PKG, UID, true).getList().size()); + assertEquals(0, + mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList().size()); } @Test @@ -1370,7 +1371,7 @@ public class RankingHelperTest extends NotificationTestCase { mHelper.createNotificationChannel(PKG, UID, channel3, true); List<NotificationChannelGroup> actual = - mHelper.getNotificationChannelGroups(PKG, UID, true).getList(); + mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList(); assertEquals(3, actual.size()); for (NotificationChannelGroup group : actual) { if (group.getId() == null) { @@ -1402,13 +1403,13 @@ public class RankingHelperTest extends NotificationTestCase { new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); channel1.setGroup(ncg.getId()); mHelper.createNotificationChannel(PKG, UID, channel1, true); - mHelper.getNotificationChannelGroups(PKG, UID, true).getList(); + mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList(); channel1.setImportance(IMPORTANCE_LOW); mHelper.updateNotificationChannel(PKG, UID, channel1, true); List<NotificationChannelGroup> actual = - mHelper.getNotificationChannelGroups(PKG, UID, true).getList(); + mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList(); assertEquals(2, actual.size()); for (NotificationChannelGroup group : actual) { diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java index fa8feb048fd6..1f9a243d0f22 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java @@ -197,6 +197,8 @@ public class PackageParserTest { assertEquals(a.coreApp, b.coreApp); assertEquals(a.mRequiredForAllUsers, b.mRequiredForAllUsers); assertEquals(a.mTrustedOverlay, b.mTrustedOverlay); + assertEquals(a.mCompileSdkVersion, b.mCompileSdkVersion); + assertEquals(a.mCompileSdkVersionCodename, b.mCompileSdkVersionCodename); assertEquals(a.use32bitAbi, b.use32bitAbi); assertEquals(a.packageName, b.packageName); assertTrue(Arrays.equals(a.splitNames, b.splitNames)); diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java index 011817e99714..884ba70b90f4 100644 --- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java +++ b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java @@ -27,12 +27,11 @@ import android.util.Log; public class TestJobActivity extends Activity { private static final String TAG = TestJobActivity.class.getSimpleName(); - public static final String EXTRA_JOB_ID_KEY = - "com.android.servicestests.apps.jobtestapp.extra.JOB_ID"; - public static final String ACTION_START_JOB = - "com.android.servicestests.apps.jobtestapp.action.START_JOB"; - public static final String ACTION_CANCEL_JOBS = - "com.android.servicestests.apps.jobtestapp.action.CANCEL_JOBS"; + private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp"; + + public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID"; + public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB"; + public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS"; public static final int JOB_INITIAL_BACKOFF = 10_000; public static final int JOB_MINIMUM_LATENCY = 5_000; @@ -59,6 +58,8 @@ public class TestJobActivity extends Activity { Log.d(TAG, "Successfully scheduled job with id " + jobId); } break; + default: + Log.e(TAG, "Unknown action " + intent.getAction()); } finish(); } diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index 095fdc63975c..9bc9cd04957c 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -323,7 +323,8 @@ public class UsbHostManager { } /* Opens the specified USB device */ - public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) { + public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings, + String packageName, int uid) { synchronized (mLock) { if (isBlackListed(deviceName)) { throw new SecurityException("USB device is on a restricted bus"); @@ -334,7 +335,7 @@ public class UsbHostManager { throw new IllegalArgumentException( "device " + deviceName + " does not exist or is restricted"); } - settings.checkPermission(device); + settings.checkPermission(device, packageName, uid); return nativeOpenDevice(deviceName); } } diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index e4fcea77fa44..17de83f0cac9 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -232,7 +232,7 @@ public class UsbService extends IUsbManager.Stub { /* Opens the specified USB device (host mode) */ @Override - public ParcelFileDescriptor openDevice(String deviceName) { + public ParcelFileDescriptor openDevice(String deviceName, String packageName) { ParcelFileDescriptor fd = null; if (mHostManager != null) { @@ -242,7 +242,8 @@ public class UsbService extends IUsbManager.Stub { boolean isCurrentUser = isCallerInCurrentUserProfileGroupLocked(); if (isCurrentUser) { - fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt)); + fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt), + packageName, Binder.getCallingUid()); } else { Slog.w(TAG, "Cannot open " + deviceName + " for user " + userIdInt + " as user is not active."); @@ -308,9 +309,10 @@ public class UsbService extends IUsbManager.Stub { } @Override - public boolean hasDevicePermission(UsbDevice device) { + public boolean hasDevicePermission(UsbDevice device, String packageName) { final int userId = UserHandle.getCallingUserId(); - return getSettingsForUser(userId).hasPermission(device); + return getSettingsForUser(userId).hasPermission(device, packageName, + Binder.getCallingUid()); } @Override @@ -322,7 +324,8 @@ public class UsbService extends IUsbManager.Stub { @Override public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) { final int userId = UserHandle.getCallingUserId(); - getSettingsForUser(userId).requestPermission(device, packageName, pi); + getSettingsForUser(userId).requestPermission(device, packageName, pi, + Binder.getCallingUid()); } @Override diff --git a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java index 96c5211cecf4..11e43e308e8a 100644 --- a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java @@ -26,6 +26,8 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbManager; import android.os.Binder; import android.os.Process; @@ -95,10 +97,70 @@ class UsbUserSettingsManager { } } + /** + * Check whether a particular device or any of its interfaces + * is of class VIDEO. + * + * @param device The device that needs to get scanned + * @return True in case a VIDEO device or interface is present, + * False otherwise. + */ + private boolean isCameraDevicePresent(UsbDevice device) { + if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) { + return true; + } + + for (int i = 0; i < device.getInterfaceCount(); i++) { + UsbInterface iface = device.getInterface(i); + if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) { + return true; + } + } + + return false; + } + + /** + * Check for camera permission of the calling process. + * + * @param packageName Package name of the caller. + * @param uid Linux uid of the calling process. + * + * @return True in case camera permission is available, False otherwise. + */ + private boolean isCameraPermissionGranted(String packageName, int uid) { + int targetSdkVersion = android.os.Build.VERSION_CODES.P; + try { + ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0); + // compare uid with packageName to foil apps pretending to be someone else + if (aInfo.uid != uid) { + Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid); + return false; + } + targetSdkVersion = aInfo.targetSdkVersion; + } catch (PackageManager.NameNotFoundException e) { + Slog.i(TAG, "Package not found, likely due to invalid package name!"); + return false; + } + + if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) { + int allowed = mUserContext.checkCallingPermission(android.Manifest.permission.CAMERA); + if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) { + Slog.i(TAG, "Camera permission required for USB video class devices"); + return false; + } + } - public boolean hasPermission(UsbDevice device) { + return true; + } + + public boolean hasPermission(UsbDevice device, String packageName, int uid) { synchronized (mLock) { - int uid = Binder.getCallingUid(); + if (isCameraDevicePresent(device)) { + if (!isCameraPermissionGranted(packageName, uid)) { + return false; + } + } if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { return true; } @@ -124,8 +186,8 @@ class UsbUserSettingsManager { } } - public void checkPermission(UsbDevice device) { - if (!hasPermission(device)) { + public void checkPermission(UsbDevice device, String packageName, int uid) { + if (!hasPermission(device, packageName, uid)) { throw new SecurityException("User has not given permission to device " + device); } } @@ -166,11 +228,11 @@ class UsbUserSettingsManager { } } - public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) { + public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) { Intent intent = new Intent(); // respond immediately if permission has already been granted - if (hasPermission(device)) { + if (hasPermission(device, packageName, uid)) { intent.putExtra(UsbManager.EXTRA_DEVICE, device); intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); try { @@ -180,6 +242,18 @@ class UsbUserSettingsManager { } return; } + if (isCameraDevicePresent(device)) { + if (!isCameraPermissionGranted(packageName, uid)) { + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); + try { + pi.send(mUserContext, 0, intent); + } catch (PendingIntent.CanceledException e) { + if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); + } + return; + } + } // start UsbPermissionActivity so user can choose an activity intent.putExtra(UsbManager.EXTRA_DEVICE, device); diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index 132b234ab1e4..ffcef8966654 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -346,22 +346,20 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) { can_be_conditional &= CollectLocations(location, keep_set, &locations); } - for (const UsageLocation& location : entry.second) { - printer.Print("# Referenced at ").Println(location.source.to_string()); - } if (keep_set.conditional_keep_rules_ && can_be_conditional) { - printer.Println("-if class **.R$layout {"); - printer.Indent(); for (const UsageLocation& location : locations) { - printer.Print("int ") + printer.Print("# Referenced at ").Println(location.source.to_string()); + printer.Print("-if class **.R$layout { int ") .Print(JavaClassGenerator::TransformToFieldName(location.name.entry)) - .Println(";"); + .Println("; }"); + printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); } - printer.Undent(); - printer.Println("}"); - printer.Println(); + } else { + for (const UsageLocation& location : entry.second) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } + printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); } - printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); printer.Println(); } |